Skip to main content
This guide covers how to build and package your compiled Espresso instrumentation suite so it runs correctly on the MobileBoost device grid, and how to trigger and monitor runs via the API. Unlike iOS/XCUITest, Android does not split builds into a physical vs. virtual “slice” - the same pair of APKs runs on both real devices and emulators, as long as the APKs contain the right ABIs (see ABI coverage).
You do not need to solve signing for our devices. Our grid re-signs both the app under test and the test APK with a common certificate before installing, so instrumentation always runs with matching signatures.
All API endpoints below authenticate with your mb_live_ API key passed as a Bearer token in the Authorization header.

Prerequisites

  • Android SDK / Gradle wrapper (the project’s own ./gradlew is all you need).
  • Your project has an androidTest source set with Espresso tests and a testInstrumentationRunner configured (usually androidx.test.runner.AndroidJUnitRunner or a subclass).
  • You know three identifiers - you’ll need them at upload time:
    • Application ID of the app under test (must match the APK you upload)
    • Test application ID (usually <applicationId>.test)
    • Instrumentation runner class (from defaultConfig.testInstrumentationRunner)

1. Build the two APKs

Espresso runs need exactly two artifacts, built from the same variant: Build both in one go:
Replace Debug with the variant you test against (e.g. assembleStagingDebug assembleStagingDebugAndroidTest for a stagingDebug variant).
Both APKs must come from the same variant of the same build - the test APK’s targetPackage is baked in at compile time and must match the application ID of the app APK you upload.
Notes:
  • Upload APKs, not AABs. If your CI produces an .aab, either build the APK variant directly or generate a universal APK with bundletool build-apks --mode=universal.
  • The app under test should be debuggable (debuggable true), which every *Debug variant is by default. Instrumenting a non-debuggable build relies on signature matching, which our re-signing handles, but a debuggable build also enables better failure artifacts (screenshots, hierarchy dumps).
  • If minification is enabled on the variant, make sure your ProGuard/R8 rules keep the classes your tests reference - a common source of ClassNotFoundException at instrumentation start.

2. ABI coverage (physical vs. virtual)

There is no separate “simulator build” on Android, but ABI splits can bite you: If your build uses splits.abi or per-ABI APKs, upload the APK that contains the ABI of the target device - or disable splits for the test build and upload a universal APK. An APK with pure Java/Kotlin code and no native libraries runs everywhere. Uploading an arm64-only APK for an emulator run is the Android equivalent of the iOS “wrong slice” mistake: it installs fine on a physical device but fails on x86_64 emulators.

3. Verify the APKs before uploading

The android:targetPackage in the test APK’s <instrumentation> element must equal the package of the app APK. If they differ, the instrumentation will install but refuse to start - the single most common failure.

4. Signing - what you do and don’t need to do

You do not need to share keystores or match our certificates. We strip the signatures from both APKs and re-sign them with the same certificate before installing, so the app/test signature pair always matches. Building with the default debug keystore is fine. Because we re-sign, avoid features in the test APK that depend on your specific signing certificate (e.g. signature-level permissions against other apps of yours, Google API keys restricted to your release SHA-1). The app under test is re-signed too - if it verifies its own certificate at runtime (tamper detection, Play Integrity, Firebase App Check in enforced mode), disable that check in the variant you upload.

5. Optional: get your test list for sharding

If you want to split tests across devices, we can shard automatically, or you can send us the test identifiers. Identifiers use package.ClassName#testMethod notation, e.g. com.mycompany.app.LoginTests#testValidLogin. Class-level (com.mycompany.app.LoginTests) and package-level (com.mycompany.app.checkout) identifiers are also accepted in filters. You can list them locally with:
(-e log true dry-runs the suite and logs test names without executing them.)

6. What to upload

Each upload returns an ID (app_id / test_suite_id) that you reference when triggering a run. Uploads are reusable across runs - upload once per CI build, trigger as many runs as you like against the same pair. Managing uploaded test suites

Common mistakes checklist

  • Test APK built against a different variant/flavor than the uploaded app APK. → targetPackage mismatch; build both from the same variant in the same Gradle invocation.
  • Uploaded an .aab instead of an .apk. → Build the APK, or convert with bundletool --mode=universal.
  • ABI-split arm64 APK uploaded for an emulator run (or x86_64 for a physical run). → Upload a universal APK or the matching ABI split.
  • R8/ProGuard stripped classes the tests reference. → Add keep rules or test against a non-minified variant.
  • App verifies its own signing certificate at runtime and detects our re-sign. → Disable tamper/integrity checks in the uploaded variant.
  • Ran ./gradlew connectedAndroidTest looking for the test APK. → That task installs and runs locally; use assemble<Variant>AndroidTest to produce the uploadable APK.

7. Pick target devices

List the Android devices available to your organisation, with their exact names to use in the devices field of the build request:
Device identifiers combine model and OS version, e.g. Google Pixel 8-15.0 or Samsung Galaxy S23-14.0. Virtual devices (emulators) are listed alongside physical ones and flagged as such.

8. Trigger a test run

Once both files are uploaded, start a run with the IDs returned by the two upload calls: the app_id from POST /uploadBuild and the test_suite_id from POST /app-automate/espresso/test-suite. Endpoint
Minimal request
Required parameters Optional parameters - test selection These map directly onto AndroidJUnitRunner instrumentation arguments and apply to the whole run: Optional parameters - execution control Optional parameters - environment & reporting Full request with GPS and sharding

GPS location

gpsLocation overrides the device’s location for the whole run; it is applied on each device before your first test starts and cleared automatically at teardown.
If you omit gpsLocation, physical devices keep their real GPS and virtual devices use our neutral default. The mock location is injected at the platform level, so FusedLocationProviderClient and LocationManager both observe it - no changes to your app or tests needed.

Sharding

Sharding is optional. Provide a shards object to split the suite across devices and run them in parallel. Auto-distribute - give only numberOfShards; we spread the discovered tests evenly across that many shards:
Explicit test cases per shard - provide a mapping array. Each entry names a shard and selects its tests with a strategy and a values list. numberOfShards must match the number of mapping entries.
Response
Store the build_id; it identifies the run when you poll status and download results.

9. Check run status

Poll the build with the build_id from the launch response (or skip polling entirely by passing callbackURL at trigger time).
The build carries an overall status plus one session per device/shard combination. Each session has its own session_id (used in the next section to download artifacts).
Status values (both build and session): queued, running, passed, failed, error (infrastructure/setup problem, e.g. install or instrumentation-start failure), timedout. Poll until terminal
Stop a running build Abort a build early (e.g. a newer commit superseded it in CI). Sessions already finished keep their results; running sessions are terminated and marked as stopped.

10. Retrieve results and artifacts

Once the build reaches a terminal status, fetch the machine-readable report at the build level and the raw artifacts per session. Build-level test report
junit returns a standard JUnit XML your CI can ingest; json returns the same pass/fail data as structured JSON. Session details and artifacts Each session (one per device/shard, listed in the build status response) exposes its per-test results and download URLs for the raw artifacts:
Artifact URLs are pre-signed and expire; download promptly or re-fetch the session to get fresh ones. Code coverage If the build was triggered with coverage: true, download the Jacoco execution data per session and merge/report it with your local Jacoco tooling: