Skip to content
Mobile Testing with Appium: iOS and Android Automation Complete Guide

Mobile Testing with Appium: iOS and Android Automation Complete Guide

DodaTech Updated Jun 20, 2026 7 min read

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 webdriverio

Android 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/emulator

iOS Setup

# Required tools (macOS only)
- Xcode 14+
- Xcode Command Line Tools
- Carthage (for WebDriverAgent)

# Install dependencies
xcode-select --install
brew install carthage

First 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.text

Cloud 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.xml

Common 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

Can Appium test native, hybrid, and mobile web apps?
Yes. Appium handles all three. Use the appropriate automation driver (UiAutomator2, XCUITest, or Chromium/Safari for web).
What is the difference between Appium and Espresso/XCUITest?
Appium is cross-platform and language-agnostic. Espresso (Android) and XCUITest (iOS) are platform-specific frameworks with faster execution and deeper integration.
Do I need a Mac to test iOS apps?
Yes. iOS testing with Appium requires macOS because Xcode and the iOS simulator only run on macOS.
How do I handle system dialogs (permissions, notifications)?
Use Appium’s auto-accept capabilities or interact with system UI elements using XPath or accessibility IDs.
What is the best practice for waiting in Appium tests?
Use explicit waits with WebDriverWait and expected conditions. Avoid time.sleep().

What’s Next

TutorialWhat You’ll Learn
Appium Advanced FeaturesParallel execution, cloud integration, detailed configuration
Detox Mobile TestingReact Native testing with Detox
Cloud Device Testing GuideBrowserStack, 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