TL;DR
- Appium automates iOS and Android apps with WebDriver protocol — one framework, both platforms
- Setup: Install Appium server, platform SDKs (Android Studio/Xcode), and client library
- Find elements by accessibility id, xpath, or platform-specific locators
- Supports gestures (swipe, scroll, tap), real devices, and emulators/simulators
- Integrates with CI/CD via Appium server in Docker or cloud services (BrowserStack, Sauce Labs)
Best for: QA teams testing mobile apps across platforms, cross-platform automation Skip if: Testing only Android (use Espresso) or only iOS (use XCUITest) Reading time: 20 minutes
Your app works on iOS. Crashes on Android. Or vice versa. Manual testing on both platforms doubles your effort. Feature parity becomes a nightmare.
Appium solves cross-platform mobile testing. Write one test, run on iOS and Android. Same language, same framework, same CI pipeline.
This tutorial covers Appium from setup to CI/CD integration — everything you need to automate mobile apps.
What is Appium?
Appium is an open-source mobile automation framework. It automates native, hybrid, and mobile web apps on iOS and Android using the WebDriver protocol.
How Appium works:
- Your test code sends commands to Appium server
- Appium translates commands to platform-specific actions
- UIAutomator2 (Android) or XCUITest (iOS) executes the actions
- Results return through the same chain
Why Appium:
- Cross-platform — same tests for iOS and Android
- Language-agnostic — Java, Python, JavaScript, Ruby, C#
- No app modification — tests real production builds
- WebDriver standard — familiar API for Selenium users
- Open-source — free, active community, regular updates
Environment Setup
Prerequisites
For Android:
- Java JDK 11+
- Android Studio with SDK
ANDROID_HOMEenvironment variable- USB debugging enabled on device
For iOS (macOS only):
- Xcode with Command Line Tools
- iOS Simulator or real device
- Apple Developer account (for real devices)
Installing Appium
# Install Appium 2.x globally
npm install -g appium
# Verify installation
appium --version
# Install platform drivers
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS
# List installed drivers
appium driver list --installed
Appium Inspector
Install Appium Inspector for element inspection:
- Download from GitHub releases
- Or use web version at inspector.appiumpro.com
Desired Capabilities
Capabilities tell Appium what device and app to use.
Android Capabilities
from appium import webdriver
from appium.options.android import UiAutomator2Options
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "Pixel 6"
options.app = "/path/to/app.apk"
options.automation_name = "UiAutomator2"
# Optional but recommended
options.no_reset = True # Don't reset app state
options.full_reset = False
options.new_command_timeout = 300
driver = webdriver.Remote("http://localhost:4723", options=options)
iOS Capabilities
from appium import webdriver
from appium.options.ios import XCUITestOptions
options = XCUITestOptions()
options.platform_name = "iOS"
options.device_name = "iPhone 14"
options.platform_version = "16.0"
options.app = "/path/to/app.app"
options.automation_name = "XCUITest"
# For real devices
options.udid = "device-udid-here"
driver = webdriver.Remote("http://localhost:4723", options=options)
Testing Installed Apps
# Android - use app package and activity
options.app_package = "com.example.myapp"
options.app_activity = "com.example.myapp.MainActivity"
# iOS - use bundle ID
options.bundle_id = "com.example.myapp"
Finding Elements
Locator Strategies
from appium.webdriver.common.appiumby import AppiumBy
# Accessibility ID (recommended - works cross-platform)
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
# ID (Android resource-id)
element = driver.find_element(AppiumBy.ID, "com.example:id/login_button")
# XPath (slower, but flexible)
element = driver.find_element(AppiumBy.XPATH, "//android.widget.Button[@text='Login']")
# Class name
element = driver.find_element(AppiumBy.CLASS_NAME, "android.widget.EditText")
# Android UIAutomator
element = driver.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().text("Login")'
)
# iOS predicate string
element = driver.find_element(
AppiumBy.IOS_PREDICATE,
'label == "Login" AND type == "XCUIElementTypeButton"'
)
# iOS class chain
element = driver.find_element(
AppiumBy.IOS_CLASS_CHAIN,
'**/XCUIElementTypeButton[`label == "Login"`]'
)
Waiting for Elements
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
# Wait for element to be visible
element = wait.until(
EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "welcome_message"))
)
# Wait for element to be clickable
element = wait.until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "submit_button"))
)
# Wait for element to disappear
wait.until(
EC.invisibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "loading_spinner"))
)
Basic Interactions
Tap, Type, Clear
# Tap/Click
login_button = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
login_button.click()
# Type text
username_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "username")
username_field.send_keys("testuser")
# Clear field
username_field.clear()
# Get text
message = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "message")
print(message.text)
# Check if displayed/enabled
assert login_button.is_displayed()
assert login_button.is_enabled()
Handling Keyboard
# Hide keyboard
driver.hide_keyboard()
# Check if keyboard shown (Android)
is_keyboard_shown = driver.is_keyboard_shown()
Gestures
Swipe and Scroll
from appium.webdriver.common.touch_action import TouchAction
# Simple swipe (start_x, start_y, end_x, end_y, duration_ms)
driver.swipe(500, 1500, 500, 500, 800)
# Scroll to element (Android)
driver.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("Target Text"))'
)
# Scroll direction
def scroll_down(driver):
size = driver.get_window_size()
start_x = size['width'] // 2
start_y = size['height'] * 0.8
end_y = size['height'] * 0.2
driver.swipe(start_x, start_y, start_x, end_y, 500)
def scroll_up(driver):
size = driver.get_window_size()
start_x = size['width'] // 2
start_y = size['height'] * 0.2
end_y = size['height'] * 0.8
driver.swipe(start_x, start_y, start_x, end_y, 500)
W3C Actions (Recommended)
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput
# Tap at coordinates
actions = ActionChains(driver)
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
actions.w3c_actions.pointer_action.move_to_location(100, 200)
actions.w3c_actions.pointer_action.pointer_down()
actions.w3c_actions.pointer_action.pause(0.1)
actions.w3c_actions.pointer_action.release()
actions.perform()
# Long press
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "item")
actions = ActionChains(driver)
actions.click_and_hold(element).pause(2).release().perform()
Complete Test Example
import pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
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(autouse=True)
def setup(self):
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.app = "./app-debug.apk"
options.automation_name = "UiAutomator2"
options.no_reset = False
self.driver = webdriver.Remote("http://localhost:4723", options=options)
self.wait = WebDriverWait(self.driver, 15)
yield
self.driver.quit()
def test_successful_login(self):
# Enter username
username = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "username_input"))
)
username.send_keys("testuser@example.com")
# Enter password
password = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password_input")
password.send_keys("securepassword123")
# Hide keyboard
self.driver.hide_keyboard()
# Tap login button
login_btn = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
login_btn.click()
# Verify welcome message
welcome = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "welcome_message"))
)
assert "Welcome" in welcome.text
def test_invalid_credentials(self):
username = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "username_input"))
)
username.send_keys("wrong@example.com")
password = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password_input")
password.send_keys("wrongpassword")
self.driver.hide_keyboard()
login_btn = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
login_btn.click()
# Verify error message
error = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "error_message"))
)
assert "Invalid credentials" in error.text
Page Object Pattern
# pages/base_page.py
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 15)
def find(self, locator):
return self.wait.until(EC.presence_of_element_located(locator))
def click(self, locator):
self.find(locator).click()
def type_text(self, locator, text):
element = self.find(locator)
element.clear()
element.send_keys(text)
# pages/login_page.py
class LoginPage(BasePage):
USERNAME = (AppiumBy.ACCESSIBILITY_ID, "username_input")
PASSWORD = (AppiumBy.ACCESSIBILITY_ID, "password_input")
LOGIN_BTN = (AppiumBy.ACCESSIBILITY_ID, "login_button")
ERROR_MSG = (AppiumBy.ACCESSIBILITY_ID, "error_message")
def login(self, username, password):
self.type_text(self.USERNAME, username)
self.type_text(self.PASSWORD, password)
self.driver.hide_keyboard()
self.click(self.LOGIN_BTN)
def get_error_message(self):
return self.find(self.ERROR_MSG).text
# tests/test_login.py
class TestLogin:
def test_login_success(self, driver):
login_page = LoginPage(driver)
login_page.login("user@example.com", "password123")
# assertions...
Real Device Testing
Android Real Device
# Connect device via USB
adb devices
# Get device name
adb shell getprop ro.product.model
# Enable USB debugging in Developer Options
options.device_name = "Pixel 6"
options.udid = "emulator-5554" # or actual device serial
iOS Real Device
Requires provisioning profile and signing:
options.udid = "00008030-001234567890"
options.xcode_org_id = "TEAM_ID"
options.xcode_signing_id = "iPhone Developer"
Cloud Device Farms
BrowserStack:
from appium import webdriver
options = {
"platformName": "Android",
"appium:deviceName": "Samsung Galaxy S23",
"appium:platformVersion": "13.0",
"appium:app": "bs://app-id",
"bstack:options": {
"userName": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
"projectName": "My Project",
"buildName": "Build 1.0"
}
}
driver = webdriver.Remote(
"https://hub-cloud.browserstack.com/wd/hub",
options=options
)
CI/CD Integration
GitHub Actions
name: Mobile Tests
on: [push, pull_request]
jobs:
android-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install appium-python-client pytest
npm install -g appium
appium driver install uiautomator2
- name: Start Android emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
script: |
appium &
sleep 10
pytest tests/android/ --junitxml=results.xml
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: test-results
path: results.xml
Docker Setup
FROM appium/appium
# Install Python and dependencies
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install appium-python-client pytest
COPY tests/ /tests/
WORKDIR /tests
CMD ["pytest", "-v"]
Appium with AI Assistance
AI tools can help write and debug Appium tests.
What AI does well:
- Generate page objects from app screenshots
- Suggest locator strategies
- Debug element not found errors
- Convert tests between languages
What still needs humans:
- Understanding app business logic
- Choosing between flaky and stable locators
- Optimizing test execution time
- Handling platform-specific edge cases
FAQ
What is Appium?
Appium is an open-source mobile automation framework for testing iOS and Android applications. It uses the WebDriver protocol, allowing you to write tests in any language that has a WebDriver client (Java, Python, JavaScript, Ruby, C#). Appium can test native apps, hybrid apps, and mobile web apps without requiring app modification — you test real production builds.
Is Appium free?
Yes, Appium is completely free and open-source under the Apache 2.0 license. There are no enterprise editions, premium features, or usage limits. The entire framework, including all drivers (UiAutomator2, XCUITest), is available at no cost.
Appium vs Espresso/XCUITest — which is better?
Appium excels at cross-platform testing — write tests once, run on both iOS and Android. Espresso (Android) and XCUITest (iOS) are platform-specific but faster, more stable, and have deeper OS integration. Use Appium when you need cross-platform coverage with a single codebase. Use native tools when you need maximum speed and reliability for one platform.
Can Appium test both iOS and Android?
Yes, this is Appium’s primary advantage. The same test code can run on both platforms with minimal changes — usually just different locators for platform-specific elements. You write tests in your preferred language, and Appium translates commands to the appropriate platform automation (UiAutomator2 for Android, XCUITest for iOS).
Official Resources
See Also
- API Testing Guide - Complete API testing fundamentals
- Selenium Tutorial - Web automation basics
- Playwright Tutorial - Modern web testing framework
- CI/CD Testing Guide - Continuous integration strategies
