Mobilerun Docs Reference
droidrun/mobilerun
Answers questions about Mobilerun, the LLM-agent framework for automating Android and iOS devices, by pointing the agent to the right page of its v5 documentation.
A skill your agent uses to wire adb reliably into CI — bash idioms, exit codes, parallel device fan-out with xargs -P, port forwarding (adb forward LOCAL REMOTE vs adb reverse REMOTE LOCAL —…
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ci --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .claude/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .claude/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ciType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ci --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .agents/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .agents/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ci --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .cursor/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .cursor/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/skydoves/android-testing-skills.git --path adb/automation/scripting-adb-for-ci--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ci --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .gemini/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .gemini/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ciInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .github/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .github/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install skydoves/android-testing-skills scripting-adb-for-ci --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/adb/automation/scripting-adb-for-ci .opencode/skills/scripting-adb-for-ci && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "scripting-adb-for-ci" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/adb/automation/scripting-adb-for-ci into .opencode/skills/scripting-adb-for-ci/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scripting-adb-for-ci", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
scripting-adb-for-ciA skill your agent uses to wire adb reliably into CI — bash idioms, exit codes, parallel device fan-out with xargs -P, port forwarding (adb forward LOCAL REMOTE vs adb reverse REMOTE LOCAL —…
Scripting Adb For CI is an agent skill from skydoves/android-testing-skills. Use this skill to wire adb reliably into CI — bash idioms, exit codes, parallel device fan-out with xargs -P, port forwarding (adb forward LOCAL REMOTE vs adb reverse REMOTE LOCAL — opposite argument order, the most common scripting bug), test-runner status codes (-1 error, -2 failure, -3 ignored, -4 assumption-failure), am instrument -w -r with --num-shards / --shard-index, the timeout wrapper (since adb -t is transport-id, NOT timeout), retry-on-transient-error with adb kill-server, idempotent setup (pm clear +…
Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering Mobile testing and debugging. It works with Bash and Android. The repository describes itself as: ⚡️ A set of skills for Android testing: Compose UI, AndroidX Test, JVM unit tests, and ADB. The licence is Apache-2.0.
Read from SKILL.md and the folder at commit 8665ed5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
adbbrewFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Scripting Adb For CI loads about 6.4k tokens when it runs. Until then it costs about 250 tokens; SKILL.md has 1,928 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from skydoves/android-testing-skills at commit 8665ed5, republished under its Apache-2.0 licence (© skydoves). 1,928 words, ~6,434 tokens.
.claude/skills/scripting-adb-for-ci/SKILL.md (or your agent's skills folder).This skill is the connective tissue that turns the other ADB skills into a CI pipeline: exit-code propagation, retries, timeouts, parallel device fan-out, port forwarding, idempotent setup, capture-on-failure, and Test Orchestrator wiring. Each section cross-links to the skill that owns the underlying primitive.
adb shell am instrument and "passes" even when tests fail — it gates on $?, but am instrument always exits 0; the real status lives in INSTRUMENTATION_STATUS_CODE lines on stdout (and -w is needed just to get that stdout at all).adb forward and adb reverse are confused — most teams have at least one bug from the opposite argument order.error: device not found / error: closed failures are not retried; the job fails on the first hiccup.settings put global ... survive into the next run.androidTestImplementation — the build silently fails to install the orchestrator APK.../../../compose/synchronization/synchronizing-with-idle/SKILL.md and friends.brew install coreutils for gtimeout if relying on timeout).adb shell exit codes are unreliable.__ADB_RC=$? in stdout and grep on the host.| Operation | Behavior |
|---|---|
adb push / adb pull | 0 on success, non-zero on transport or filesystem failure. Always check. |
adb install / adb uninstall | 0 on success. Modern adb returns non-zero on INSTALL_FAILED_*, but old versions exit 0 even on package-manager failure — verify by piping stderr. |
adb wait-for-device (and wait-for-usb-device, wait-for-local-recovery, etc.) | Block until the state is reached, then exit 0. Useful boot gate: `adb wait-for-device shell getprop sys.boot_completed |
adb shell <cmd> | On platform-tools ≥ 24 and device adbd ≥ Android 7.0 (Nougat / API 24), the device-side command's exit code is propagated. Older devices always returned 0 regardless. |
adb -t <transport-id> | Transport ID, NOT a timeout. Most common scripting confusion. |
am instrument | Always exits 0, pass or fail — AOSP Instrument.java ends run() with an unconditional System.exit(0) and AndroidJUnitRunner calls finish(Activity.RESULT_OK, …) regardless of test outcome. -w makes the shell wait for the runner so its stdout is complete; without -w the shell returns immediately with nothing usable. Either way, gate CI on parsed INSTRUMENTATION_STATUS_CODE lines (below), never on $?. |
adb shell exit-code propagation can be disabled with -x:
shell [-e ESCAPE] [-n] [-Tt] [-x] [COMMAND...]
-x: disable remote exit codes and stdout/stderr separationYou almost never want -x in CI. The default is what you want.
am instrument -w -r emits per-test INSTRUMENTATION_STATUS_CODE: <int> followed by a final INSTRUMENTATION_CODE: <int>:
| Code | Symbolic name | Meaning |
|---|---|---|
1 | REPORT_VALUE_RESULT_START | Test started |
0 | REPORT_VALUE_RESULT_OK | Test passed |
-1 | REPORT_VALUE_RESULT_ERROR | Unexpected error (process death, runner failure) |
-2 | REPORT_VALUE_RESULT_FAILURE | Assertion failed |
-3 | REPORT_VALUE_RESULT_IGNORED (AndroidJUnitRunner extension) | @Ignored / filtered out |
-4 | REPORT_VALUE_RESULT_ASSUMPTION_FAILURE (AndroidJUnitRunner extension) | JUnit assumeXxx returned false |
Don't treat -3 or -4 as failures — they are skips; only -1 and -2 are failures. The trailing INSTRUMENTATION_CODE line is unrelated: it's an Activity.RESULT_* value (-1 = RESULT_OK = no runner-level crash; 0 = RESULT_CANCELED = runner died) and, like the shell $? (always 0), it does not reflect test pass/fail. Grep stdout for per-test INSTRUMENTATION_STATUS_CODE: -1 / -2 (see the WRONG/RIGHT pattern below).
adb forward vs adb reverseThe argument order is opposite. This is the most common scripting bug.
adb forward [--no-rebind] LOCAL REMOTE # host → device. LOCAL REMOTE
adb reverse [--no-rebind] REMOTE LOCAL # device → host. REMOTE LOCAL| Direction | Use case | Recipe |
|---|---|---|
adb forward | Host needs to drive a device-side service. | adb forward tcp:9222 localabstract:chrome_devtools_remote |
adb reverse | Device needs to reach a local-only host service (CI runner, dev laptop). | adb reverse tcp:8080 tcp:8080 (Metro/local backend) |
Token grammar (from adb help):
| Token | Where |
|---|---|
tcp:<port> | Either side. tcp:0 on LOCAL = pick any free port; chosen port is printed to stdout. |
localabstract:<name> | Linux abstract Unix domain socket. Used by Chrome DevTools / Stetho / Flipper. |
localreserved:<name> | Reserved Unix socket namespace. |
localfilesystem:<name> | Filesystem-backed Unix socket. |
jdwp:<pid> | REMOTE only. Java Debug Wire Protocol for the given device-side pid. |
vsock:<CID>:<port> | REMOTE only. virtio-vsock (emulator). |
dev:<char-device> | Character device passthrough. |
acceptfd:<fd> | LOCAL only — listen on a pre-opened fd. |
Cleanup is the same surface: --list, --remove, --remove-all. Always remove what was added in a trap so re-runs on the same agent don't fail with "cannot bind: address already in use".
timeout, not adb -tadb -t <transport-id> is unrelated to timeout. The host has no per-command adb timeout flag. Wrap with the GNU/BSD timeout utility:
timeout 60s adb -s emulator-5554 shell am instrument -w -r ...
echo $?
# 124 = timeout reached (process killed); 137 = had to escalate to SIGKILL.
# 0 just means am instrument exited (it always does) — see the timeout pattern below for pass/fail parsing.On macOS timeout is gtimeout from coreutils (brew install coreutils).
For background captures, set a max duration so a stuck job doesn't fill the disk:
( timeout 600s adb logcat -v threadtime > artifacts/logcat.txt ) &
LOGCAT_PID=$!
trap 'kill $LOGCAT_PID 2>/dev/null' EXITThe usual offenders: error: device 'XYZ' not found, error: closed, error: protocol fault, error: device offline. A kill-server + reconnect resolves most.
retry() {
local n=0 max=3 delay=2
until "$@"; do
n=$((n+1))
[ "$n" -ge "$max" ] && return 1
echo "[retry] $* failed (attempt $n); restarting adb"
adb kill-server; adb start-server
sleep "$delay"
done
}
retry adb install -r app.apk
retry adb shell am instrument -w -r ...For am instrument retries specifically, also pm clear the test pkg first — otherwise transient state leaks across attempts. Note retry only re-runs on transport errors (the adb host binary exits non-zero); a genuine test failure leaves am instrument exiting 0, so it won't be retried — detect those by parsing the output, not by exit code.
adb devices \
| tail -n +2 \
| awk '$2=="device"{print $1}' \
| xargs -I {} -P 4 adb -s {} <command>Key fragments:
tail -n +2 skips the List of devices attached header.awk '$2=="device"' filters out unauthorized, offline, recovery, etc.-P 4 runs 4 in parallel; tune to physical USB-hub count.adb -s {} targets a serial. $ANDROID_SERIAL is the env-var equivalent inside the spawned shells.For tests with shared device-state (logcat clear, app install), serialise the prep then parallelise the run:
SERIALS=$(adb devices | awk '$2=="device"{print $1}')
echo "$SERIALS" | xargs -I {} -P 0 adb -s {} install -r app-debug.apk
echo "$SERIALS" | xargs -I {} -P 0 adb -s {} install -r app-debug-androidTest.apk
# Distribute shards across devices (one shard per device)
i=0
for S in $SERIALS; do
adb -s "$S" logcat -c
adb -s "$S" shell am instrument -w -r \
-e numShards $(echo "$SERIALS" | wc -l) -e shardIndex "$i" \
com.example.test/androidx.test.runner.AndroidJUnitRunner \
> "results-$S.txt" &
i=$((i+1))
done
wait
# am instrument always exits 0 — derive pass/fail from the captured output:
if grep -qE '^INSTRUMENTATION_STATUS_CODE: -[12]$' results-*.txt; then
echo "test failures detected"; exit 1
fi(xargs -P 0 runs as many concurrently as there are inputs.)
numShards / shardIndex"If you need to parallelize the execution of your tests ... use the
-e numShardsoption to specify the number of separate shards to create and the-e shardIndexoption to specify which shard to run." — AndroidJUnitRunner docs.
Distribution is hash-bucketed by test name — deterministic across runs given the same test set. Adding/removing tests reshuffles buckets.
Always namespace by serial:
artifact_dir() { echo "artifacts/${1//[:_]/-}"; }
for S in $(adb devices | awk '$2=="device"{print $1}'); do
D=$(artifact_dir "$S"); mkdir -p "$D"
adb -s "$S" logcat -d -v threadtime > "$D/logcat.txt"
adb -s "$S" shell screencap -p /sdcard/last.png
adb -s "$S" pull /sdcard/last.png "$D/last.png"
donetrapNever assume clean state from a previous run. Bake cleanup into setup, and use trap to enforce teardown.
adb wait-for-device
adb shell input keyevent 82 # wake; no-op if already on
adb shell pm clear com.example.app # wipe app data + cache
adb shell pm clear com.example.app.test # wipe test process state
adb shell am force-stop com.example.app
# Hermetic animations
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(See ../../control/injecting-input-and-state/SKILL.md for the full pm clear vs am force-stop reasoning.)
trap cleanupcleanup() {
adb shell am force-stop com.example.app || true
adb shell pm clear com.example.app || true
adb uninstall com.example.app || true
adb forward --remove tcp:6100 || true
adb reverse --remove-all || true
adb shell settings put global window_animation_scale 1 || true
adb shell settings put global transition_animation_scale 1 || true
adb shell settings put global animator_duration_scale 1 || true
}
trap cleanup EXITEach step is || true — partial-state cleanup must never mask the original failure exit code.
set +e
./gradlew connectedDebugAndroidTest
RC=$?
set -e
if [ "$RC" -ne 0 ]; then
mkdir -p artifacts
adb shell screencap -p /sdcard/fail.png
adb pull /sdcard/fail.png artifacts/fail.png
adb logcat -d -v threadtime > artifacts/logcat.txt
adb logcat -d -b crash -v threadtime > artifacts/crash.txt
adb logcat -d -b events -v descriptive > artifacts/events.txt
adb shell dumpsys activity > artifacts/dumpsys-activity.txt
fi
exit $RC(See ../../capture/capturing-screenshots-and-screenrecord/SKILL.md and ../../observability/extracting-logs-with-logcat/SKILL.md for the underlying primitives.)
"Android Test Orchestrator collects JUnit tests at the beginning of your test suite run, but it then executes each test separately, in its own instance of
Instrumentation." — developer.android.com.
Each @Test runs in its own Instrumentation invocation, so process state, statics, and Application singletons are reset between tests.
androidTestUtil, NOT androidTestImplementationandroid {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
}
dependencies {
androidTestImplementation 'androidx.test:runner:1.7.0'
androidTestUtil 'androidx.test:orchestrator:1.6.1' // NOT androidTestImplementation
}clearPackageData: 'true' makes the Orchestrator run pm clear <pkg> between tests — the strongest isolation knob.
adb install --force-queryable -r path/to/orchestrator-1.6.1.apk
adb install --force-queryable -r path/to/test-services-1.6.0.apk
adb shell 'CLASSPATH=$(pm path androidx.test.services) app_process / \
androidx.test.services.shellexecutor.ShellMain am instrument -w -e \
targetInstrumentation com.example.test/androidx.test.runner.AndroidJUnitRunner \
androidx.test.orchestrator/.AndroidTestOrchestrator'--force-queryable is needed on API 30+ so the Orchestrator can bindService into the target despite package-visibility rules.
Application; one crash doesn't kill the rest of the run; pm clear between tests is real, not "I cleaned up in @After".am instrument's exit code# WRONG
adb shell am instrument -w -r com.example.test/androidx.test.runner.AndroidJUnitRunner
RC=$?
[ "$RC" -eq 0 ] && echo "tests passed" || echo "tests failed (rc=$RC)"
# WRONG because: am instrument calls System.exit(0) (AOSP Instrument.java) and AndroidJUnitRunner's
# finish() reports RESULT_OK regardless. $? is 0 even when tests fail — with OR without -w.# RIGHT — keep -w (so stdout is complete) and -r (raw, parseable), then parse the stream
adb shell am instrument -w -r com.example.test/androidx.test.runner.AndroidJUnitRunner \
| tee instrument.log
# -1 = error, -2 = failure; -3 (ignored) and -4 (assumption failure) are skips, not failures.
if grep -qE '^INSTRUMENTATION_STATUS_CODE: -[12]$' instrument.log; then
echo "tests failed"; exit 1
fi
echo "tests passed"adb forward and adb reverse argument order# WRONG — copy/pasted the forward syntax for reverse
adb reverse tcp:8080 tcp:8080 # ambiguous (works because both are tcp:8080)
adb reverse tcp:9000 tcp:8080 # this means: device port 9000 → host port 8080
# Developer expected: host service on 8080 reachable as 9000 on device.
# WRONG because: reverse argument order is REMOTE LOCAL. The device tries to dial 8080 on
# the host, but the dev meant to expose the local 8080 to the device on port 9000.# RIGHT — adb reverse REMOTE LOCAL
# Expose host service on 8080 to the device as 9000:
adb reverse tcp:9000 tcp:8080
# In the app code: open a connection to localhost:9000 — it terminates at the host's :8080.
# Or, simpler, identical port:
adb reverse tcp:8080 tcp:8080 # device 8080 ⇆ host 8080androidTestImplementation for the orchestrator APK// WRONG
dependencies {
androidTestImplementation 'androidx.test:orchestrator:1.6.1'
}
// WRONG because: the orchestrator is a SEPARATE APK, not a library. androidTestImplementation
// merges it into the test APK's classpath, which AGP rejects (or silently ignores). The
// correct configuration is androidTestUtil, which AGP installs as an additional APK.// RIGHT
dependencies {
androidTestImplementation 'androidx.test:runner:1.7.0'
androidTestUtil 'androidx.test:orchestrator:1.6.1'
}timeout 600s adb shell am instrument -w -r \
com.example.test/androidx.test.runner.AndroidJUnitRunner | tee instrument.log
RC=${PIPESTATUS[0]} # exit of `timeout`, not `tee`
case "$RC" in
124) echo "timed out at 600s"; exit 124 ;;
137) echo "timed out and had to be SIGKILLed"; exit 137 ;;
esac
# RC is 0 whether tests passed or failed (am instrument always exits 0); pass/fail comes from the stream:
if grep -qE '^INSTRUMENTATION_STATUS_CODE: -[12]$' instrument.log; then
echo "failed"; exit 1
fi
echo "passed"retry adb -s "$SERIAL" install -r app-debug.apk
retry adb -s "$SERIAL" shell am instrument -w -r \
-e clearPackageData true \
com.example.test/androidx.test.runner.AndroidJUnitRunnerThe retry helper bounces the adb server on transient failure (see "Retries on transient errors" above).
SERIALS=( $(adb devices | awk '$2=="device"{print $1}') )
N=${#SERIALS[@]}
mkdir -p artifacts
pids=()
for i in "${!SERIALS[@]}"; do
S="${SERIALS[$i]}"
D="artifacts/${S//[:_]/-}"
mkdir -p "$D"
(
adb -s "$S" logcat -c
adb -s "$S" shell pm clear com.example.app
adb -s "$S" shell am instrument -w -r \
-e numShards "$N" -e shardIndex "$i" \
com.example.test/androidx.test.runner.AndroidJUnitRunner \
> "$D/results.txt"
# am instrument exits 0 regardless — pass/fail is in the captured stream.
if grep -qE '^INSTRUMENTATION_STATUS_CODE: -[12]$' "$D/results.txt"; then
adb -s "$S" shell screencap -p /sdcard/fail.png
adb -s "$S" pull /sdcard/fail.png "$D/fail.png"
adb -s "$S" logcat -d -v threadtime > "$D/logcat.txt"
exit 1
fi
) &
pids+=($!)
done
rc=0
for p in "${pids[@]}"; do wait "$p" || rc=1; done # fail the job if any shard failed
exit $rc-w -r to am instrument so the shell waits for the runner and emits raw, parseable output. Without -w the shell returns before any output is produced.$? from am instrument — it calls System.exit(0) regardless of test outcome (with or without -w). Detect failures by grepping stdout for INSTRUMENTATION_STATUS_CODE: -1 (error) and -2 (failure).-3 IGNORED and -4 ASSUMPTION_FAILURE from real failures when parsing INSTRUMENTATION_STATUS_CODE. -1 and -2 are the failure codes.adb forward is LOCAL REMOTE (host first) and adb reverse is REMOTE LOCAL (device first). The argument orders are opposite.timeout <N>s (or gtimeout on macOS). adb has no built-in timeout flag — adb -t is transport ID, not timeout.androidTestUtil("androidx.test:orchestrator:1.6.1"), not androidTestImplementation. The orchestrator is a separate APK, not a library.device not found, closed, protocol fault) with a bounded retry helper that calls adb kill-server; adb start-server between attempts.trap cleanup EXIT that removes port forwards (adb forward --remove-all, adb reverse --remove-all), restores animation scales, and pm clears the app under test. Each step || true so cleanup never masks the test exit code.pm clear <pkg> (not just am force-stop) before each test run. See ../../control/injecting-input-and-state/SKILL.md.adb shell exit-code propagation on devices below API 24. Use a sentinel __ADB_RC=$? in stdout for older targets, or pin minSdk for the test infra.adb -t <transport-id> with timeout. They share zero semantics.adb install to the same device race.clearPackageData: 'true' for hermetic isolation; pair with useTestStorageService: 'true' for routed artefacts (see ../../transfer/extracting-test-artifacts/SKILL.md).artifacts/${SERIAL//[:_]/-}/) so archive artifacts/**/* in CI captures everything cleanly.am instrument invocation in scripts/Gradle has -w -r, and no script branches on its $? as a pass/fail signal — failures are detected by parsing INSTRUMENTATION_STATUS_CODE: -1/-2 from stdout.adb forward and adb reverse appear with arguments in the documented order; CI passes a smoke test that the host can reach the device service and vice versa.androidTestUtil('androidx.test:orchestrator:...') (not androidTestImplementation) — ./gradlew :app:dependencies --configuration androidTestUtil shows the orchestrator.trap cleanup EXIT is installed in the top-level CI script.timeout.-3 and -4 as skips, not failures.adb kill-server; adb start-server between attempts on transient errors.forward / reverse syntax, transport, server.am instrument -w -r, -e numShards / -e shardIndex, AndroidJUnitRunner arguments, output dirs.REPORT_VALUE_RESULT_* constants (OK = 0, START = 1, ERROR = -1, FAILURE = -2).0 pass, 10 test failure, 15 indeterminate, 18 incompatible, 20 infra error).tasks/research/A3-adb-observability-automation.md — full bash idioms, exit-code propagation matrix, port-forward token grammar, parallel fan-out, retries, capture-on-failure, Test Orchestrator wiring.tasks/research/A2-adb-shell-commands.md — am instrument arguments, status codes, AndroidJUnitRunner -3/-4 extensions.../../architecture/understanding-adb-architecture/SKILL.md — server / daemon / adb kill-server.../../devices/connecting-to-devices/SKILL.md — device states, wait-for-device.../../devices/connecting-over-wifi/SKILL.md — adb pair / adb connect for headless CI.../../apps/installing-and-managing-apps/SKILL.md — pm install / pm uninstall / pm clear.../../tests/running-instrumented-tests-via-adb/SKILL.md — am instrument -w -r deep dive.../../control/injecting-input-and-state/SKILL.md — hermetic preamble (animations to 0, pm clear, am force-stop).../../capture/capturing-screenshots-and-screenrecord/SKILL.md — screencap / screenrecord capture-on-failure.../../observability/extracting-logs-with-logcat/SKILL.md — logcat -d capture-on-failure.../../transfer/extracting-test-artifacts/SKILL.md — pull / push / run-as / TestStorage.../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md — runner internals.../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md — Activity scenarios under instrumentation.../../../fundamentals/strategies/applying-testing-strategies/SKILL.md — when to invest in CI orchestration vs simpler tooling.© skydoves, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in adb/automation/scripting-adb-for-ci of skydoves/android-testing-skills.
Open the folder on GitHubat commit 8665ed5
Scripting Adb For CI next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Scripting Adb For CI this skillskydoves/android-testing-skills | 333 | — | ~6.4k | Automated safety check: Pass | Apache-2.0 | |
| Mobilerun Docs Referencedroidrun/mobilerun | 9.6k | — | ~943 | Automated safety check: Pass | MIT | |
| Appiumblokadaorg/blokada | 3.3k | — | ~3.5k | Automated safety check: Pass | MPL-2.0 | |
| Phoneagentrounak/PhoneAgent | 798 | — | ~2.2k | Automated safety check: Pass | MIT | |
| Phone HarnessShawnPana/phone-harness | 3.2k | — | ~4.3k | Automated safety check: Pass | MIT | |
| Simvynpranshuchittora/simvyn | 438 | — | ~938 | Automated safety check: Pass | MIT |
droidrun/mobilerun
Answers questions about Mobilerun, the LLM-agent framework for automating Android and iOS devices, by pointing the agent to the right page of its v5 documentation.
blokadaorg/blokada
A skill your agent uses for dynamic inspection and navigation of the Blokada app through the repo-local Appium machine session.
rounak/PhoneAgent
Control a connected iPhone, iOS simulator, Android emulator, or Android device from macOS through PhoneAgent's JSON-RPC bridge.
ShawnPana/phone-harness
Control the user's phone — an iPhone through the Mac's iPhone Mirroring window, an Android over adb, or a rented cloud Android: open apps, tap, type, swipe, read the screen.
pranshuchittora/simvyn
Operate iOS Simulators, Android Emulators, and connected mobile devices with Simvyn.
nuclearpasta/react-native-drax
Drives iOS and Android devices and simulators from the command line: open apps, snapshot the UI tree, tap, type, scroll, take screenshots and read UI info.
skydoves/android-testing-skills
A skill your agent uses to verify Compose layout measurements from a UI test using assertWidthIsEqualTo, assertHeightIsEqualTo, assertWidthIsAtLeast, assertHeightIsAtLeast…
skydoves/android-testing-skills
A skill your agent uses to verify a Compose semantics node's properties from a UI test using assertExists, assertDoesNotExist, assertIsDisplayed, assertIsNotDisplayed, assertIsDeactivated…
skydoves/android-testing-skills
A skill your agent uses to render every Jetpack Compose @Preview as a screenshot on a real Android device or emulator and publish a browsable HTML catalog from CI.
skydoves/android-testing-skills
A skill your agent uses to capture visual artefacts from a device for test failures, golden image generation, QA repro, and demo videos.
skydoves/android-testing-skills
A skill your agent uses to pick the correct Compose UI test entry point.
skydoves/android-testing-skills
A skill your agent uses to pick which behaviors to cover in an Android test suite using Google's five-category state vocabulary plus the explicit "what NOT to test" list from…
Categories
A skill your agent uses to wire adb reliably into CI — bash idioms, exit codes, parallel device fan-out with xargs -P, port forwarding (adb forward LOCAL REMOTE vs adb reverse REMOTE LOCAL —…. Scripting Adb For CI is an agent skill from skydoves/android-testing-skills.
Scripting Adb For CI fits situations like: wire adb reliably into CI — bash idioms; parallel device fan-out with xargs -P; port forwarding (adb forward LOCAL REMOTE vs adb reverse REMOTE LOCAL — opposite argument order; the most common scripting bug).
Run `npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a claude-code`. Or copy the skill folder (adb/automation/scripting-adb-for-ci in skydoves/android-testing-skills) into .claude/skills/scripting-adb-for-ci in your project. Claude Code loads it when a task matches its description.
Run `npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a codex`. Or copy the skill folder (adb/automation/scripting-adb-for-ci in skydoves/android-testing-skills) into .agents/skills/scripting-adb-for-ci in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add skydoves/android-testing-skills --skill scripting-adb-for-ci -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/scripting-adb-for-ci, .gemini/skills/scripting-adb-for-ci, .github/skills/scripting-adb-for-ci and .opencode/skills/scripting-adb-for-ci in your project.
Going by SKILL.md and its folder, Scripting Adb For CI needs the command-line tools its instructions call (adb and brew).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Scripting Adb For CI is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.4k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Scripting Adb For CI: Mobilerun Docs Reference (droidrun/mobilerun, 9.6k stars), Appium (blokadaorg/blokada, 3.3k stars), Phoneagent (rounak/PhoneAgent, 798 stars) and Phone Harness (ShawnPana/phone-harness, 3.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
skydoves (a GitHub user) maintains it in skydoves/android-testing-skills, which has 333 GitHub stars. The repository holds 50 skills in this directory. The repository was last updated on May 25, 2026.
Source: skydoves/android-testing-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.