Guides

Run Appium tests on a cloud Android device

Upload a build, start a hosted Appium runner, and point an existing W3C WebDriver suite at the session URL. No local emulator or SDK.

A local Appium run needs an emulator image, hardware virtualisation, a matching SDK and a device farm you keep alive. SpawnDroid runs Android on ARM64 servers and gives the suite a WebDriver URL, so the local machine only needs the test framework.

What you need

  • An APK or AAB, or the sample app.
  • An API key with upload scope, or a signed-in console session.
  • A WebDriver client: Appium's Java/Python clients, WebdriverIO, Selenium, or any W3C client.

1. Upload the build

APK_ID=$(curl -sS -X POST https://app.spawndroid.io/api/apps \
  -H "Authorization: Bearer $SPAWNDROID_API_KEY" \
  -F file=@app-debug.apk | jq -r '.app.slug')

Uploading only stores the build. It does not boot a device.

2. Start a runner

RESPONSE=$(curl -sS -X POST https://app.spawndroid.io/api/v1/sessions \
  -H "Authorization: Bearer $SPAWNDROID_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"artifactId\":\"$APK_ID\"}")

The response carries everything the suite needs:

{
  "sessionId": "sess_…",
  "state": "creating",
  "webdriverUrl": "https://stream.spawndroid.io/ci/sess_…/token/wd/hub",
  "device": { "serial": "emulator-5554" },
  "app": { "package": "com.acme.checkout" },
  "eventsUrl": "https://…/events"
}

Poll GET /api/v1/sessions/:id (or follow eventsUrl) until state is ready. Starting is asynchronous because the device boots and installs the build first.

3. Point the suite at it

test_checkout.py
import os
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy

options = UiAutomator2Options().load_capabilities({
    "platformName": "Android",
    "appium:automationName": "UiAutomator2",
    "appium:udid": os.environ["SPAWNDROID_UDID"],
    "appium:appPackage": os.environ["SPAWNDROID_APP_PACKAGE"],
    "appium:autoGrantPermissions": True,
})

driver = webdriver.Remote(os.environ["SPAWNDROID_WD_URL"], options=options)
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Add to cart").click()
driver.quit()

Only the URL, udid and package change from a local run. Do not set appium:app: the build is pre-installed.

4. Report the result

curl -sS -X DELETE https://app.spawndroid.io/api/v1/sessions/$SESSION_ID \
  -H "Authorization: Bearer $SPAWNDROID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"result":"passed"}'

Use "failed" when the suite fails. Teardown destroys the device and retains the final logcat and a screen recording on Personal and above.

Per-run or stable hub?

ModePick it when
Per-run sessionYou want explicit control over one session, a mock location, or a session-scoped URL.
Stable hubYou want one endpoint and one token, and you choose the build by capability. The hub allocates a device on the first command.

Both modes run the same Appium 3 + UiAutomator2 image. See CI tests for the stable-hub setup and the API reference for every field.

Run it on SpawnDroid

The Hobby plan includes one parallel runner and 200 device-minutes a month, no card. Start free, then follow the quickstart or the GitHub Actions guide.

Was this page helpful?

On this page