mcpField
R
running-tests-on-gradle-managed-devices

running-tests-on-gradle-managed-devices

github.com/skydovesTesting & quality33962 installsUpdated 22w ago
Repository

About running-tests-on-gradle-managed-devices

A guide to configuring and running Android instrumented tests on Gradle Managed Devices. It is for teams seeking reproducible emulator runs and covers device matrices, generated tasks, ATD images, sharding, and CI reports.

Key features

  1. 01Declare virtual devices in Gradle
  2. 02Run device and device-group tasks
  3. 03Use ATD images for CI tests
  4. 04Shard instrumented tests in parallel
  5. 05Collect managed-device test reports

Use cases

  1. 01Run tests on defined devices in CI
  2. 02Cover multiple API levels or device profiles
  3. 03Reduce CI emulator startup overhead
  4. 04Split slow test suites across emulators

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/skydoves/android-testing-skills --skill running-tests-on-gradle-managed-devices

After installing, type/running-tests-on-gradle-managed-devicesin Claude Code to confirm it loaded.

Details

From the project README

Running Tests On Gradle Managed Devices — Gradle Owns The Emulator

Gradle Managed Devices (GMD) move the emulator into the build: you declare devices in build.gradle.kts, and ./gradlew <device>DebugAndroidTest downloads the system image, boots a fresh emulator, runs androidTest, collects results, and shuts it down. The payoff is reproducibility — CI and every machine run the exact same device — and no hand-managed emulators. This skill covers the DSL, the generated tasks, ATD images for cheap CI, sharding, and where GMD sits relative to connectedAndroidTest and raw am instrument (see ../../../adb/tests/running-instrumented-tests-via-adb/SKILL.md).

When to use this skill

  • The user wants instrumented tests to run on a defined, reproducible emulator in CI without adb-managing a device.
  • The user mentions managedDevices, allDevicesCheck, ManagedVirtualDevice, or a generated task like pixel2api30DebugAndroidTest.
  • The user wants the test matrix (which API levels / form factors) to live in version control, not in someone's local AVD list.
  • CI emulator runs are slow/flaky and the user asks about ATD ("Automated Test Device") images.
  • The user wants to shard a slow instrumented suite across several emulator copies.

When NOT to use this skill

  • The user wants to run tests on a physical device or an emulator they already have running — that is ./gradlew connectedAndroidTest / connectedDebugAndroidTest; GMD is for emulators Gradle creates.
  • The user is invoking the runner directly without Gradle (adb shell am instrument -w -r …, sharding via -e numShards, Test Orchestrator wiring) — use ../../../adb/tests/running-instrumented-tests-via-adb/SKILL.md and ../../../adb/automation/scripting-adb-for-ci/SKILL.md.
  • The user is choosing the AndroidJUnit4 runner / writing the test class itself — use ../../runner/running-instrumented-tests-with-androidjunit4/SKILL.md.
  • The user wants Compose UI tests specifically — those still run as instrumented tests; GMD just hosts them. See ../../../compose/setup/setting-up-host-vs-device-tests/SKILL.md for host-vs-device choice first.

Prerequisites

  • Android Gradle Plugin with GMD support (the stable managedDevices DSL; older AGP exposed parts of it under android.testOptions.managedDevices experimentally — check the docs for your AGP version).
  • androidTest set up normally: testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner", androidTestImplementation dependencies. GMD changes where tests run, not how they are written.
  • Disk space and a working KVM/HAXM/hypervisor on the build machine (CI runners need KVM enabled) — GMD launches a real emulator.
  • License acceptance for the system images GMD downloads (sdkmanager --licenses, or accepted in CI setup).

Workflow

  • 1. Declare the managed devices. In the module's build.gradle.kts:
android {
    testOptions {
        managedDevices {
            localDevices {
                create("pixel6api34") {
                    device = "Pixel 6"          // a Device Manager profile name
                    apiLevel = 34
                    systemImageSource = "aosp"  // "aosp" | "google" | "google_apis_playstore" | "aosp-atd" | "google-atd"
                }
                create("pixel2api30") {
                    device = "Pixel 2"
                    apiLevel = 30
                    systemImageSource = "aosp-atd"  // ATD: stripped-down, headless, faster — good for CI
                }
            }
            groups {
                create("ciMatrix") {
                    targetDevices.add(localDevices["pixel6api34"])
                    targetDevices.add(localDevices["pixel2api30"])
                }
            }
        }
    }
}

Each create("name") { … } is a ManagedVirtualDevice: device is a Device Manager profile, apiLevel the system image API, systemImageSource which image family. Use require64Bit = true if you need to force the 64-bit image. A group bundles devices so one task runs the suite across all of them.

  • 2. Run the generated tasks. GMD synthesizes a task per device, per group, and an all-devices task:
./gradlew pixel6api34DebugAndroidTest          # androidTest on one managed device
./gradlew ciMatrixGroupDebugAndroidTest        # androidTest on every device in the "ciMatrix" group
./gradlew allDevicesCheck                      # androidTest on ALL managed devices defined in the project

Variant naming follows the build variant (…DebugAndroidTest, …ReleaseAndroidTest, flavor-prefixed if you have flavors). These tasks: download the system image if missing, boot a fresh emulator, install the app + test APKs, run the suite, write results, and tear the emulator down — no adb choreography from you.

  • 3. Read the results. Per-device HTML reports land under app/build/reports/androidTests/managedDevice/<deviceName>/ (and an aggregated report when running a group / allDevicesCheck); machine-readable results under app/build/outputs/androidTest-results/managedDevice/; anything your tests route through TestStorage / additional test output under app/build/outputs/managed_device_android_test_additional_output/<deviceName>/. Wire those paths into the CI artifact archive.

  • 4. Prefer ATD images for CI. Automated Test Device images (systemImageSource = "aosp-atd" or "google-atd") are pared down for headless test execution — no setup wizard, no UI niceties, smaller, faster to boot, lower memory. They keep the Google APIs you usually need for tests (the google-atd variant) without the Play Store. Use a full image only when a test genuinely needs Play services / Play Store behavior.

  • 5. Shard a slow suite across emulator copies. GMD can run the suite on N copies of a managed device in parallel, splitting tests across them, via the documented Gradle property (e.g. -Pandroid.experimental.androidTest.numManagedDeviceShards=N) and --max-concurrent-shards to cap how many run at once. Distribution is hash-bucketed by test name, same as am instrument -e numShards. Check the GMD docs for the exact property name on your AGP version before relying on it.

  • 6. CI wiring. A GMD task is a normal Gradle task; CI just needs KVM and accepted licenses:

# .github/workflows/instrumented-tests.yml
jobs:
  androidTest:
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: zulu, java-version: 17 }
      - name: Enable KVM
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules && sudo udevadm trigger --name-match=kvm
      - run: ./gradlew pixel2api30DebugAndroidTest   # ATD device declared above
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: androidTest-report
          path: |
            app/build/reports/androidTests/managedDevice/
            app/build/outputs/managed_device_android_test_additional_output/

The emulator is headless by default. (If you need to watch it locally — debugging a UI test — see the GMD docs for the option to show the emulator window; it is off in CI.)

Patterns

Pattern: using connectedAndroidTest in CI and managing the emulator by hand
# WRONG — boot an emulator with adb/avdmanager scripting, then connectedAndroidTest
- run: |
    avdmanager create avd -n ci -k 'system-images;android-30;default;x86_64'
    emulator -avd ci -no-window -no-snapshot &
    adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done'
    ./gradlew connectedDebugAndroidTest
# WRONG because: every repo reinvents this, the device definition is not in version control, and
# "works on my machine" diverges from CI. GMD makes the device a build input, provisioned identically everywhere.
// RIGHT — declare it once in build.gradle.kts; CI just runs the task
android { testOptions { managedDevices { localDevices { create("ciDevice") {
    device = "Pixel 2"; apiLevel = 30; systemImageSource = "aosp-atd"
} } } } }
// CI:  ./gradlew ciDeviceDebugAndroidTest
Pattern: full system image in CI when ATD would do
// WRONG
create("ciDevice") { device = "Pixel 6"; apiLevel = 34; systemImageSource = "google_apis_playstore" }
// WRONG because: the Play Store image is the heaviest one — slow to download, slow to boot, more RAM —
// and the test suite does not exercise Play Store behavior. CI minutes burn for nothing.
// RIGHT — ATD: headless, stripped, keeps the Google APIs tests usually need
create("ciDevice") { device = "Pixel 6"; apiLevel = 34; systemImageSource = "google-atd" }
Pattern: expecting GMD task $? to be the only signal
# WRONG — assume a green exit code means everything passed and stop there
./gradlew allDevicesCheck && echo "all good"
# WRONG because: Gradle does fail the build on test failures here (unlike raw `am instrument`), but a
# device that fails to provision, an OOM-killed emulator, or a flaky boot can also fail the task with
# nothing useful on stdout. Always archive app/build/reports/androidTests/managedDevice/ so a failure is diagnosable.
# RIGHT — keep the reports regardless of outcome
- run: ./gradlew allDevicesCheck
- uses: actions/upload-artifact@v4
  if: always()
  with: { name: androidTest-report, path: app/build/reports/androidTests/managedDevice/ }

Mandatory rules

  • MUST declare managed devices in android.testOptions.managedDevices in version control — the test device matrix is a build input, not a per-machine AVD.
  • MUST prefer ATD images (aosp-atd / google-atd) for CI; use a full or Play Store image only when a test needs GMS / Play Store behavior.
  • MUST enable KVM (or the platform hypervisor) on CI runners and accept SDK licenses before invoking a GMD task — otherwise the emulator never boots.
  • MUST archive app/build/reports/androidTests/managedDevice/ (and …/managed_device_android_test_additional_output/) on every run, pass or fail, so failures are diagnosable.
  • MUST NOT hand-script avdmanager/emulator/adb wait-for-device and then run connectedAndroidTest when GMD applies — GMD owns provisioning, boot, and teardown.
  • MUST NOT assume a managed-device task's exit code distinguishes test failures from infra failures; read the HTML report.
  • PREFERRED: group devices (groups { create("ciMatrix") { … } }) and run ciMatrixGroupDebugAndroidTest so the matrix is one task; use allDevicesCheck for the full sweep.
  • PREFERRED: shard slow suites with the documented numManagedDeviceShards property + --max-concurrent-shards rather than splitting the suite manually.

Verification

  • ./gradlew tasks --all | grep -i AndroidTest lists the generated <deviceName>DebugAndroidTest, <groupName>GroupDebugAndroidTest, and allDevicesCheck tasks.
  • ./gradlew <deviceName>DebugAndroidTest boots an emulator, runs androidTest, and produces app/build/reports/androidTests/managedDevice/<deviceName>/index.html.
  • The device definitions are in build.gradle.kts (committed), not relying on a local AVD.
  • CI runs a GMD task with KVM enabled and uploads the managed-device report directory with if: always().
  • CI devices use an ATD image (*-atd) unless a specific test documents why it needs a full/Play Store image.

References

  • developer.android.com/studio/test/gradle-managed-devices — the managedDevices / localDevices DSL, ManagedVirtualDevice (device, apiLevel, systemImageSource, require64Bit), device groups, the generated <device>…AndroidTest / <group>Group…AndroidTest / allDevicesCheck tasks, ATD images, sharding (numManagedDeviceShards, --max-concurrent-shards), report/output locations, and showing the emulator window.
  • developer.android.com/studio/test/advanced-test-setup — testOptions.animationsDisabled, Test Orchestrator, sharding context that also applies to GMD runs.
  • developer.android.com/training/testing/instrumented-tests — instrumented test fundamentals; GMD is one execution environment for them.
  • developer.android.com/tools/adb — adb and am instrument, for the lower-level alternative when GMD is not in play.
  • Sibling skill: ../../runner/running-instrumented-tests-with-androidjunit4/SKILL.md — the AndroidJUnit4 runner the tests use, regardless of where they execute.
  • Cross-set: ../../../adb/tests/running-instrumented-tests-via-adb/SKILL.md — running the same tests via adb shell am instrument -w -r without Gradle.
  • Cross-set: ../../../adb/automation/scripting-adb-for-ci/SKILL.md — CI bash idioms, sharding via -e numShards, Test Orchestrator wiring, capture-on-failure.
  • Cross-set: ../../../compose/preview/capturing-preview-screenshots-in-ci/SKILL.md — uses a managed device (or android-emulator-runner) to render @Preview screenshots in CI.

Comments

Markdown supported