Running Instrumented Tests Via Adb
Communityskydoves
Use this skill to run instrumented Android tests directly through `adb shell am instrument -w -r` without going through Gradle. Covers the required `-w` (wait — REQUIRED for meaningful exit codes) and `-r` (raw output) flags, the `-e` argument table (`class`, `class#method`, `package`, `size`, `numShards`/`shardIndex`, `debug`, `annotation` / `notAnnotation`, `listener`, `clearPackageData`, `targetInstrumentation`), the canonical runners `AndroidJUnitRunner` and `AndroidTestOrchestrator`, the orchestrator wrapping pattern (target = orchestrator, `-e targetInstrumentation <pkg>/<runner>`), and the output framing (`INSTRUMENTATION_STATUS_CODE` 1=start, 0=ok, -1=error, -2=failure, -3=ignored, -4=assumption-failure; `INSTRUMENTATION_RESULT`; `INSTRUMENTATION_CODE`). Use when the user mentions `am instrument`, `AndroidJUnitRunner`, "run tests from CI without Gradle", "Orchestrator", `clearPackageData`, `targetInstrumentation`, exit codes from `am instrument`, or `INSTRUMENTATION_STATUS_CODE`.
Overview
- Repository
- skydoves/android-testing-skills
- Publisher
- skydoves
- Stars
- 347
- Forks
- 14
- Bundled files
- 0
- License
- Apache-2.0
- Updated
- May 25, 2026
Installation
tmp=$(mktemp -d)
git clone --depth 1 https://github.com/skydoves/android-testing-skills.git "$tmp"
mkdir -p ~/.claude/skills
cp -R "$tmp/adb/tests/running-instrumented-tests-via-adb" ~/.claude/skills/running-instrumented-tests-via-adb
rm -rf "$tmp"tmp=$(mktemp -d)
git clone --depth 1 https://github.com/skydoves/android-testing-skills.git "$tmp"
mkdir -p ~/.codex/skills
cp -R "$tmp/adb/tests/running-instrumented-tests-via-adb" ~/.codex/skills/running-instrumented-tests-via-adb
rm -rf "$tmp"Install the Running Instrumented Tests Via Adb agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/tests/running-instrumented-tests-via-adb into this project's skills folder.Instructions
Running Instrumented Tests via ADB — am instrument without Gradle
adb shell am instrument is the underlying command Gradle invokes; running it directly is the right tool for CI scripts that already manage their own APKs, for sharding fan-out across many devices, and for tight-loop debugging of a single test method. The pitfall most CI scripts fall into: omitting -w, which makes the exit code meaningless. The second-most-common pitfall: flipping the orchestrator-target relationship.
When to use this skill
- The user wants a single test method to run from a script:
adb shell am instrument -w -r -e class com.example.MyTest#myMethod .... - The user wants to shard a test suite across N devices using
numShards/shardIndex. - The user is wiring AndroidX Test Orchestrator with
clearPackageData trueand gets the target/targetInstrumentationorder confused. - The user's CI script reports green when tests actually failed because
$?is0even thoughINSTRUMENTATION_STATUS_CODE: -2shows a failure. - The user wants the on-device runner to wait for a debugger attach before running tests.
When NOT to use this skill
- The user wants to install or reset the app under test — use
../../apps/installing-and-managing-apps/SKILL.md. - The user wants to choose a device, wait for boot, or set up Wi-Fi debugging — use
../../devices/connecting-to-devices/SKILL.mdand../../devices/connecting-over-wifi/SKILL.md. - The user is writing the JUnit4 /
AndroidJUnit4test class itself — use../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md. - The user wants Gradle to do the run for them (
./gradlew connectedDebugAndroidTest) — that path also reads instrumentation runner args fromtestInstrumentationRunnerArguments.
Prerequisites
- App APK installed (
com.example.app) and test APK installed (com.example.app.test) — see../../apps/installing-and-managing-apps/SKILL.md. - The test APK was built with
-tallowed (android:testOnly="true"). - For Test Orchestrator:
androidx.test.orchestratorAPK installed (typically viaandroidTestUtil("androidx.test:orchestrator:1.6.1")andadb install -r androidx.test.orchestrator.apk). - Device in
devicestate and animations disabled for stable runs (seedocs/CORPUS.md§I.6 hermetic test setup).
Workflow
1. Compose the canonical command. Verbatim from developer.android.com/studio/test/command-line and
am help:adb shell am instrument -w -r \ [-e <key> <value>] ... \ <pkg>/<runner>-w"Wait for instrumentation to finish before returning. Required for test runners." Without it, the shell returns immediately and$?is meaningless.-r"Print raw results (otherwise decodereport_key_streamresult)." Pair with-wfor CI-friendly output.
Common runners:
androidx.test.runner.AndroidJUnitRunner— the default for AGPandroidTest.androidx.test.orchestrator.AndroidTestOrchestrator— the wrapper that runs each test in its own instrumentation invocation.
Bare invocation against the AndroidJUnitRunner:
adb shell am instrument -w -r \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner2. Use the
-eargument table for selection, sharding, and runner behavior. The full table understood by AndroidJUnitRunner / AndroidX Test:Key Value Meaning package<java_package>"Fully qualified Java package name. Any test class using this package executes." Takes precedence over class. Comma-separate for multiple.class<fqcn>"Only this test case class executes." class<fqcn>#<method>"Only this method executes." Hash separator. class<fqcn1>,<fqcn2>#m,...Comma-separate to combine multiple selectors. sizesmall/medium/large"Runs test methods annotated with @SmallTest,@MediumTest, or@LargeTest."annotation<fqcn>Filter to tests carrying this annotation. notAnnotation<fqcn>Exclude tests carrying this annotation. numShards<N>Total shard count for parallel runs. shardIndex<i>This shard's 0-based index (0 ≤ i < N). debugtrue"Runs tests in debug mode." Waits for debugger attach before each test. logtrue"Loads and logs all specified tests but doesn't run them." Use to verify filter combinations. listener<fqcn>Register a RunListener. Comma-separate for multiples.filter<fqcn>Register a custom JUnit Filter.runnerBuilder<fqcn>Custom RunnerBuilder.disableAnalyticstrueDisable AndroidJUnitRunner usage stats. clearPackageDatatrueAndroidX Orchestrator only: pm clearbetween tests.targetInstrumentation<pkg>/<runner>AndroidX Orchestrator only: the wrapped instrumentation. coveragetrueCollect JaCoCo coverage. coverageFile<path>Override device coverage file location. Recipes:
# One class. adb shell am instrument -w -r \ -e class com.example.app.LoginTests \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # One method. adb shell am instrument -w -r \ -e class com.example.app.LoginTests#login_succeeds \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # @LargeTest only. adb shell am instrument -w -r -e size large \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # Shard 0 of 4 (run on 4 devices in parallel for 4× speedup). adb shell am instrument -w -r -e numShards 4 -e shardIndex 0 \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # Wait for debugger before each test. adb shell am instrument -w -e debug true \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # Custom RunListener. adb shell am instrument -w -r \ -e listener com.example.app.MyRunListener \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner3. Wire AndroidX Test Orchestrator correctly. The orchestrator runs each test in its own instrumentation invocation, isolating crashes and (with
clearPackageData) wiping app state between tests. To use it:- The target (last positional arg) is the orchestrator:
androidx.test.orchestrator/.AndroidTestOrchestrator. - The wrapped runner is passed via
-e targetInstrumentation <pkg>/<runner>.
adb shell am instrument -w -r \ -e clearPackageData true \ -e targetInstrumentation com.example.app.test/androidx.test.runner.AndroidJUnitRunner \ androidx.test.orchestrator/.AndroidTestOrchestratorCommon copy-paste mistake is to flip the two — putting the AndroidJUnitRunner as the positional argument and the orchestrator inside
targetInstrumentation. That fails with confusing errors because the orchestrator is the runner and the AndroidJUnitRunner is the target.- The target (last positional arg) is the orchestrator:
4. Read the output framing. Verbatim format:
INSTRUMENTATION_STATUS: <key>=<value> # repeated per Bundle entry per status frame INSTRUMENTATION_STATUS_CODE: <int> # one per status frame ... INSTRUMENTATION_RESULT: <key>=<value> # repeated per Bundle entry of the final result INSTRUMENTATION_CODE: <int> # final, exactly onceStatus code values (per
Instrumentation.REPORT_VALUE_RESULT_*plus AndroidJUnitRunner additions):Code Meaning 1Test started ( REPORT_VALUE_RESULT_START).0Test passed ( REPORT_VALUE_RESULT_OK).-1Process error / unexpected throw ( REPORT_VALUE_RESULT_ERROR).-2Assertion failure ( REPORT_VALUE_RESULT_FAILURE).-3Ignored ( @Ignore) — added by AndroidJUnitRunner.-4Assumption failure ( org.junit.AssumeViolatedException) — added by AndroidJUnitRunner.AndroidJUnitRunner adds
-3and-4on top of the framework's four canonical values; pure framework code only emits1,0,-1,-2for per-testINSTRUMENTATION_STATUS_CODE. The finalINSTRUMENTATION_CODEline is separate and uses Android'sActivity.RESULT_*constants —-1(RESULT_OK) when the run completes without a runner-level error and0(RESULT_CANCELED) when the runner itself errored. Neither this line nor the shell$?reliably reports test pass/fail: AndroidJUnitRunner callsfinish(Activity.RESULT_OK, results)regardless, and AOSPframeworks/base/cmds/am/.../Instrument.javaendsrun()with unconditionalSystem.exit(0)— so$?is0even when tests fail. CI scripts MUST parseINSTRUMENTATION_STATUS_CODE: -2(failure) andINSTRUMENTATION_STATUS_CODE: -1(error) lines from stdout to detect failures. See step 6 for the canonical grep idiom.Standard Bundle keys per status frame (from the
Instrumentationreference):REPORT_KEY_NAME_CLASS— current test FQCN.REPORT_KEY_NAME_TEST— current test method.REPORT_KEY_NUM_CURRENT— 1-based index of the current test.REPORT_KEY_NUM_TOTAL— total test count.REPORT_KEY_STACK— failure stack trace.REPORT_KEY_STREAMRESULT— pretty stream output (decoded by default; pass-rto keep raw).
5. Do NOT trust the shell exit code as a pass/fail signal. With
-w,$?reports0on overall pass AND on test failures —am instrumentends withSystem.exit(0)and AndroidJUnitRunner'sfinish()reportsRESULT_OKregardless of test outcome. Without-w, the shell returns immediately and$?is even less meaningful. MUST parse stdout forINSTRUMENTATION_STATUS_CODE: -2(failure) and-1(error) lines — see step 6.-w -ris still required (the wait + raw flags AGP/Gradle and Android Studio invoke with), just not for its exit code.6. Parse the output for CI. Two practical options:
- Greppable lines for fast pipelines:
adb shell am instrument -w -r ... \ | tee instrument.log grep -E '^INSTRUMENTATION_(CODE|STATUS_CODE|RESULT)' instrument.log grep '^INSTRUMENTATION_STATUS_CODE: -2' instrument.log && exit 1 - Let Gradle write the XML at
app/build/outputs/androidTest-results/connected/<variant>/TEST-*.xml— required when integrating with JUnit XML test reporters (most CI dashboards). Use./gradlew connectedDebugAndroidTestfor that path. The two approaches are not mutually exclusive; many pipelines runam instrumentdirectly for sharded execution, then post-process raw status streams into JUnit XML themselves.
- Greppable lines for fast pipelines:
7. Disable animations and reset state for hermetic runs. Per
docs/CORPUS.md§I.6:adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 adb shell pm clear com.example.app # before each run; orchestrator does this per-testOr via Gradle:
testOptions.animationsDisabled = trueand Test Orchestrator's-e clearPackageData true.8. Be aware of shell exit-code propagation limits.
adb shellexit-code propagation is reliable only since API 24 / Platform Tools 24. On older combinations, scripts that depend on$?fromadb shell <cmd>need to capture the value on-device (echo $? > /sdcard/exit) and pull it back. For modern devices and Platform Tools 24+,adb shell <cmd>; echo $?propagates the device-side exit code as expected — but note that foram instrumentthat code is always0regardless of test results (step 5), so propagation is irrelevant to pass/fail detection.
Patterns
Pattern: WRONG vs RIGHT — exit code without -w
# WRONG
adb shell am instrument -r \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
echo $?
# 0
# WRONG because: -w is omitted, so the adb shell returns immediately. $?
# reflects the launch, not the test outcome. Failures in the test run are
# silently lost.# RIGHT — gate on stdout, NOT on $?
output=$(adb shell am instrument -w -r \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner)
if echo "$output" | grep -qE 'INSTRUMENTATION_STATUS_CODE: -[12]$'; then
echo "FAILED"; exit 1
fi
# am instrument exits 0 even when tests fail (System.exit(0) in AOSP Instrument.java),
# so the only reliable signal is parsing INSTRUMENTATION_STATUS_CODE: -2 (failure) or -1 (error).Pattern: WRONG vs RIGHT — orchestrator target/targetInstrumentation flip
# WRONG
adb shell am instrument -w -r \
-e clearPackageData true \
-e targetInstrumentation androidx.test.orchestrator/.AndroidTestOrchestrator \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
# WRONG because: the orchestrator is the runner, not the target. AndroidJUnitRunner
# does not know how to invoke an "orchestrator target", and clearPackageData is
# not a recognised arg of AndroidJUnitRunner — only of the orchestrator.# RIGHT
adb shell am instrument -w -r \
-e clearPackageData true \
-e targetInstrumentation com.example.app.test/androidx.test.runner.AndroidJUnitRunner \
androidx.test.orchestrator/.AndroidTestOrchestrator
# Orchestrator is the runner (last positional arg).
# AndroidJUnitRunner is the wrapped target (passed via -e targetInstrumentation).Pattern: WRONG vs RIGHT — sharding across two devices
# WRONG
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 0 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 1 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
wait
# WRONG because: both shards run on the same device (-s emulator-5554). They
# serialize against each other through am, defeating the parallelism goal of
# sharding. Worse, they may collide on the same app under test.# RIGHT
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 0 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
adb -s emulator-5556 shell am instrument -w -r -e numShards 2 -e shardIndex 1 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
wait
# One shard per device. Each device runs roughly half the suite.Pattern: WRONG vs RIGHT — running a single method
# WRONG
adb shell am instrument -w -r -e class com.example.app.LoginTests:login_succeeds \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
# WRONG because: the separator between class and method is `#`, not `:`.
# AndroidJUnitRunner treats the whole string as a class name and finds nothing.# RIGHT
adb shell am instrument -w -r -e class com.example.app.LoginTests#login_succeeds \
com.example.app.test/androidx.test.runner.AndroidJUnitRunnerMandatory rules
- MUST pass
-wtoam instrumentfor any CI/script invocation so the shell waits for the runner to complete (necessary for stdout parsing). MUST NOT gate on$?—am instrumentcallsSystem.exit(0)regardless of test outcome. - MUST pair
-wwith-rfor CI consumption — raw output is parseable; the decodedreport_key_streamresultform is not. - MUST use
<class>#<method>(hash separator) for single-method selection. Colons or dots fail silently. - MUST put the orchestrator as the runner (positional last arg) and the AndroidJUnitRunner as the target via
-e targetInstrumentation— not the other way around. - MUST NOT rely on
am instrumentexit codes when-wis omitted. - MUST NOT assume
clearPackageData trueworks without Orchestrator — it is an Orchestrator-only argument. - PREFERRED: use Test Orchestrator +
clearPackageData truefor hermetic test isolation in CI; this maps to Gradle'stestOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR". - PREFERRED: disable animations (
settings put global *_animation_scale 0) before the run, or rely on Gradle'stestOptions.animationsDisabled = true.
Verification
- No script gates on
$?fromam instrument— it is always0(System.exit(0)in AOSPInstrument.java;finish(RESULT_OK, …)in AndroidJUnitRunner). Pass/fail is detected by grepping stdout forINSTRUMENTATION_STATUS_CODE: -1(error) /-2(failure). - Output stream contains exactly one
INSTRUMENTATION_CODE: <int>line at the end. - Per-test frames carry matching
INSTRUMENTATION_STATUS_CODE: 1(start) followed by0(ok) or-1/-2/-3/-4. - When using Orchestrator, the runner positional arg is
androidx.test.orchestrator/.AndroidTestOrchestratorand-e targetInstrumentationcarries the AndroidJUnitRunner FQN. - When sharding,
-e numShards N -e shardIndex iruns on distinct devices (-sdiffers per shard). -
-e class <fqcn>#<method>runs exactly one test method (cross-check withINSTRUMENTATION_STATUS: numtests=1). - Animation scales are
0.0on the device (adb shell settings get global window_animation_scale).
References
- Run tests from the command line (
am instrument): https://developer.android.com/studio/test/command-line - ADB user guide (
am instrumentflag table): https://developer.android.com/tools/adb#am - AndroidX Test Orchestrator: https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner
Instrumentationreference (report keys, status codes): https://developer.android.com/reference/android/app/Instrumentation- Advanced test setup (Gradle Managed Devices, sharding): https://developer.android.com/studio/test/advanced-test-setup
tasks/research/A2-adb-shell-commands.md— full-etable, status-code values including AndroidJUnitRunner's-3/-4, orchestrator wiring, exit-code-only-with--wrule.docs/CORPUS.md§I.5 (am instrumentinvocation), §I.6 (hermetic test setup), §I.10 (-wexit-code rule).- Sibling skills:
- High-level architecture:
../../architecture/understanding-adb-architecture/SKILL.md - Connect a device / wait for boot:
../../devices/connecting-to-devices/SKILL.md - Wireless ADB:
../../devices/connecting-over-wifi/SKILL.md - Install / clear / list apps:
../../apps/installing-and-managing-apps/SKILL.md
- High-level architecture:
- Cross-set neighbours:
- Run instrumented tests with
AndroidJUnit4:../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md - Configure JUnit4 on Android:
../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md - Source-set strategy:
../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md
- Run instrumented tests with
FAQ
What does Running Instrumented Tests Via Adb do?
Use this skill to run instrumented Android tests directly through `adb shell am instrument -w -r` without going through Gradle. Covers the required `-w` (wait — REQUIRED for meaningful exit codes) and `-r` (raw output) flags, the `-e` argument table (`class`, `class#method`, `package`, `size`, `numShards`/`shardIndex`, `debug`, `annotation` / `notAnnotation`, `listener`, `clearPackageData`, `targetInstrumentation`), the canonical runners `AndroidJUnitRunner` and `AndroidTestOrchestrator`, the orchestrator wrapping pattern (target = orchestrator, `-e targetInstrumentation <pkg>/<runner>`), and the output framing (`INSTRUMENTATION_STATUS_CODE` 1=start, 0=ok, -1=error, -2=failure, -3=ignored, -4=assumption-failure; `INSTRUMENTATION_RESULT`; `INSTRUMENTATION_CODE`). Use when the user mentions `am instrument`, `AndroidJUnitRunner`, "run tests from CI without Gradle", "Orchestrator", `clearPackageData`, `targetInstrumentation`, exit codes from `am instrument`, or `INSTRUMENTATION_STATUS_CODE`.
Who made Running Instrumented Tests Via Adb?
skydoves (a community member) publishes it in the skydoves/android-testing-skills repository on GitHub.
Is Running Instrumented Tests Via Adb free?
Yes. Running Instrumented Tests Via Adb is open source, published by skydoves under the Apache-2.0 license.
What comes with Running Instrumented Tests Via Adb?
Just its instructions. Everything the agent needs is in one SKILL.md file.
Which agents can use Running Instrumented Tests Via Adb?
Any coding agent that reads SKILL.md files, including Claude Code, Codex, Cursor, GitHub Copilot and Gemini CLI. The agent loads the skill when a task matches its description.
What are agent skills?
An agent skill is a folder with a SKILL.md file: instructions that teach a coding agent how to do one kind of task, and optionally files it can read while it works. The agent keeps only the skill's name and description in mind and reads the rest when a task calls for it.
How are agent skills different from MCP servers?
An MCP server gives an agent new tools to call, code that runs somewhere and returns results. A skill gives it knowledge: how to approach a task with the tools it already has. Many agents use both.
How can my agent test my Android app?
Robomori lets coding agents create, start and control Android emulators and devices from one command, so they can install your app, take screenshots and check dark mode, font sizes and languages.