Appium Mobile Testing: A to Z Guide (2026)
The 2026 complete Appium tutorial for mobile testing. Step-by-step setup, locators, touch actions, Page Object Model, Appium 2.0, cloud testing with BrowserStack, and CI/CD for Android and iOS.

What you'll master: By the end of this Appium tutorial you will have a working Appium 2.0 setup for both Android and iOS, locators and touch actions mastered, the Page Object Model pattern adapted for mobile, parallel execution on real device clouds, and a CI pipeline that ships mobile tests on every PR.
1. Why Appium in 2026
Mobile is the dominant platform for software in 2026. Every product has a mobile app or a responsive web view that behaves like one. And the QA engineer who can test that experience — across devices, OS versions, and form factors — is in high demand.
Appium is the W3C WebDriver-based mobile automation framework. It is the de facto open-source standard for native, hybrid, and mobile-web testing, with bindings in Java, Python, JavaScript, C#, and Ruby.
The reasons Appium dominates in 2026:
- W3C standard — the same protocol as Selenium, so the patterns transfer
- Multi-language — one codebase, many language bindings
- Cross-platform — one API for Android and iOS
- No app modification required — test the app as-is, no SDK injection
- Cloud-friendly — works on BrowserStack, Sauce Labs, LambdaTest
The trade-offs: slower than Espresso/XCTest (because of the WebDriver bridge), no native gesture DSL, and a heavier setup than some cloud-only alternatives.
🚀 Skip the setup pain. SoftwareTestPilot Pro members get ready-to-run Appium starter kits for Android and iOS, with parallel cloud execution configured. Start free trial →
2. Appium Architecture
┌─────────────────────────────────────────────┐
│ Your test code (Java/Python/JS) │
└──────────────────┬──────────────────────────┘
│ Appium Client Library
▼
┌─────────────────────────────────────────────┐
│ Appium Server (Node.js) │
│ - Listens on port 4723 │
│ - Speaks W3C WebDriver │
│ - Routes to platform drivers │
└──────────┬───────────────────┬───────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ UiAutomator2 │ │ XCUITest │
│ (Android driver) │ │ (iOS driver) │
└──────────────────┘ └──────────────────┘Key components
- Appium Server — Node.js process that orchestrates the WebDriver session
- Driver plugins — UiAutomator2 (Android), XCUITest (iOS), Espresso (Android), UIAutomation (deprecated)
- Appium Client — language-specific library (
java-client,python-client,webdriverio) - Appium Inspector — GUI tool to inspect app elements and generate locators
The W3C WebDriver connection
Appium speaks the same W3C WebDriver protocol as Selenium. This means:
- Same concepts: capabilities, sessions, locators
- Same patterns: explicit waits, POM
- Same cloud integration: BrowserStack, Sauce Labs
For Selenium WebDriver basics, see our Selenium WebDriver Guide.
3. Install Appium Step by Step
Prerequisites
- Node.js 18 LTS or 20 LTS
- Java 17+ (for Android)
- Xcode 15+ (for iOS, macOS only)
- Android Studio (for Android SDK and emulator)
- Homebrew (macOS) or apt (Linux)
Step 1 — Install the Appium server
npm install -g appium
appium --version # should show 2.xStep 2 — Install the drivers
appium driver install uiautomator2
appium driver install xcuitest
appium driver list --installedStep 3 — Install the client library
Choose your language and install the matching client.
Java (Maven):
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>9.4.0</version>
</dependency>Python:
pip install Appium-Python-ClientJavaScript:
npm install appium @appium/webdriverioStep 4 — Set up an Android emulator
Use Android Studio's AVD Manager. Create a Pixel 7 with API 34. Boot it once to verify.
Step 5 — Set up an iOS simulator
On macOS only:
xcrun simctl list devices available
xcrun simctl boot "iPhone 15"Step 6 — Install Appium Inspector
npm install -g appium-inspector
appium-inspectorThe Inspector is a desktop app that connects to Appium and lets you visually inspect your app's UI to generate locators.
Step 7 — Start the Appium server
appiumYou should see: [Appium] Welcome to Appium v2.x. The server is now listening on port 4723.
4. Your First Appium Test on Android
We'll use the ApiDemos app that ships with Android Studio.
Java
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.net.URL;
import java.time.Duration;
public class FirstAndroidTest {
public static void main(String[] args) throws Exception {
UiAutomator2Options options = new UiAutomator2Options()
.setDeviceName("Pixel_7_API_34")
.setApp("/path/to/ApiDemos.apk")
.setAutomationName("UiAutomator2");
AndroidDriver driver = new AndroidDriver(
new URL("http://127.0.0.1:4723"), options
);
try {
// Tap "App" → "Alert Dialogs" → "OK"
driver.findElement(By.xpath("//*[@text='App']")).click();
driver.findElement(By.xpath("//*[@text='Alert Dialogs']")).click();
driver.findElement(By.xpath("//*[@text='OK']")).click();
// Verify the dialog appeared
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement okButton = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[@text='OK']")
)
);
System.out.println("Test passed: " + okButton.getText());
} finally {
driver.quit();
}
}
}Python
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
import time
options = UiAutomator2Options()
options.device_name = "Pixel_7_API_34"
options.app = "/path/to/ApiDemos.apk"
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
try:
driver.find_element(AppiumBy.XPATH, "//*[@text='App']").click()
driver.find_element(AppiumBy.XPATH, "//*[@text='Alert Dialogs']").click()
driver.find_element(AppiumBy.XPATH, "//*[@text='OK']").click()
ok_button = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((AppiumBy.XPATH, "//*[@text='OK']"))
)
print(f"Test passed: {ok_button.text}")
finally:
driver.quit()Run the test:
mvn test
pytest test_first_android.pyFor Selenium-style wait patterns adapted to mobile, see our Selenium wait strategies guide.
5. Your First Appium Test on iOS
For iOS you need a macOS host with Xcode installed. We'll use the XCTest sample app.
from appium import webdriver
from appium.options.ios import XCUITestOptions
from appium.webdriver.common.appiumby import AppiumBy
options = XCUITestOptions()
options.device_name = "iPhone 15"
options.platform_version = "17.0"
options.app = "/path/to/TestApp.app"
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
try:
# Compute 2 + 3 = 5
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "IntegerA").send_keys("2")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "IntegerB").send_keys("3")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ComputeSumButton").click()
result = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Answer").text
assert result == "5", f"Expected 5, got {result}"
print(f"Test passed: {result}")
finally:
driver.quit()⚠️ iOS apps must be built for testing. If you don't have a
.appfile, use the freely available Appium iOS Test App from GitHub.
6. Mobile Locators Deep Dive
Mobile apps don't have HTML. They have native UI elements. The locator strategy is different but the principles are the same.
Locator priority (mobile)
| Priority | Locator | When to use |
|---|---|---|
| 1 | accessibility id | Always preferred — explicitly set for testing |
| 2 | predicate string (iOS) / app:android=new UiSelector().resourceId(...) (Android) | When IDs are stable |
| 3 | xpath | Last resort; brittle |
| 4 | -ios class chain / -android class chain | For complex hierarchies |
| 5 | id (resource-id on Android) | Stable across builds |
Accessibility IDs (the right default)
Set accessibility IDs in your app's source code:
// iOS (Swift)
button.accessibilityIdentifier = "submit-button"// Android (Kotlin)
submitButton.contentDescription = "submit-button"In tests:
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "submit-button").click()Android UiAutomator2 selectors
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().resourceId("com.example:id/submit")').click()
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().textContains("Submit")').click()
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().className("android.widget.Button").index(0)').click()iOS predicate strings
driver.find_element(AppiumBy.IOS_PREDICATE, 'label == "Submit"').click()
driver.find_element(AppiumBy.IOS_PREDICATE, 'label CONTAINS "Submit"').click()
driver.find_element(AppiumBy.IOS_PREDICATE, 'type == "XCUIElementTypeButton" AND label == "Submit"').click()iOS class chains
driver.find_element(AppiumBy.IOS_CLASS_CHAIN, '**/XCUIElementTypeButton[`label == "Submit"`]').click()For more locator patterns, see our Selenium locator guide.
7. Touch Actions and Gestures
Mobile is gesture-driven. Appium provides the W3C Actions API for touch, plus a deprecated TouchAction API still useful for older code.
Tap
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "submit").click()
driver.tap([(540, 1200)]) # tap at (x=540, y=1200)Long press
from selenium.webdriver.common.action_chains import ActionChains
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "menu-item")
actions = ActionChains(driver)
actions.click_and_hold(element).pause(1).release().perform()Swipe (using W3C Actions)
from appium.webdriver.extensions.action_helpers import ActionHelpers
def swipe_up(driver):
driver.swipe(540, 1500, 540, 500, 500)
def swipe_left(driver):
driver.swipe(900, 1200, 100, 1200, 500)Scroll (the most common mobile gesture)
def scroll_to(driver, text):
driver.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
f'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("{text}"))'
)
def scroll_to_ios(driver, predicate):
driver.execute_script('mobile:scroll', {'direction': 'down', 'predicateString': predicate})Pinch and zoom (advanced)
actions = ActionChains(driver)
actions.add_action(input_) # build multi-touchFor more gesture patterns and a swipe helper library, see our mobile gestures guide.
8. Page Object Model for Mobile
The Page Object Model works exactly the same for mobile as for web. The class name is the screen name; the methods are user actions.
Login screen (Android)
public class LoginScreen {
private AndroidDriver driver;
@AndroidFindBy(accessibility = "email-field")
private WebElement emailField;
@AndroidFindBy(accessibility = "password-field")
private WebElement passwordField;
@AndroidFindBy(accessibility = "submit-button")
private WebElement submitButton;
public LoginScreen(AndroidDriver driver) {
this.driver = driver;
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
public HomeScreen loginAs(String email, String password) {
emailField.clear();
emailField.sendKeys(email);
passwordField.clear();
passwordField.sendKeys(password);
submitButton.click();
return new HomeScreen(driver);
}
}Base screen (shared logic)
public abstract class BaseScreen {
protected AndroidDriver driver;
protected WebDriverWait wait;
public BaseScreen(AndroidDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(15));
}
public abstract boolean isLoaded();
public void waitForLoaded() {
wait.until(d -> isLoaded());
}
}Test using POM
@Test
public void loginFlow() {
LoginScreen login = new LoginScreen(driver);
login.waitForLoaded();
HomeScreen home = login.loginAs("admin@example.com", "Sup3rSecret!");
assertTrue(home.isLoaded());
}For the full POM pattern with WebDriver and Java, see our Selenium POM guide.
9. Appium 2.0 New Features
Appium 2.0 (released April 2023) is the 2026 standard. The big shift: driver plugins instead of bundled drivers.
Key improvements
- Driver management —
appium driver installinstead of--default-capabilitiesmagic - Better protocol support — BiDi protocol support added
- No more Appium Doctor — replaced by
appium doctorcommand - Multiple drivers per session — run Espresso + UiAutomator2 in the same test
- Improved error messages — actionable stack traces
- Better parallel — each driver is independently versioned
Espresso driver (Android fast path)
For purely native Android apps, use the Espresso driver — it's 5–10× faster than UiAutomator2:
options = EspressoOptions()
options.app = "/path/to/app.apk"
options.app_activity = "com.example.MainActivity"The trade-off: Espresso only sees your app's UI, not other apps or system UI. Use it for fast in-app tests; use UiAutomator2 for cross-app tests.
For a comparison of mobile test drivers, see our mobile testing strategy guide.
10. Hybrid and Web Apps
Hybrid apps (Cordova, Ionic, React Native WebView) and mobile web apps can be tested through the Chrome or Safari driver inside Appium.
Mobile Chrome (Android)
from appium.options.android import ChromeOptions
options = ChromeOptions()
options.device_name = "Pixel_7_API_34"
options.browser_name = "Chrome"
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
driver.get("https://example.com")Mobile Safari (iOS)
from appium.options.ios import SafariOptions
options = SafariOptions()
options.device_name = "iPhone 15"
options.platform_version = "17.0"
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
driver.get("https://example.com")For hybrid apps, switch between NATIVE_APP and WEBVIEW contexts:
contexts = driver.contexts
driver.switch_to.context(contexts[1])
driver.find_element(By.ID, "submit").click()
driver.switch_to.context(contexts[0])11. Cloud Testing with BrowserStack and Sauce Labs
Running tests against real devices in a cloud grid is the 2026 default. It gives you 100+ device/OS combos without maintaining your own device farm.
BrowserStack
options = UiAutomator2Options()
options.device_name = "Google Pixel 7"
options.platform_version = "14.0"
options.app = "bs://<your-app-hash>" # upload to BrowserStack first
options.set_capability("bstack:options", {
"userName": "your_username",
"accessKey": "your_access_key",
"projectName": "QA Regression",
"buildName": "1.0.0",
"sessionName": "Login test",
})
driver = webdriver.Remote(
"https://hub-cloud.browserstack.com/wd/hub",
options=options
)Sauce Labs
options.set_capability("sauce:options", {
"username": "your_username",
"accessKey": "your_access_key",
"name": "Login test",
"build": "1.0.0",
})
driver = webdriver.Remote(
"https://ondemand.us-west-1.saucelabs.com:443/wd/hub",
options=options
)Parallel execution on cloud
import pytest
@pytest.mark.parametrize("device", [
{"deviceName": "Google Pixel 7", "platformVersion": "14.0"},
{"deviceName": "Samsung Galaxy S23", "platformVersion": "13.0"},
{"deviceName": "Google Pixel 4", "platformVersion": "12.0"},
])
def test_login_across_devices(driver, device):
# driver is provided by pytest plugin, one session per param
login = LoginScreen(driver)
login.waitForLoaded()
home = login.loginAs("admin@example.com", "Sup3rSecret!")
assert home.isLoaded()For more cloud testing patterns, see our BrowserStack integration guide.
12. CI/CD Integration Patterns
GitHub Actions (Android emulator)
name: Android Mobile Tests
on: [push, pull_request]
jobs:
android:
runs-on: macos-latest # macOS has fastest Android emulator
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: '17' }
- run: npm install -g appium
- run: appium driver install uiautomator2
- name: Start emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
profile: pixel_7
script: ./gradlew test
- run: mvn -B test -Dtest=MobileTestGitLab CI with BrowserStack
mobile-tests:
image: maven:3.9-eclipse-temurin-17
script:
- mvn -B test -Dbs.user=$BROWSERSTACK_USER -Dbs.key=$BROWSERSTACK_KEY
artifacts:
when: always
paths: [target/surefire-reports/]For more CI patterns, see our mobile testing CI/CD guide.
13. Appium vs Alternatives
| Dimension | Appium | Espresso | XCUITest | Maestro | Detox |
|---|---|---|---|---|---|
| Cross-platform | Yes | Android only | iOS only | Yes | React Native only |
| Languages | Many | Kotlin/Java | Swift/Obj-C | YAML | JS |
| Speed | Medium | Fastest | Fastest | Fast | Fast |
| Setup complexity | High | Low | Low | Very low | Medium |
| Cloud-friendly | Excellent | Limited | Limited | Good | Good |
| WebDriver standard | Yes | No | No | No | No |
| Best for | Cross-platform teams | Pure Android | Pure iOS | Smoke tests | React Native |
The 2026 rule of thumb:
- Cross-platform team + needs WebDriver → Appium
- Pure Android, deep coverage → Espresso for in-app + Appium for cross-app
- Pure iOS, deep coverage → XCUITest for in-app + Appium for cross-app
- React Native → Detox or Maestro
- Quick smoke tests → Maestro (YAML is faster to write)
For more on mobile framework selection, see our mobile testing tools comparison.
14. Mobile Testing Best Practices for 2026
Do
- Set accessibility IDs in the app source — they are the most stable locator
- Test on real devices for the most realistic results
- Use cloud grids to cover the device/OS matrix
- Use Espresso (Android) or XCUITest (iOS) for in-app performance tests
- Use Appium for cross-app and cross-platform tests
- Reset app state between tests (clear data, kill, relaunch)
- Capture screenshots on failure for debugging
- Test on the slowest, oldest device in your supported matrix
Don't
- Don't use XPath as your default — it's brittle on mobile
- Don't test gestures with hard-coded coordinates — use locators
- Don't share app state across tests — isolation is critical
- Don't run mobile tests on a Wi-Fi connection in CI — use wired
- Don't test against production builds — use dedicated test builds
- Don't skip testing on tablets — your responsive design may break
For more on test automation best practices, see our test automation strategy guide.
Frequently asked questions
1.What is Appium?
2.Is Appium still relevant in 2026?
3.What languages does Appium support?
4.Can Appium test native iOS apps?
5.Can Appium test native Android apps?
6.What's the difference between Appium and Selenium?
7.How long does it take to set up Appium?
8.Where can I practice?
9.Is Appium slower than native frameworks?
10.How much does mobile cloud testing cost?
Practice these questions
Rehearse Selenium and Playwright automation questions covering framework design, waits, locators and CI/CD.
Was this article helpful?
More from Mobile Testing Strategy
Real vs cloud devices, coverage matrix, tooling.
Keep building your QA edge
Pillar guides- Automation QA Engineer Roleexplore this role in depthAutomation QA Engineer job scope, tools, salary, and hiring pipeline.
- Mobile Tester RoleMobile Tester career pathMobile Tester career guide — Appium, real devices, iOS/Android pipelines.
- Appium vs EspressoSoftwareTestPilot's Appium vs Espresso breakdownSide-by-side Appium vs Espresso — speed, hybrid vs native, CI, hiring signals for 2026.
- QA Skills Hubstructured learning tracks for testersStructured skill tracks — Selenium, Playwright, Cypress, API, JMeter, SQL, Java, Python for testers.
- QA & Testing GlossaryQA terminology explained in plain English500+ software testing terms defined — from ISTQB vocabulary to CI/CD, AI testing, and framework jargon.
Continue reading

Playwright Locator Best Practices (2026) — The Only Guide You Need
11 min read
How to Migrate a Postman Collection to Playwright API Tests (2026 Guide)
12 min read
Why Every QA Engineer Must Master CI/CD Pipelines in 2026 (Or Risk Obsolescence)
12 min readRelated concepts, tools & standards around Automation Testing
A quick reference of the people, companies, frameworks and technologies most often mentioned alongside Automation Testing in real QA teams — useful when you're mapping a learning path, preparing for interviews, or scoping a new project.
Join the QA Community
Connect with fellow testers, share job leads, and get career advice.
Discussion
Ask a question, share your experience, or correct us. Be kind — real people are reading.