Appium
Overview
Appium is a server that exposes the W3C WebDriver protocol for mobile apps. Test scripts (WebdriverIO, Python, Java, Ruby, .NET) send commands to the server, and a platform driver carries them out: UiAutomator2 for Android and XCUITest for iOS (iOS needs macOS and Xcode). Appium 3 is the current major version (3.8 at the time of writing); it needs Node.js 20.19+ or 22.12+ and npm 10+, speaks only W3C commands, and ships no drivers, so you install them separately.
Instructions
Initial assessment
- Platform: Android, iOS or both? iOS requires a Mac.
- App type: native, hybrid (WebView) or mobile web.
- Client language: JavaScript (WebdriverIO), Python, Java, Ruby.
- Emulators/simulators or real devices (real iOS devices need signing for WebDriverAgent).
Setup
npm install -g appium # the only supported install route
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS (macOS only)
appium driver list --installed
# Environment checks. The standalone appium-doctor package is deprecated;
# use the doctor built into each driver:
appium driver doctor uiautomator2
appium driver doctor xcuitest
appium # listens on http://127.0.0.1:4723
Android needs ANDROID_HOME pointing at the SDK and adb on the PATH. Appium 3 requires driver-scoped insecure features, e.g. appium --allow-insecure=uiautomator2:adb_shell (*:adb_shell applies to all drivers).
Capabilities
Every non-standard key carries the appium: prefix. deviceName does not pick a device on Android: select one with appium:udid (real device), appium:avd (emulator to boot) or appium:platformVersion (auto-detect).
// capabilities.js
export const androidCaps = {
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:avd': 'Pixel_7_API_34',
'appium:app': '/Users/dana/projects/shop-app/app/build/outputs/apk/debug/app-debug.apk',
'appium:autoGrantPermissions': true,
'appium:newCommandTimeout': 240,
};
export const iosCaps = {
platformName: 'iOS',
'appium:automationName': 'XCUITest',
'appium:deviceName': 'iPhone 15',
'appium:platformVersion': '17.5',
'appium:app': '/Users/dana/projects/shop-app/build/ShopApp.app',
'appium:autoAcceptAlerts': true,
};
WebdriverIO config
npm install --save-dev webdriverio @wdio/cli @wdio/appium-service @wdio/mocha-framework
// wdio.conf.js
export const config = {
runner: 'local',
specs: ['./tests/**/*.spec.js'],
capabilities: [{
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:avd': 'Pixel_7_API_34',
'appium:app': './app/build/outputs/apk/debug/app-debug.apk',
}],
framework: 'mocha',
mochaOpts: { timeout: 60000 },
services: ['appium'], // starts the globally installed Appium server on port 4723
};
Run with npx wdio run wdio.conf.js. In WebdriverIO, $('~login-button') finds by accessibility id (content-desc on Android, accessibility identifier on iOS).
Mobile gestures
Prefer the driver's mobile: commands over legacy TouchAction. The UiAutomator2 set includes clickGesture, longClickGesture, doubleClickGesture, dragGesture, flingGesture, swipeGesture, scrollGesture, pinchOpenGesture, pinchCloseGesture. XCUITest has mobile: swipe, mobile: scroll, mobile: touchAndHold, mobile: pinch and others (see the driver docs).
Examples
Example 1: Login test in WebdriverIO
Request: "Write an Appium test that logs into my Android shopping app and checks the welcome text."
// tests/login.spec.js
describe('Login screen', () => {
it('logs in with valid credentials', async () => {
await $('~email-input').setValue('dana.ortiz@shopmail.dev');
await $('~password-input').setValue(process.env.SHOP_TEST_PASSWORD);
await $('~login-button').click();
const welcome = await $('~welcome-message');
await welcome.waitForDisplayed({ timeout: 10000 });
await expect(welcome).toHaveText('Welcome back, Dana');
});
});
Run appium in one terminal (or rely on the appium service) and npx wdio run wdio.conf.js. The emulator boots, the APK installs, and Mocha reports 1 passing.
Example 2: Python test with scrolling
Request: "Python version: add an item, scroll the list, and assert it is listed."
# tests/test_items.py
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
@pytest.fixture
def driver():
options = UiAutomator2Options()
options.platform_name = "Android"
options.avd = "Pixel_7_API_34"
options.app = "./app-debug.apk"
d = webdriver.Remote("http://127.0.0.1:4723", options=options)
yield d
d.quit()
def test_add_item(driver):
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "add-item"))).click()
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "item-name").send_keys("Oat milk 1L")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "save-button").click()
driver.execute_script("mobile: scrollGesture", {
"left": 100, "top": 500, "width": 200, "height": 600,
"direction": "down", "percent": 1.0,
})
assert wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "item-Oat milk 1L"))
).is_displayed()
Install with pip install Appium-Python-Client pytest (client 6.x, Python 3.10+). UiAutomator2Options replaced the removed desired_capabilities argument.
Guidelines
- Pin the Appium and driver versions in CI; upgrade drivers with
appium driver update uiautomator2, not by reinstalling Appium.
- Missing
appium: prefix on a custom capability is the most common "invalid capabilities" error.
- Give elements accessibility ids; XPath is slow and breaks on layout changes. Android
-android uiautomator locators are on Google's deprecation path.
- Use explicit waits (
waitForDisplayed, WebDriverWait) instead of sleeps.
- Appium 3 removed many old REST endpoints (app launch, lock, clipboard); use the driver's
mobile: execute commands.
- Real iOS devices need a development team and WebDriverAgent signing; simulators do not.
- Keep passwords in environment variables; never commit them or the app binaries.
- Only expose the server beyond localhost behind authentication; insecure features give shell access to the device.
- For React Native apps with synchronised gray-box testing, Detox is a lighter choice.