Android testing with Espresso, UIAutomator, and Compose Testing; layered strategy, flake control, device matrix, CI integration, and ADB automation.
Android testing automation with Espresso, UI Automator, Compose Testing, screenshot tests, and adaptive UI validation.
Core References: Android Testing Docs, Build-Managed Devices, Compose Testing, UI Automator, Screenshot Testing, Accessibility Checking
| Task | Command |
|------|---------|
| List emulators | emulator -list-avds |
| Start emulator | emulator @<avd_name> |
| List devices | adb devices |
| Install APK | adb install -r <path-to-apk> |
| Run unit tests | ./gradlew test |
| Run instrumented tests (connected) | ./gradlew connectedAndroidTest |
| Run instrumented tests (GMD) | ./gradlew <device><variant>AndroidTest |
| Run screenshot tests | ./gradlew validateDebugScreenshotTest |
| List GMD tasks | ./gradlew tasks --all | rg -n "AndroidTest|Group|ManagedDevices" |
| Clear app data | adb shell pm clear <applicationId> |
| Component | Current stable | Notes |
|-----------|---------------|-------|
| Android | 17 (API 37), shipped 2026-06-16; Android 16 (API 36) is the prior release | See "Google Play Target API Policy" below for what to actually target — the newest OS version is not automatically the target-API requirement |
| AGP | 9.2.x | Requires Gradle 8.11+; breaking DSL changes from 8.x |
| Robolectric | 4.16.x | Supports up to SDK 36 (Baklava); SDK 36 requires JDK 21. API 37 support is not yet released as of 2026-07-11 — an open upstream issue (robolectric/robolectric#11239, filed 2026-06-14) tracks it. Do not assume Robolectric can simulate Android 17 behavior yet; verify before relying on it for API-37-specific logic |
| UI Automator | 2.4.0-rc01 | Modern uiAutomator {} DSL; release-candidate, not yet fully stable — verify current status before pinning in a template |
| Compose Preview Screenshot Testing | 0.0.1-alpha15 | Still alpha; requires AGP 8.5+, Kotlin 2.2.10+ (raised from 1.9.20 — verify against the current release notes before relying on an older Kotlin floor), JDK 17+ |
| ATD images | API 30 only | Use standard google/aosp images for API 35/36/37 |
| Maestro | API 35/36 added 2026 Q2 | API 37 support unverified as of 2026-07-11 — check release notes before targeting Android 17 devices in Maestro Cloud |
As of the 2026-08-31 deadline: new apps and app updates submitted to Google Play must target API level 36 (Android 16) or higher — submissions targeting lower are rejected in Play Console. Apps not updated at all must still target at least API level 35 (Android 15) or they become invisible/uninstallable for new users on newer OS versions. A one-time extension to 2026-11-01 is available by request. This is a moving deadline — re-check https://developer.android.com/google/play/requirements/target-sdk before treating any specific API number as "the" requirement, since Google raises it roughly once a year.
connectedAndroidTest for local ad-hoc runs.google or aosp images.clearPackageData for instrumented suites that need strong isolation.@Preview coverage, Paparazzi or Roborazzi for JVM rendering, device-based snapshots only when hardware fidelity matters.Recommended Gradle defaults for stable instrumented tests:
android {
testOptions {
animationsDisabled = true
execution = "ANDROIDX_TEST_ORCHESTRATOR"
emulatorSnapshots {
enableForTestFailures = true
maxSnapshotsForTestFailures = 2
}
}
}
dependencies {
androidTestUtil(libs.androidx.test.orchestrator)
}
If you rely on test isolation between instrumented tests, also set runner args such as clearPackageData=true in your Gradle or CI wiring.
| Layer | Framework | Scope |
|-------|-----------|-------|
| Unit | JUnit + Mockito | JVM, no Android |
| Unit (Android) | Robolectric | JVM, simulated framework |
| UI (Views) | Espresso | Instrumented |
| UI (Compose) | Compose Testing | Instrumented |
| Adaptive UI | Espresso Device API + DeviceConfigurationOverride | Instrumented or host-assisted |
| Screenshot | Compose Preview Screenshot Testing, Paparazzi, Roborazzi | JVM or instrumented |
| System | UI Automator | Cross-app, system UI, benchmarking drivers |
testOptions { animationsDisabled = true } for instrumented tests.waitUntil, or UI Automator conditions instead of sleeps.MockWebServer or DI fakes; avoid live backends in CI.withId() for Views, testTag for Compose, and resource-id or content descriptions for UI Automator.A checklist says "use emulators for CI, real devices for release." The judgment call is which real-device signals are worth paying for:
google images; anything asserting on hardware-rendered pixels needs a non-ATD image; everything else should default to ATD for speed.Generic "flaky test" triage wastes time re-running instead of classifying. Android UI-test flakiness clusters into a small number of root-cause families — identify which one you're looking at before reaching for retries:
NoMatchingViewException or an assertion firing against a stale loading state. Root cause is almost always an unregistered IdlingResource (Espresso) or a missing waitUntil/synchronized TestDispatcher (Compose/coroutines) — see references/espresso-patterns.md for the registration trap.animationsDisabled = true), never per-test with sleeps; ad-hoc adb shell settings put global *_scale 0 inside a test body is a workaround that silently stops working if the runner changes.assertExists()/onNodeWithText() finds nothing because Material components merge child semantics into the parent node by default. Not a timing issue at all; do not "fix" it with a wait loop. See the merged-tree trap in references/compose-testing.md.clearPackageData, not by reordering tests.System.currentTimeMillis(), or unseeded random data leaking into assertions or screenshot goldens. Always mockable; the fact that it's still happening usually means a fake wasn't reused when a new screen was added.When a test is flaky, name which of the seven it is before touching the test — the fix for #1 (register/await) will not touch #4 (isolate/clear state), and applying #2's fix (disable animations) to a #5 issue (Doze) does nothing.
The classic pyramid (many unit tests, some integration, few E2E) still holds, but Compose changes where the middle layer sits:
createComposeRule() without an Activity or device at all — this is functionally a unit test even though it "looks like" a UI test. Prefer it over createAndroidComposeRule<Activity>() whenever the composable doesn't need real navigation, DI graph, or activity lifecycle.StateFlow, not through the UI). A Compose app that only has "instrumented Compose tests that also exercise the ViewModel" has recreated the old inverted pyramid with new tools.Screenshot testing has real setup and maintenance cost (goldens go stale, false positives from font/renderer drift, review burden on every intentional UI change). It is worth that cost when:
It is a poor early investment for a small team still iterating rapidly on visual design — churn in intentional goldens will dominate signal from real regressions. Start with host-side tools (Compose Preview Screenshot Testing, Paparazzi, Roborazzi) for cheap iteration; only add device-based snapshot testing (Shot, or ATD-excluded device runs) once a specific rendering-sensitive surface (WebView, Maps, camera preview, custom Canvas/GPU work) has already caused a shipped visual bug that host-side rendering could not have caught.
references/espresso-patterns.mdreferences/compose-testing.mdreferences/uiautomator.mdreferences/screenshot-testing.mdreferences/adaptive-screen-testing.mdreferences/accessibility-checks.mdid, Compose Modifier.testTag, system resource-id or content description.Thread.sleep().connectedAndroidTest or a single managed-device task before widening the matrix.@Preview-driven Compose UI states.waitUntil, onElement, or watcher-based synchronization.build/reports/androidTests/, screenshot reports, diff images, logcat, and managed-device outputs.# Screenshot
adb exec-out screencap -p > screenshot.png
# Screen recording
adb shell screenrecord /sdcard/demo.mp4
# Pull managed-device test artifacts after a local failure
adb pull /sdcard/Android/media ./device-artifacts
Preferred: build-managed devices. See references/gradle-managed-devices.md and references/android-ci-optimization.md.
# .github/workflows/android.yml
name: Android CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6 # or later — verify at actions/checkout releases
- uses: actions/setup-java@v5 # or later
with:
java-version: '17' # or later
distribution: 'temurin'
- uses: gradle/actions/setup-gradle@v5 # or later
- run: ./gradlew testDebugUnitTest <device><api>DebugAndroidTest -Pandroid.testoptions.manageddevices.emulator.gpu=swiftshader_indirect
Android testing request
-> Classify layer: unit, Robolectric, Compose, Espresso, UI Automator, screenshot
-> Choose device/API matrix from risk, analytics, and adaptive UI needs
-> Stabilize state, idling, selectors, permissions, and test data
-> Run local targeted tests before managed-device or connected-device gates
-> Capture artifacts: logs, screenshots, videos, and test reports
-> Deflake root cause before expanding CI matrix or retries
The reference guides are intentionally large; search within them instead of loading everything:
rg -n "^## " frameworks/shared-skills/skills/qa-testing-android/references/compose-testing.mdrg -n "Idling|waitUntil|Synchronization" frameworks/shared-skills/skills/qa-testing-android/references/compose-testing.mdrg -n "DisplaySizeRule|DeviceConfigurationOverride|fold" frameworks/shared-skills/skills/qa-testing-android/references/adaptive-screen-testing.mdrg -n "PreviewTest|Paparazzi|Roborazzi|Shot" frameworks/shared-skills/skills/qa-testing-android/references/screenshot-testing.mdMainDispatcherRule + TestDispatcher (runTest { }, advanceUntilIdle(), kotlinx-coroutines-test) for every ViewModel and StateFlow test — Dispatchers.setMain(UnconfinedTestDispatcher()) hides off-main crashes that only surface in production@Serializable endpoint per data class, so R8 full-mode stripping of kotlinx-serialization $serializer classes is caught before release (see software-android-runtime-debugging/references/proguard-r8-triage.md)onNodeWithTag(...).assertTextEquals(...) — not on reference equality of UI state objects, because Strong Skipping Mode means the UI may or may not receive the same instance across emissionsThread.sleep() for synchronizationStateFlow<UiState> reference equality across emissions (assertThat(state).isSameInstanceAs(previous)) — new data class instances from copy() have different references but structurally equal content; test semantic equality, not identity| Resource | Purpose | |----------|---------| | references/espresso-patterns.md | Espresso matchers and actions | | references/compose-testing.md | Compose testing guide | | references/uiautomator.md | UI Automator patterns for system UI and benchmarking | | references/gradle-managed-devices.md | Managed device setup and CI | | references/screenshot-testing.md | Visual regression testing | | references/adaptive-screen-testing.md | Screen-size and foldable coverage | | references/accessibility-checks.md | Accessibility checks for Espresso and Compose | | references/test-orchestrator-patterns.md | AndroidX Test Orchestrator patterns | | references/android-ci-optimization.md | CI pipeline optimization | | references/modern-test-tooling.md | JU
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->npx skills add vasilyu1983/qa-testing-android下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer