Mobile Testing with Appium: iOS and Android Automation Complete Guide
Appium is an open-source mobile test automation framework that allows you to write tests against iOS and Android apps using the same WebDriver API used for browser automation — making it accessible to developers familiar with Selenium.
What You’ll Learn
- Setting up Appium for Android and iOS automation
- Finding elements with accessibility IDs, XPath, and class names
- Simulating gestures: tap, swipe, scroll, pinch, and long press
- Running tests on real devices and cloud services (BrowserStack, Sauce Labs)
- Integrating Appium tests into CI/CD pipelines
Why Appium Matters
Mobile app testing is harder than web testing because of device fragmentation — thousands of Android devices with different screen sizes, OS versions, and hardware configurations. Appium lets you write one test suite that runs across platforms using the WebDriver protocol. It’s the industry standard for mobile test automation, supported by most cloud testing providers.
Doda Browser uses Appium to test its mobile browser across 200+ Android and iOS device configurations, ensuring consistent performance on everything from budget phones to flagship devices.
Learning Path
flowchart LR
A[Selenium Basics] --> B[Appium Mobile Testing<br/>You are here]
B --> C[Cloud Device Testing]
C --> D[CI/CD Integration]
D --> E[Performance Monitoring]
style B fill:#f90,color:#fff
Setup
Prerequisites
# Install Appium server
npm install -g appium
# Install drivers
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS
# Install Appium client library (Python example)
pip install Appium-Python-Client
# Or for JavaScript
npm install --save-dev webdriverioAndroid Setup
# Required tools
- Android SDK (Android Studio)
- ADB (Android Debug Bridge)
- Appium Settings APK (installed automatically)
# Set environment variables
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/emulatoriOS Setup
# Required tools (macOS only)
- Xcode 14+
- Xcode Command Line Tools
- Carthage (for WebDriverAgent)
# Install dependencies
xcode-select --install
brew install carthageFirst Test: Android
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
desired_caps = {
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Pixel_6",
"appium:app": "/path/to/app.apk",
"appium:appPackage": "com.example.app",
"appium:appActivity": ".MainActivity",
"appium:noReset": False,
}
driver = webdriver.Remote("http://localhost:4723", desired_caps)
# Find and interact with elements
welcome_text = driver.find_element(AppiumBy.ID, "com.example.app:id/welcome")
assert welcome_text.text == "Welcome!"
login_button = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Login")
login_button.click()
driver.quit()Element Locators
Accessibility ID (Preferred)
Fastest and most reliable — recommended by Appium:
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "SubmitButton")ID
Android resource ID or iOS accessibility identifier:
driver.find_element(AppiumBy.ID, "com.example.app:id/email_input")Class Name
Mobile element class names differ from web:
driver.find_element(AppiumBy.CLASS_NAME, "android.widget.Button")
driver.find_element(AppiumBy.CLASS_NAME, "XCUIElementTypeButton")XPath
Use only when other locators can’t find the element:
driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[@text='Submit']")
driver.find_element(AppiumBy.XPATH, "//XCUIElementTypeButton[@label='Submit']")Predicate String (iOS)
iOS-specific locator supporting complex conditions:
driver.find_element(AppiumBy.IOS_PREDICATE, "label BEGINSWITH 'Welcome' AND visible == true")Gestures
Tap
from appium.webdriver.common.touch_action import TouchAction
# Tap by coordinates
TouchAction(driver).tap(x=100, y=200).perform()
# Tap on element
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "button")
TouchAction(driver).tap(element).perform()Swipe
def swipe_down(driver):
size = driver.get_window_size()
start_x = size["width"] // 2
start_y = size["height"] * 0.8
end_y = size["height"] * 0.2
TouchAction(driver).press(x=start_x, y=start_y).move_to(
x=start_x, y=end_y
).release().perform()
def swipe_up(driver):
size = driver.get_window_size()
start_x = size["width"] // 2
start_y = size["height"] * 0.2
end_y = size["height"] * 0.8
TouchAction(driver).press(x=start_x, y=start_y).move_to(
x=start_x, y=end_y
).release().perform()Scroll
# Scroll to element
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true))'
'.scrollIntoView(new UiSelector().text("Settings"))')
# iOS scroll
driver.execute_script("mobile: scroll", {
"direction": "down",
"element": element_id,
})Complete Test Example
import pytest
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestLoginFlow:
@pytest.fixture
def driver(self):
caps = {
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "emulator-5554",
"appium:appPackage": "com.example.app",
"appium:appActivity": ".LoginActivity",
"appium:noReset": True,
}
driver = webdriver.Remote("http://localhost:4723", caps)
yield driver
driver.quit()
def test_successful_login(self, driver):
wait = WebDriverWait(driver, 10)
# Enter credentials
email_input = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "emailInput")
email_input.send_keys("user@example.com")
password_input = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "passwordInput")
password_input.send_keys("secret123")
# Submit
login_button = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "loginButton")
login_button.click()
# Wait for dashboard
dashboard = wait.until(
EC.presence_of_element_located(
(AppiumBy.ACCESSIBILITY_ID, "dashboardTitle")
)
)
assert dashboard.is_displayed()
def test_invalid_credentials(self, driver):
wait = WebDriverWait(driver, 10)
email_input = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "emailInput")
email_input.send_keys("wrong@example.com")
password_input = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "passwordInput")
password_input.send_keys("wrongpass")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "loginButton").click()
error = wait.until(
EC.presence_of_element_located(
(AppiumBy.ACCESSIBILITY_ID, "errorMessage")
)
)
assert "Invalid" in error.textCloud Device Testing
BrowserStack
desired_caps = {
"platformName": "Android",
"appium:platformVersion": "13.0",
"appium:deviceName": "Samsung Galaxy S23",
"appium:app": "bs://your-app-id",
"bstack:options": {
"userName": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
"projectName": "Mobile App Tests",
"buildName": "Release 2.4",
},
}
driver = webdriver.Remote(
"https://hub-cloud.browserstack.com/wd/hub",
desired_caps
)Sauce Labs
desired_caps = {
"platformName": "iOS",
"appium:platformVersion": "16",
"appium:deviceName": "iPhone 14 Pro Max",
"appium:app": "storage:filename=app.ipa",
"sauce:options": {
"username": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
},
}
driver = webdriver.Remote(
"https://ondemand.us-west-1.saucelabs.com/wd/hub",
desired_caps
)CI/CD Integration
# .github/workflows/mobile-tests.yml
name: Mobile Tests
on: [pull_request]
jobs:
android-tests:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Start Appium
run: |
npm install -g appium
appium driver install uiautomator2
appium &
- name: Boot emulator
run: |
echo "no" | avdmanager create avd -n test -k "system-images;android-33;google_apis;x86_64"
$ANDROID_HOME/emulator/emulator -avd test -no-window -no-audio &
- name: Wait for emulator
run: |
adb wait-for-device
adb shell input keyevent 82 # Unlock screen
- name: Run tests
run: |
pip install -r requirements.txt
pytest tests/mobile/ --junitxml=results.xml
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: results.xmlCommon Appium Mistakes
1. Slow Tests Due to Implicit Waits
Implicit waits applied globally slow down tests when elements are genuinely absent.
Fix: Use explicit waits with specific conditions, not implicit waits.
2. Hardcoding Device Capabilities
Tests that hardcode device names and OS versions fail when run on different devices.
Fix: Use environment variables or configuration files for device capabilities.
3. No Retry Logic
Flaky tests are common in mobile automation due to network delays, animations, and device state.
Fix: Implement retry mechanisms for flaky operations, especially gestures and waits.
4. Testing on Default Only
Running tests only on the default device misses device-specific bugs.
Fix: Test on multiple screen sizes, OS versions, and device types (phone vs tablet).
5. Ignoring App State
Tests fail when the app is in the wrong state — not logged in, permissions denied, first-run dialogs.
Fix: Reset app state between tests (noReset: False or clear app data).
6. Not Using Page Object Model
Mobile tests with duplicated locators are hard to maintain, just like web tests.
Fix: Apply the Page Object Model pattern to mobile tests as well.
7. Testing Only on Emulators
Emulators don’t represent real device behavior — different performance, network handling, and sensor behavior.
Fix: Test on real devices via a cloud provider in addition to emulator testing.
Practice Questions
1. What locator strategy does Appium recommend for element finding?
Accessibility ID — it’s the fastest and most reliable across both platforms.
2. How do you simulate a swipe gesture in Appium?
Using TouchAction: press at start coordinates, move to end coordinates, release.
3. What is the purpose of Appium Desired Capabilities?
To configure the test session — platform, device, app path, automation driver, and other settings.
4. How does Appium handle cross-platform testing?
Appium exposes the same WebDriver API for both iOS and Android. Platform-specific behavior is handled through capabilities and locator strategies.
5. What are the benefits of cloud device testing (BrowserStack/Sauce Labs)?
Access to hundreds of real devices, no hardware maintenance, parallel test execution, geographic distribution.
Challenge: Set up Appium for a sample Android app. Write tests for: login flow (success + failure), scrolling through a list, form submission, and verify elements on the profile screen. Run on both an emulator and a cloud device.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Appium Advanced Features | Parallel execution, cloud integration, detailed configuration |
| Detox Mobile Testing | React Native testing with Detox |
| Cloud Device Testing Guide | BrowserStack, Sauce Labs, and AWS Device Farm |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro