Browse the Knowledge Hub211 resources
Appium review: what it is, tutorial and alternatives
Appium is an open-source automation server for native, hybrid and mobile web apps. Test code in Java, Python, JavaScript, C# or Ruby sends W3C WebDriver commands to the Appium server, which hands them to a platform driver such as UiAutomator2 for Android or XCUITest for iOS. The current major is Appium 3; Appium 4 is in beta and not yet stable.
QAble runs this tool on client engagements. The assessment below is from that experience.
- last reviewed
- 13 October 2026
- version covered
- Appium 3 (server 3.8)
- licence
- Open source
At a glance
Appium at a glance
- Vendor
- OpenJS Foundation
- Category
- Mobile automation
- Licence
- Open source (Apache 2.0)
- Platforms
- iOS, Android, tvOS, Windows, macOS
- Languages
- Java, Python, JavaScript, C#, Ruby
- Integrates with
- BrowserStack, Sauce Labs, TestNG, WebdriverIO
- First released
- 2012
- Version reviewed
- Appium 3 (server 3.8)
Verdict
Our verdict on Appium
Strengths
- One W3C WebDriver API across Android, iOS, tvOS, Windows and macOS, so Selenium skills transfer
- Clients in Java, Python, JavaScript, C# and Ruby; the test language need not match the app’s
- Drives the build under test from outside, so no test code has to be compiled into the app
- Drivers and plugins install separately, so each platform upgrades on its own schedule
- Every major device cloud accepts Appium sessions, so the suite moves from emulator to real devices unchanged
- Free under Apache 2.0 and governed by the OpenJS Foundation
Weaknesses
- Setup is heavy: Android SDK, JDK, Xcode and WebDriverAgent signing before the first test
- Every command crosses several processes, so suites run slower than Espresso or XCUITest
- No automatic synchronisation with the app; explicit waits are hand-written
- Server, drivers, Xcode and clients version independently and break each other
- Many tutorials still show /wd/hub URLs and TouchAction, both gone in current versions
Best for
One QA team covering iOS and Android with a single language, a single client API and a single reporting pipeline, which is still Appium’s defining advantage. It is also the practical choice when the testers are not the app’s developers, when tests must drive the release build without test code compiled into it, and when the same suite has to run on local emulators, an in-house device lab and a commercial device cloud without rewriting.
Not for
Platform teams whose developers will write Espresso and XCUITest tests themselves. Those frameworks run beside the app, synchronise with it and give faster feedback, so for developer-owned UI tests they are the better fit. Appium is also a poor match for a large suite that must run on every commit in minutes: each command crosses several processes, and a big suite is better split into a fast smoke set and a slower nightly run.
Setup cost
The highest of any tool we run. Android needs the SDK, a JDK, ANDROID_HOME and an emulator or a device with USB debugging. iOS needs a Mac, Xcode and, on real devices, a signed WebDriverAgent. appium driver doctor turns most of that into a checklist, but a first green test on both platforms is still usually days rather than hours, and the CI machines need the same toolchain again.
Maintenance burden
High, and mostly outside the tests themselves. The Appium server, each driver, Xcode, the Android SDK and the client library all move on their own schedules, and an Xcode or iOS release can break WebDriverAgent until the driver catches up. Pin every version, upgrade one layer at a time, and budget for locator abstraction, because the same screen rarely has the same element tree on both platforms.
Honest limitations
Slower than the in-process frameworks, because every command is an HTTP round trip through the server and the driver to the device. No automatic synchronisation with the app, so waits are yours to write. Real iOS devices need signing and, for automatic provisioning, a paid Apple Developer account. Hardware such as camera input and real biometrics can only be simulated where the driver offers a mobile: command for it.
How it works
What is Appium and how does it work?
A server between your tests and the device
Appium is a Node.js HTTP server that implements the W3C WebDriver protocol. Your test is a client: it opens a session by posting capabilities, then sends commands such as find element or click as HTTP requests. The server routes each session to a driver, the component that knows one platform. Since Appium 2 the server ships with no drivers at all; you install them with appium driver install uiautomator2 or xcuitest, and they live in APPIUM_HOME, by default ~/.appium. Appium 3 keeps that model and raises the floor to Node.js 20.19 and npm 10. Because the client only speaks HTTP, the server, the devices and the tests can sit on different machines.
Each driver is its own stack
The UiAutomator2 driver installs a helper server on the Android device and combines Google’s UiAutomator2 framework with adb commands. The XCUITest driver builds WebDriverAgent, an XCTest runner app written in Objective-C, installs it on the simulator or phone and proxies most commands straight to it. Appium’s own docs list the layers involved in one iOS command: your code, the Appium client, the Selenium client, the network, the server, the driver, WebDriverAgent, Xcode, XCTest, iOS and macOS. That depth explains both the reach and the fragility. A failure can start in any layer, and the server log, which records every request a session makes, is where diagnosis begins.
Capabilities decide the session
Everything about a session is set by capabilities at creation time: platformName, appium:automationName to pick the driver, appium:app or a package and activity to choose the app, appium:udid to pick the device, and reset behaviour through appium:noReset and appium:fullReset. Appium-specific keys carry the appium: vendor prefix; the Python client adds it for you. Most first-day failures are capability failures, such as a missing driver, a wrong package name or two sessions fighting over one device, and they surface as a session that never starts rather than a test that fails.
Synchronisation is the client’s job
Appium does not know when your app is idle. Espresso and XCUITest wait for the app to settle because they run beside it; Appium sees only what the driver reports when asked. Waiting is therefore written in the client, with explicit waits such as Selenium’s WebDriverWait polling for a condition, and locator choice matters for speed as well as stability. The driver documentation ranks accessibility id and resource id among the fastest strategies and XPath among the slowest, because XPath is evaluated against a page source document the driver has to generate from the whole screen hierarchy first.
Strengths
What it does well
One protocol for every platform
The same WebDriver commands drive Android through UiAutomator2 or Espresso, iOS and tvOS through XCUITest, macOS through Mac2 and Windows apps through the Windows driver. appium setup installs a preset of mobile, desktop or browser drivers in one step. For a team testing an app on both phone platforms, one client API and one framework around it is the reason to choose Appium.
Platform commands through mobile: methods
Where WebDriver has no command for something, drivers expose execute methods: mobile: scroll and mobile: startActivity on Android, mobile: terminateApp and mobile: clearApp on both platforms, simulator biometrics on iOS. Appium 3 moved many old endpoints to these methods, so they are now the supported route for app lifecycle, gestures and device settings rather than an escape hatch.
Hybrid apps and web views
Appium adds context commands to WebDriver. A session starts in NATIVE_APP; listing contexts returns any active web views, and switching into one lets you use ordinary web locators inside the embedded page, then switch back. That matters for apps that mix native screens with web checkout or help pages, which native-only frameworks handle awkwardly.
Inspector and plugins
Appium Inspector shows a live screenshot beside the element tree, with each element’s attributes and suggested locators, and from Appium 3 it can be hosted by the server itself with appium plugin install inspector. Plugins change server behaviour without forking it; the images plugin, for example, adds finding elements by image comparison.
Masking secrets in server logs
Appium 3 lets a client mark a request as sensitive with the X-Appium-Is-Sensitive header, so typed passwords and tokens are masked in the server log. Because the server log is the main debugging artefact, and CI systems keep it, that closes a real leak in shared pipelines.
Portable across labs and clouds
A session is just a URL and a set of capabilities, so moving a suite from a local emulator to an in-house Mac mini lab or to providers such as BrowserStack, Sauce Labs or LambdaTest means changing the server URL and adding the provider’s capabilities. That portability is how most teams get real-device coverage without buying hundreds of phones.
Pricing
Appium pricing and licensing
Appium, its official drivers and its client libraries are free and open source under the Apache 2.0 licence, with no paid edition and no feature held back. The project is part of the OpenJS Foundation and funded through sponsorship. The real costs sit around it: Mac hardware for any iOS work, emulators or physical devices for Android, a device cloud for breadth, CI minutes, and the engineering time to keep a deep version stack working.
- Licence: Apache 2.0 for the server, the official drivers such as UiAutomator2 and XCUITest, and the official clients.
- No tiers: parallel sessions, plugins, the Inspector and every driver are free, with no account or licence key.
- Infrastructure: iOS automation needs macOS with Xcode, so a Mac in CI or macOS runner minutes is unavoidable for iOS coverage.
- Device clouds: providers such as BrowserStack, Sauce Labs and LambdaTest sell real-device access, priced by them, usually by parallel sessions.
- Hidden cost: maintenance. Driver, Xcode and Android SDK upgrades, WebDriverAgent signing and cross-platform locator layers take ongoing engineering time.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
Appium tutorial: getting started
Setup, step by step
- Install Node.js 20.19, 22.12 or 24 and later, plus npm 10, as Appium 3 requires.
- Install the Android SDK platform tools and a JDK, set ANDROID_HOME and JAVA_HOME, and start an emulator.
- Install Appium globally, then the UiAutomator2 driver, and run the driver’s doctor until it reports no required fixes.
- Start the server with appium and note the URL it prints, http://127.0.0.1:4723/ by default.
- Install the Python client and pytest, save the test below and run pytest in a second terminal.
Install
npm install -g appium
appium driver install uiautomator2
appium driver doctor uiautomator2
pip install Appium-Python-Client pytest
# start the server, then run the tests from another terminal
appiumA first example
# test_settings.py: opens Android Settings and taps Apps
import pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
# Appium 2 and 3 serve from the root path: no /wd/hub suffix
APPIUM_URL = 'http://127.0.0.1:4723'
CAPS = {
'platformName': 'Android',
'automationName': 'UiAutomator2', # selects the installed driver
'appPackage': 'com.android.settings',
'appActivity': '.Settings',
}
@pytest.fixture
def driver():
# The Python client adds the appium: prefix to vendor capabilities
drv = webdriver.Remote(APPIUM_URL, options=UiAutomator2Options().load_capabilities(CAPS))
yield drv
drv.quit()
def test_open_apps(driver):
apps = (AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Apps")')
# Appium does not wait for the app to settle: wait explicitly
WebDriverWait(driver, 15).until(EC.element_to_be_clickable(apps)).click()
assert driver.find_element(*apps).is_displayed()What trips people up first
The session that never starts. The usual causes are a driver that is not installed in the APPIUM_HOME the server is reading, an emulator that adb does not list, environment variables set in one terminal but not the one running the server, or a URL ending in /wd/hub copied from an Appium 1 tutorial. Run appium driver list --installed and adb devices, read the server log from the moment the session request arrives, and use appium driver doctor uiautomator2 or xcuitest to check prerequisites. On iOS the equivalent first wall is WebDriverAgent signing on a real device, so start on a simulator.
In practice
Using Appium in practice
Screen objects that work on both platforms
The structure that survives is a screen object per app screen, holding one locator per platform and exposing actions such as sign_in rather than taps. Ask the app developers for accessibility identifiers early: on Android the accessibility id strategy matches the content description, and on iOS it matches the element’s name, usually its accessibilityIdentifier, so a shared identifier gives one locator for both platforms. Fall back to resource ids, UiSelector or iOS predicate strings, and keep XPath for the rare case nothing else can reach. Keep capabilities in configuration files per platform and environment, never inside tests, so the same suite can point at an emulator, a lab or a cloud.
# screens/login_screen.py
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
class LoginScreen:
# Shared accessibility ids first; per-platform fallbacks only where needed
LOCATORS = {
'email': {'android': (AppiumBy.ACCESSIBILITY_ID, 'login-email'),
'ios': (AppiumBy.ACCESSIBILITY_ID, 'login-email')},
'password': {'android': (AppiumBy.ID, 'com.example.app:id/password'),
'ios': (AppiumBy.IOS_PREDICATE, "type == 'XCUIElementTypeSecureTextField'")},
'submit': {'android': (AppiumBy.ACCESSIBILITY_ID, 'login-submit'),
'ios': (AppiumBy.ACCESSIBILITY_ID, 'login-submit')},
}
def __init__(self, driver):
self.driver = driver
self.platform = driver.capabilities['platformName'].lower()
def _el(self, name, timeout=15):
locator = self.LOCATORS[name][self.platform]
return WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator))
def sign_in(self, email, password):
self._el('email').send_keys(email)
self._el('password').send_keys(password)
self._el('submit').click()Gestures after TouchAction
Older suites built swipes and scrolls with TouchAction and MultiAction. Those classes were removed from the Python client in version 4 and from the Java client in version 11, and Appium 3 removed the server endpoints behind them. There are two supported routes. W3C Actions, available in every Selenium-based client, describe pointer moves precisely and work on any driver. Driver execute methods are shorter and usually steadier: on Android, mobile: scroll keeps swiping a scrollable container until an element found by accessibility id, class name or UiSelector is visible, and the XCUITest driver offers mobile: scroll and mobile: swipe for iOS. Wrap whichever you choose in a helper so tests never contain coordinates.
# helpers/gestures.py
def scroll_to_text_android(driver, text):
# UiAutomator2 execute method: scrolls the first scrollable view
# until a matching element is visible, or fails after maxSwipes
driver.execute_script('mobile: scroll', {
'strategy': '-android uiautomator',
'selector': f'new UiSelector().text("{text}")',
'maxSwipes': 10,
})
def terminate_app(driver, app_id):
# Replaces the old close_app(), whose endpoint Appium 3 removed
driver.execute_script('mobile: terminateApp', {'appId': app_id})Android runs in GitHub Actions
GitHub’s Ubuntu runners support hardware-accelerated Android emulators once KVM permissions are enabled, and the ReactiveCircus android-emulator-runner action installs the system image, boots the emulator, waits for it and runs your command. Start the Appium server in the background before the emulator step and write its log to a file, because that log is what explains a failed session after the runner is gone. Pin the Appium and driver versions in the workflow so a new driver release cannot change behaviour between two runs of the same commit. iOS needs a macOS runner and a simulator destination instead.
# .github/workflows/android-e2e.yml
name: android-e2e
on: [push, pull_request]
jobs:
appium:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Install Appium and pinned driver
run: |
npm install -g [email protected]
appium driver install [email protected]
pip install -r requirements.txt
- name: Start Appium in the background
run: appium --log appium.log &
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
script: pytest -q --junitxml=results.xml
- uses: actions/upload-artifact@v7
if: failure()
with:
name: appium-logs
path: |
appium.log
results.xmlParallel sessions and device farms
One Appium server can run several sessions at once, and the drivers document the capabilities that keep them apart. On Android, give each session its own appium:udid and appium:systemPort, plus a unique chromedriverPort for web views and mjpegServerPort for video. On iOS, give each session its own udid or device name and version, appium:wdaLocalPort and appium:derivedDataPath. With pytest-xdist, derive those values from the worker id so each worker owns one device. If you are not running in parallel, the drivers recommend starting the server with --session-override so a stale session cannot block the next one. Beyond a handful of local devices, a device cloud is usually cheaper than a lab you maintain, and the suite only needs a new server URL.
iOS signing, version pinning and upgrades
For iOS, start on simulators; real devices need WebDriverAgent signed against an Apple Account. Free accounts can sign it manually through the project that appium driver run xcuitest open-wda opens, while automatic provisioning needs a paid Apple Developer account, and a prebuilt or preinstalled WebDriverAgent avoids building on every run. The XCUITest driver aims to fully support the latest two major Xcode and iOS versions, so check its compatibility tables before upgrading Xcode on CI. For a project, declare appium and its drivers in package.json, which Appium reads when appium is a dependency, so every machine resolves the same versions. Appium 3 itself is a small upgrade from 2: Node.js 20.19, scoped insecure features and the removal of deprecated endpoints.
Pitfalls
Common pitfalls
Connecting to http://localhost:4723/wd/hub
Why it happens: The /wd/hub base path was the Appium 1 default. Since Appium 2 the server listens at the root path, so old URLs return an unknown command error before any session exists.
The fix: Use http://127.0.0.1:4723 with no suffix, or start the server with --base-path=/wd/hub while you migrate an old framework.
TouchAction and MultiAction no longer exist
Why it happens: Appium 3 removed the touch endpoints, the Python client removed the classes in version 4 and the Java client in version 11. Code copied from older tutorials fails to import or compile.
The fix: Rewrite gestures with W3C Actions or with driver execute methods such as mobile: scroll, mobile: swipe and mobile: longClickGesture, behind one helper module.
Server refuses --allow-insecure=adb_shell after upgrading
Why it happens: Appium 3 made the scope prefix mandatory for insecure features, and throws an error when a feature name has no driver scope.
The fix: Write uiautomator2:adb_shell to enable it for one driver, or *:adb_shell to keep the Appium 2 behaviour for every driver that supports it.
XPath as the default locator
Why it happens: XPath is evaluated against a page source the driver must generate from the whole hierarchy, so it is among the slowest strategies, and absolute paths break whenever layout changes.
The fix: Agree accessibility identifiers with the developers, then use accessibility id, resource id, UiSelector or iOS predicate strings, keeping XPath for genuine edge cases.
Parallel sessions that fail at random
Why it happens: Two sessions on one machine claimed the same device or the same helper port, such as the UiAutomator2 system port or WebDriverAgent’s port 8100.
The fix: Give every session a unique udid plus systemPort on Android or wdaLocalPort and derivedDataPath on iOS, assigned from the test worker’s index.
A green suite turns red after an Xcode or driver update
Why it happens: The server, drivers, Xcode and Android tooling version independently. An unpinned appium driver install takes the newest driver, which may expect a newer Xcode or behave differently.
The fix: Pin the server and every driver, check the XCUITest driver’s compatibility tables before moving Xcode, and upgrade one layer at a time on a branch.
State leaking from one test into the next
Why it happens: Without a reset strategy the app keeps its login, data and permissions between sessions, so tests pass or fail depending on order.
The fix: Choose deliberately: appium:noReset for speed with explicit cleanup, mobile: clearApp between tests, or appium:fullReset where a clean install is worth the time.
Compared
Appium alternatives compared
| Tool | Test languages | Platforms | How it drives the app | Synchronisation | Licence |
|---|---|---|---|---|---|
| Appium | Java, Python, JS, C#, Ruby | Android, iOS, tvOS, Windows, macOS | External WebDriver server and platform drivers | Explicit waits in client code | Apache 2.0 |
| Espresso | Kotlin, Java | Android only | Instrumentation inside the app process | Automatic, waits for idle UI thread | Apache 2.0 |
| XCUITest | Swift, Objective-C | Apple platforms only | Separate test runner through accessibility | Waits for app idle, plus explicit waits | Proprietary, free with Xcode |
| Maestro | YAML flows, optional JavaScript | Android, iOS simulators, web | Black box through the accessibility layer | Built-in settle waits and polling assertions | Apache 2.0 |
| Detox | JavaScript, TypeScript | React Native on iOS, Android | Grey box, instrumented app build | Monitors the app’s async work | MIT |
FAQ
Frequently asked questions
What is Appium?
Appium is an open-source automation server for native, hybrid and mobile web apps. Test code in Java, Python, JavaScript, C# or Ruby sends W3C WebDriver commands to the Appium server, which hands them to a platform driver such as UiAutomator2 for Android or XCUITest for iOS. The current major is Appium 3; Appium 4 is in beta and not yet stable.
What are the best Appium alternatives?
The alternatives most often weighed against Appium are Espresso, XCUITest, Maestro and Detox. The comparison on this page sets them side by side on test languages, platforms, how it drives the app, synchronisation and licence, and the verdict above says where Appium is the wrong choice and which of them to pick instead.
Is Appium free?
Yes. The Appium server, the official drivers and the official client libraries are open source under the Apache 2.0 licence, with no paid tier. The costs are around it: Macs for iOS automation, emulators or devices, CI time, any commercial device cloud you rent for real-device coverage, and the engineering time to keep the server, drivers and platform tools in step.
Should I use Appium or Espresso and XCUITest?
Use Espresso and XCUITest when the app’s developers own the UI tests, write Kotlin and Swift, and want fast, well-synchronised tests inside each codebase. Use Appium when one QA team covers both platforms in one language, tests the release build from outside, or needs to run the same suite on clouds and labs. Many teams combine both: native frameworks for developer feedback, Appium for cross-platform journeys.
Do I need a Mac to test iOS apps with Appium?
In practice, yes. The XCUITest driver builds and runs WebDriverAgent with Xcode, which needs macOS, and its documentation describes Windows and Linux hosts as limited support. Your test code can run anywhere, because it only sends HTTP to the Appium server, but the server driving iOS sessions should run on a Mac, a macOS CI runner or a device cloud.
What changed in Appium 3, and should I wait for Appium 4?
Appium 3, the current stable major, raised the minimum to Node.js 20.19 and npm 10, removed deprecated endpoints in favour of W3C commands and mobile: execute methods, required a driver scope on insecure feature flags, and moved session discovery to GET /appium/sessions behind a feature flag. Appium 4 has been in beta since 19 September 2026, with removals such as legacy JSONWP support and Selenium Grid 3 nodeconfig. Build on Appium 3 now and test the beta on a branch.
Why does my TouchAction code no longer work?
TouchAction and MultiAction were deprecated for years and are now gone: Appium 3 removed the touch endpoints, the Python client dropped the classes in version 4 and the Java client in version 11. Replace them with W3C Actions, which every Selenium-based client supports, or with the drivers’ gesture methods such as mobile: scroll, mobile: swipeGesture or mobile: clickGesture.
Can Appium test Flutter and React Native apps?
React Native apps render native views, so UiAutomator2 and XCUITest drive them directly; set accessibility labels or test ids so elements have stable identifiers. Flutter draws its own widgets, so the standard drivers see only what Flutter exposes through its semantics tree. Appium’s ecosystem list includes a Flutter driver and a community Flutter Integration driver for deeper access.
Is Appium still worth learning in 2026?
Yes, for anyone testing mobile apps across both platforms. It remains the common protocol that device clouds, WebdriverIO and many in-house labs build on, and the skills overlap heavily with Selenium. Learn the layers underneath as well, UiAutomator2, XCUITest and WebDriverAgent, because diagnosing Appium failures means knowing which layer failed.
Alternatives
Appium alternatives worth weighing
Espresso
Android native UI testing framework running in process, with automatic synchronisation with the app.
XCUITest
Apple’s UI testing API, built on XCTest and the XCUIAutomation framework, for iOS, iPadOS, macOS, tvOS and watchOS apps, run from Xcode or xcodebuild.
Detox
Grey-box end-to-end framework for React Native, synchronising with the app rather than polling.
Maestro
Declarative mobile UI testing with flows defined in YAML and built-in tolerance for flakiness.
Resources
Where to go next
Keep going
Prepare, or get it done
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.