Skip to content
SDETRole
SDETRole

Lesson 6 of 12 · Automation Frameworks

Appium Introduction

How Appium automates native, hybrid and mobile web apps: the server and driver model, W3C capabilities, mobile locators, a first Java test and what makes mobile tests flaky.

intermediate 7 min read Updated

You will learn to

  • Explain Appium's client, server and driver architecture
  • Install the Appium server and the UiAutomator2 and XCUITest drivers
  • Write W3C capabilities and a first Android test with the Java client
  • Choose robust mobile locators and avoid common causes of flaky mobile tests

Prerequisites

  • Selenium WebDriver Basics
  • Java for Testers

Appium is the best-known open-source, cross-platform tool for automating mobile apps. It speaks the same W3C WebDriver protocol as Selenium, so if you know Selenium the API will feel familiar: sessions, findElement, click, explicit waits. What is different is everything around it: devices, emulators, app builds, platform-specific drivers and much more variability in timing.

This lesson covers the Appium 2 and 3 architecture, which differs significantly from Appium 1 tutorials you may still find online.

What Appium can automate

App type What it is How Appium drives it
Native Built with platform SDKs (Kotlin/Java, Swift) or cross-platform frameworks that render native views Through the platform’s automation framework
Hybrid Native shell with web content in a WebView Native context for the shell, switch to a WebView context for web content
Mobile web A website in the device browser Like Selenium, with mobile Chrome or Safari

Appium’s guiding idea is that you should not need to modify or recompile your app to test it: it automates the same build you ship, using the vendors’ own automation frameworks under the hood.

Architecture: server, drivers, clients

Test code (Java, Python, JS, ...)
   |  W3C WebDriver over HTTP
   v
Appium server (Node.js)
   |-- UiAutomator2 driver --> Android device/emulator (Google's UiAutomator2)
   |-- XCUITest driver     --> iOS device/simulator (Apple's XCUITest)
   |-- other drivers (Espresso, Mac2, Windows, ...)
  • The Appium server is a Node.js program. Since Appium 2 it ships without any drivers; you install the ones you need.
  • Drivers translate WebDriver commands into platform automation. UiAutomator2 is the standard Android driver; XCUITest is the iOS driver and requires macOS with Xcode.
  • Plugins extend the server (for example, image-based element finding).
  • Clients are language libraries. The Java client (io.appium:java-client) extends Selenium’s Java bindings.

Installation

You need Node.js, plus Android Studio (SDK, platform tools, an emulator) for Android, or Xcode for iOS.

npm install -g appium

appium driver install uiautomator2
appium driver install xcuitest      # macOS only

appium driver list --installed
appium driver doctor uiautomator2   # checks ANDROID_HOME, Java, adb and so on

appium                              # starts the server on http://127.0.0.1:4723

Appium 1 served on http://localhost:4723/wd/hub. Appium 2 and later use the root path, http://127.0.0.1:4723, by default. A “404 on session creation” when migrating old code is almost always this.

Appium Inspector is a separate desktop app (also available as a web version) that starts a session, shows a screenshot of the app with its element tree, and lets you try locators. It is the mobile equivalent of browser dev tools; use it constantly.

Capabilities

Every session starts with capabilities describing the platform, device and app. Appium follows the W3C rule that non-standard capabilities need a vendor prefix, so everything except standard ones like platformName is prefixed appium::

{
  "platformName": "Android",
  "appium:automationName": "UiAutomator2",
  "appium:deviceName": "Pixel_8_API_35",
  "appium:app": "/builds/shop-app-release.apk",
  "appium:appPackage": "com.example.shop",
  "appium:appActivity": ".MainActivity",
  "appium:noReset": false,
  "appium:newCommandTimeout": 120
}

For iOS:

{
  "platformName": "iOS",
  "appium:automationName": "XCUITest",
  "appium:deviceName": "iPhone 16",
  "appium:platformVersion": "18.0",
  "appium:app": "/builds/ShopApp.app",
  "appium:bundleId": "com.example.shop"
}

Common ones worth knowing:

  • appium:app: path or URL to the .apk, .app or .ipa to install.
  • appium:udid: a specific physical device, when several are connected.
  • appium:noReset and appium:fullReset: whether app data is cleared between sessions. Clearing gives isolation; keeping it is faster.
  • appium:newCommandTimeout: seconds the server waits for a new command before ending the session; raise it while debugging.

A first Android test in Java

The Java client provides option classes so you do not have to type capability names by hand:

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import java.net.URI;
import java.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

class LoginAndroidTest {
    private AndroidDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setUp() throws Exception {
        UiAutomator2Options options = new UiAutomator2Options()
            .setDeviceName("Pixel_8_API_35")
            .setApp(System.getenv("APP_PATH"))
            .setNewCommandTimeout(Duration.ofSeconds(120));

        driver = new AndroidDriver(URI.create("http://127.0.0.1:4723").toURL(), options);
        wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    }

    @AfterEach
    void tearDown() {
        if (driver != null) driver.quit();
    }

    @Test
    void validUserSeesHome() {
        wait.until(ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("email-input")))
            .sendKeys("ana@example.com");
        driver.findElement(AppiumBy.accessibilityId("password-input"))
            .sendKeys(System.getenv("TEST_PASSWORD"));
        driver.findElement(AppiumBy.accessibilityId("login-button")).click();

        var title = wait.until(ExpectedConditions.visibilityOfElementLocated(
            AppiumBy.id("com.example.shop:id/home_title")));
        assertEquals("Welcome back", title.getText());
    }
}

UiAutomator2Options sets platformName and appium:automationName for you. The iOS equivalent is XCUITestOptions with IOSDriver.

Mobile locators

Mobile apps have no CSS and no HTML ids, so the locator strategies differ:

Strategy Android iOS Notes
AppiumBy.accessibilityId content-description accessibilityIdentifier / label First choice: cross-platform and fast
AppiumBy.id resource-id (package:id/name) name Stable when developers set ids
AppiumBy.androidUIAutomator UiSelector expressions n/a Powerful Android-only queries, including scrolling into view
AppiumBy.iOSNsPredicateString / iOSClassChain n/a Native iOS queries Fast alternatives to XPath on iOS
AppiumBy.xpath Works Works Slowest, especially on iOS; last resort

The best mobile automation investment is asking developers to add accessibility identifiers (or testTags exposed as resource ids in Jetpack Compose) to key elements. It makes tests stable and it improves accessibility for screen-reader users at the same time.

// Android: scroll a list until an item is visible, then return it
WebElement item = driver.findElement(AppiumBy.androidUIAutomator(
    "new UiScrollable(new UiSelector().scrollable(true))"
    + ".scrollIntoView(new UiSelector().text(\"Laptop Stand Aluminium\"))"));

Gestures

Taps and typing work like Selenium. Swipes, scrolls and long presses use either W3C Actions (PointerInput sequences) or driver-specific mobile commands run through executeScript, which are simpler:

// UiAutomator2: swipe up within a region of the screen
driver.executeScript("mobile: swipeGesture", Map.of(
    "left", 100, "top", 500, "width", 600, "height", 1000,
    "direction", "up",
    "percent", 0.75));

Mobile command names and arguments differ between drivers, so check the driver’s documentation (UiAutomator2 and XCUITest each document their mobile: commands).

Why mobile tests get flaky

Mobile adds variability that web tests rarely see:

  • Device and OS fragmentation: screen sizes, OS versions and manufacturer skins change layouts and timing.
  • Emulator performance: cold boots, animations and slow CI machines. Disable animations in developer options on test devices and use generous explicit waits.
  • System dialogs: permission prompts, keyboards covering fields, battery or update notifications. Grant permissions up front where the driver supports it (UiAutomator2 has appium:autoGrantPermissions) and hide the keyboard deliberately.
  • App state: leftover data from earlier tests. Decide on a reset strategy and stick to it.

Running across many real devices is usually done on a device cloud or an in-house device lab rather than on CI agents directly.

Practice exercise

You are automating the checkout of a shopping app on Android and iOS. For each element below, choose a locator strategy and explain why, then write the Android capabilities JSON for a session that installs /builds/shop.apk on the physical device with UDID R58M123ABC, auto-grants permissions and clears app data before the session.

  1. A “Checkout” button with accessibility id checkout-button on both platforms
  2. A product name in a long, scrollable list, with no id
  3. A promo code text field with Android resource-id com.example.shop:id/promo and no iOS identifier
  4. A banner whose only identifying feature is its position in a deeply nested layout

The locator thinking carries over from web: practise it in the Selenium Lab.

Show a model answer
  1. AppiumBy.accessibilityId("checkout-button"): one locator for both platforms, fast and stable.
  2. Android: androidUIAutomator with UiScrollable(...).scrollIntoView(new UiSelector().text("...")), which scrolls and finds in one call. iOS: an iOSNsPredicateString such as label == "Laptop Stand Aluminium" after scrolling with a mobile: scroll command.
  3. Android: AppiumBy.id("com.example.shop:id/promo"). iOS: ask developers to add an accessibility identifier; until then, an iOS class chain or predicate on the field’s type and placeholder.
  4. Raise it with the developers first. A positional XPath will break on the next layout change and is slow; if you must ship a test now, use it as a clearly marked temporary locator.

Capabilities:

{
  "platformName": "Android",
  "appium:automationName": "UiAutomator2",
  "appium:udid": "R58M123ABC",
  "appium:deviceName": "Android Device",
  "appium:app": "/builds/shop.apk",
  "appium:autoGrantPermissions": true,
  "appium:noReset": false,
  "appium:fullReset": false
}

With noReset: false and fullReset: false, UiAutomator2 clears the app’s data at session start without uninstalling the app, which is faster than a full reset while still isolating tests.

Key takeaways

  • Appium is a Node.js server that speaks W3C WebDriver and delegates to drivers: UiAutomator2 for Android, XCUITest for iOS. Install drivers with appium driver install.
  • Appium 2 and later serve at the root path (http://127.0.0.1:4723), and non-standard capabilities need the appium: prefix.
  • Prefer accessibility ids, then resource ids and platform-native locators; treat XPath as a slow last resort.
  • Use Appium Inspector to explore the element tree and test locators.
  • Mobile flakiness comes from devices, emulators, system dialogs and app state; design waits, permissions and resets deliberately.

Key terms

Finished this lesson?

Progress is saved in this browser. No account needed.

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →