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
uploadscope, 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
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?
| Mode | Pick it when |
|---|---|
| Per-run session | You want explicit control over one session, a mock location, or a session-scoped URL. |
| Stable hub | You 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?
Appium capabilities for Android
The UiAutomator2 capabilities that matter for Android, what SpawnDroid fills in, and working Java, Python, Kotlin and WebdriverIO examples.
Appium in GitHub Actions without an emulator
Run an existing Appium or WebdriverIO suite on a hosted Android runner from GitHub Actions, with secrets, JUnit reporting and guaranteed teardown.