Appium UI Automation

Jumpstarter gives every UI-automation target the same Appium client interface, whatever its platform: physical devices, emulators and simulators, on Android or iOS, local or remote. A test asks the target for an Appium endpoint and opens WebDriver sessions on it; it does not change when the target does.

Why Appium

Appium is Jumpstarter’s default UI-automation framework because it suits how Jumpstarter is used:

  • One API on every platform. Appium speaks the W3C WebDriver protocol and hands each session to a platform driver, such as UiAutomator2 for Android or XCUITest for iOS, with further drivers for desktop and TV platforms. Tests use the same client calls whichever device they drive, so one suite covers every target, and every Jumpstarter target can offer the same interface.

  • Python support. Jumpstarter clients are written in Python, and the Appium project maintains an official Python client, Appium-Python-Client, built on Selenium’s Python bindings. It installs with pip, works with pytest and other Python test runners, and accepts the configuration Jumpstarter hands it directly. Clients in other languages, such as Java, JavaScript, Ruby or .NET, can connect too.

  • Nothing to install on the client. Appium and its platform drivers run on the exporter, next to the device, so clients need only a WebDriver client library: no Appium, Node.js or platform SDKs.

  • Open and established. Appium is open source under the Apache 2.0 licence, hosted by the OpenJS Foundation, and widely used, so existing Appium tests, tools and experience carry over.

How it works

Each target driver adds an appium child when the exporter configures one. The exporter runs Appium for the lease, next to the device, and the client reaches it over the lease through verified HTTPS. Clients need neither Appium nor the device’s tools.

Target

Driver

Appium runs

Configure

Android device

adb

exporter

appium:

Android emulator

androidemulator

exporter

appium:

iPhone or iPad on USB

iosdevice

exporter

appium: with wda_port for an already running WebDriverAgent, or WebDriverAgent launch capabilities

iOS simulator

iossimulator

exporter (macOS)

appium: with a prebuilt simulator WebDriverAgent

iPhone or iPad in Xcode’s Device Hub

iosdevice

this Mac

devicehub_appium() with your WebDriverAgent build

Device Hub is the one case where Appium runs on the client: WebDriverAgent is reachable only inside the Mac’s Device Hub connection, so the Mac starts your installed Appium. It still yields the same target, so tests do not change.

Every target pins the session to its own device. Capabilities such as appium:udid, the device set or the ADB server are fixed by the exporter and replace whatever a client sends.

Write the test once

from appium import webdriver
from appium.options.common import AppiumOptions

from jumpstarter.common.utils import env

with env() as root, root.phone.appium.serve() as target:
    options = AppiumOptions().load_capabilities(target.capabilities())
    driver = webdriver.Remote(target.url, options=options, client_config=target.client_config())
    try:
        print(driver.page_source)
    finally:
        driver.quit()

root.phone is the target’s export name. For a simulator or emulator, power it on first (root.simulator.power.on()); Appium waits for it to boot. For Device Hub, replace root.phone.appium.serve() with root.phone.devicehub_appium("WebDriverAgentRunner.xctestrun").

target.capabilities() holds what the target fixes, such as platformName, appium:automationName and appium:udid. Add your own, for example appium:bundleId or appium:appPackage, before opening a session.

If your client configuration allow-lists driver packages under drivers.allow, add jumpstarter_driver_appium.*, or the appium child cannot load.

Install Appium-Python-Client to run this example. For complete pytest suites that drive the built-in Settings app through a local exporter, see python/examples/appium-android-emulator and python/examples/appium-ios-simulator in the repository. Other WebDriver clients work too: use target.ssl_context() or target.ca_file for the lease’s certificate authority, and keep certificate verification enabled.

Applications

The exporter’s Appium cannot read files on your machine. Upload an application first, then pass the token it returns wherever Appium expects an application:

token = target.upload_app("build/app-debug.apk")  # or an .ipa, or a .app bundle
options.set_capability("appium:app", token)
# or, inside a session; the argument is appPath on Android and app on iOS:
driver.execute_script("mobile: installApp", {"appPath": token})
driver.execute_script("mobile: installApp", {"app": token})

root.phone.appium.upload_app() does the same without serving Appium first. Installed applications start with appium:bundleId (iOS) or appium:appPackage (Android). A token works only on the target that issued it.

Device Hub sessions accept the same capabilities, with tokens from the Device Hub target’s own upload_app(). Appium on your Mac cannot install applications over Device Hub, so Jumpstarter installs them with xcrun devicectl before Appium sees the request, resetting an installed copy as Appium does, and starts an appium:app by its bundle identifier. Web views, MJPEG screen streaming and mobile: listApps are not available over Device Hub; see iosdevice.

Command line

Inside a jmp shell:

j phone appium                          # print url, ca_file and capabilities until Ctrl+C
j phone appium -- pytest tests/ui       # run a command with the endpoint in its environment
j phone appium upload build/app.ipa     # print an application token

The command receives JUMPSTARTER_APPIUM_URL, JUMPSTARTER_APPIUM_CAPABILITIES (JSON), JUMPSTARTER_APPIUM_CA_FILE and Node’s NODE_EXTRA_CA_CERTS, so test runners in other languages can connect too. For iOS targets the export is usually ios (j ios appium), and for a simulator or emulator the appium child sits next to power (j simulator appium).

Prepare the exporter

The exporter needs Node.js, Appium and the Appium driver for its targets:

  • Android: UiAutomator2, a JDK, and the Android SDK’s platform tools and build tools, with JAVA_HOME and ANDROID_HOME set in the exporter’s environment.

  • iOS: XCUITest.

Jumpstarter does not bundle, download, build or sign Appium, its drivers or WebDriverAgent.

Containerized exporters pin these in their image. For example, on top of an exporter image:

FROM quay.io/jumpstarter-dev/jumpstarter:latest
RUN dnf install -y nodejs npm java-21-openjdk-headless && dnf clean all
# For Android targets, install the SDK's platform-tools and build-tools here, for
# example with sdkmanager, and set JAVA_HOME to the JDK.
ENV ANDROID_HOME=/opt/android-sdk
ENV APPIUM_HOME=/opt/appium/home
RUN npm install --prefix /opt/appium appium@3.8.0 && \
    /opt/appium/node_modules/.bin/appium driver install uiautomator2@8.7.3 && \
    /opt/appium/node_modules/.bin/appium driver install xcuitest@12.22.8

Then point the target’s appium block at that installation:

export:
  phone:
    type: jumpstarter_driver_adb.driver.AdbDevice
    config:
      usb_port: "1-4.2"
      appium:
        executable: /opt/appium/node_modules/.bin/appium
        home: /opt/appium/home

macOS exporters for simulators install Appium and its XCUITest driver on the host in the same way. Each driver’s page lists its own options.

Security

A lease controls its device through Appium, and Appium runs inside the exporter. The Appium driver keeps it to the device:

  • Appium listens on exporter loopback only, with every Appium insecure feature denied, no plugins and a private home directory.

  • Capabilities that name exporter files, executables or URLs, the proxy capability and remotePath uploads are rejected, so clients cannot make the exporter read its files or contact other hosts. Applications are accepted only as uploads.

  • Target-specific capabilities that would reach beyond the device, such as starting emulators or another ADB host on Android, or erasing the simulator, are rejected or fixed off.

Leases still get full control of their device. Reset or re-provision devices between untrusted tenants, and keep exporters that serve untrusted tenants dedicated to that role.