AutoMobile Manual Test Iteration
SkillDev toolsRun one AutoMobile manual-test iteration: from a start point (commit, milestone/tag, or date), rebuild ALL components, restart the daemon with the right flags, and verify that closed issues and merged PRs actually fix their bugs / deliver their specced features on current HEAD by exercising tool calls on an Android emulator and iOS simulator. Use when asked to retest landed work, verify a release, or manually test what changed.
Use AutoMobile Manual Test Iteration in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add AutoMobile Manual Test Iteration and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the AutoMobile Manual Test Iteration skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by kaeawc/auto-mobile in skills/manual-test/SKILL.md and read by Ahel’s review.
Verify that the work claimed done since a starting point is actually done on
the current HEAD of main — reproduce-then-confirm each bug fix, exercise each
specced feature end to end, and sweep the changed tool surface for regressions.
Drive a real Android emulator and iOS simulator. Ground every PASS in an observed
field or device-side ground truth, never the tool's self-reported success.
Device work is sequential — one device at a time (no parallelism yet). Do Phase A (Android) fully, then Phase B (iOS). Delegate breadth to one subagent at a time to conserve context; never let two actors drive devices at once.
Phase 0 — Scope from the start point
- Get the start point. Accept a commit SHA, a tag/milestone, or a date. If
none was given, ask for one (offer the last release tag as default:
git tag | sort -V | tail). Resolve it to a git ref<START>. - Enumerate landed work in
<START>..origin/main:- Merged PRs:
gh pr list --state merged --search "merged:>=<DATE>" --json number,title,closingIssuesReferences(or by commit range). - Closed issues:
gh issue list --state closed --search "closed:>=<DATE>" --json number,title,labels. - Map each to a type: bug-fix (reproduce → confirm fixed) or feature/spec (exercise → confirm the output/effect exists).
- Merged PRs:
- Scope the changed tool surface for regression risk:
git log --oneline <START>..HEAD | grep -viE "README test count badges|deps"andgit diff --stat <START>..HEAD -- src/. Map changed non-test source files to the MCP tools they implement (src/features/**,src/server/*Tools.ts,schemas/tool-definitions.json). - Note which items are runner-side (need an APK/runner rebuild — see Phase 1)
vs flag-gated (need
--embedded-sdk/--network-mockable— see Phase 2) vs blocked (need a physical iOS device or an on-sim SDK app — see Phase 3). - Produce a checklist:
item # | type | tool(s) | needs (rebuild/flag/device) | observable to check.
Phase 1 — Rebuild ALL necessary components
CRITICAL GOTCHA — stale dist masked by the version string. The daemon reports
0.0.x+g<HEAD>computed fromgit rev-parse HEADat startup, NOT from the compiled code. A dist built days ago will still print the current HEAD and look fresh. Never trust the version string. Verify freshness by theDaemon Build Identitybuild hash (changes when dist changes) and/ordist/src/index.jsmtime. Always rebuild.
- Sync git. Rebase this worktree on
origin/main, then fast-forward the main checkout the daemon runs from (~/kaeawc/auto-mobile,git pull --ff-only). The daemon's entry script is that checkout'sdist/src/index.js, and the MCP proxy must match its build — keep them on the same commit. - TS dist + schemas (always):
bun run buildthenbash scripts/update-tool-definitions.sh. Confirm the dist mtime moved and, for a specific fix, grep the compileddist/src/index.jsfor a token from the change. - Android ctrlproxy APK — rebuild if any
android/control-proxy/**(runner) changed. Runner-gated features (e.g. occlusionoccludedByViewId, new extractor fields) will NOT appear until the APK is re-cut, even with fresh TS:cd android && ./gradlew :control-proxy:assembleDebug→android/control-proxy/build/outputs/apk/debug/control-proxy-debug.apk. - iOS runner — rebuild if any
ios/control-proxy/**changed:scripts/ios/ctrl-proxy-build-for-testing.sh→/tmp/automobile-ctrl-proxy/Build/Products. - Playground SDK app — only if testing SDK features. Use the standard
Gradle output
android/playground/app/build/outputs/apk/debug/app-debug.apk. Do NOT useandroid/build/grit/**orandroid/build/gojvm/**variants — they are incomplete (missingandroidx.startupresources) and crash on launch withNoClassDefFoundError: androidx.startup.R$string.
Phase 2 — Restart the daemon with the right flags
Multi-worktree daemon churn. Other worktrees/sessions spawn competing daemons on the shared socket
/tmp/auto-mobile-daemon-501.sock. They cause build-skew rejects and the CLI's daemon auto-restart can replace your flag-configured daemon with a flagless one. Kill ALL daemons first and re-check for strays after starting yours. If a competing daemon keeps respawning, flag-gated (SDK) testing is BLOCKED: multi-worktree daemon churn — record it and move on rather than fighting it.
ps aux | grep 'index.js --daemon-mode' | grep -v grep | awk '{print $2}' | xargs -r kill -9; rm -f /tmp/auto-mobile-daemon-501.sock.- Start ONE daemon from the fresh dist with the env + flags the run needs:
-
AUTOMOBILE_CTRL_PROXY_APK_PATH=<fresh apk>to use the freshly-built Android runner (also uninstall+reinstall the APK on the emulator first for a runner fix); otherwiseAUTOMOBILE_SKIP_ACCESSIBILITY_DOWNLOAD_IF_INSTALLED=trueto keep the installed one and avoid the ~30s blocking download (#2590). -
Do NOT set
AUTOMOBILE_CTRL_PROXY_IOS_BUNDLE_PATH. It wants an.ipafile, andscripts/ios/ctrl-proxy-build-for-testing.shproduces no.ipa— only a derived-data tree. The failure mode depends on whether the runner service is already running and responding, which is not the same as "artifacts are cached".setup()short-circuits only on a live health probe (isRunning(),src/ctrlProxy/IOSCtrlProxyManager.ts:988-995, and theattemptedSetupreuse at:966which also re-probes viaisAvailable()); cached artifacts alone never short-circuit.- Service already running / responsive — the builder is never consulted, so the override is bypassed with no diagnostic and you attribute results to a local build that never ran.
- Anything else, including cached-but-not-running — setup reaches
needsRebuild()(src/ctrlProxy/IOSCtrlProxyManager.ts:1001), which returns true whenever an override is set (src/ctrlProxy/IosCtrlProxyBuilder.ts:393-396).build()then callsensureBundleDownloaded(), which throwsCtrlProxy bundle override is not a file(src/ctrlProxy/IosCtrlProxyBuilder.ts:741-742); that becomes a failed build result (:485-495) andsetup()returns the failure (src/ctrlProxy/IOSCtrlProxyManager.ts:1020-1028). There is no fallback to cached artifacts — CtrlProxy iOS setup fails loudly.
So: if iOS setup fails with
bundle override is not a file, unset the override; if it appears to work, a runner was already live — confirm which runner actually served the call (ref #4221). The build script writes to the default derived-data path (/tmp/automobile-ctrl-proxy), so no path env var is needed; only for a non-default location setAUTOMOBILE_CTRL_PROXY_IOS_DERIVED_DATA=<derived-data-root>(the root — the code appendsBuild/Productsitself). -
Serve a locally built iOS runner. Set
AUTOMOBILE_CTRL_PROXY_IOS_USE_LOCAL_BUILD=trueand pointAUTOMOBILE_CTRL_PROXY_IOS_DERIVED_DATA=<derived-data-root>at your build (omit the path variable for the default location). Local-build mode never downloads or extracts the released bundle, regardless of cache metadata,AUTOMOBILE_VERSION, or a vendored bundle override. It validates the local products before consulting release metadata, including during background prefetch. Do not setAUTOMOBILE_SKIP_CTRL_PROXY_DOWNLOADor pass--skip-ctrl-proxy-downloadfor this workflow: the skip flag returns earlier and bypasses product validation, pin capture, and prefetch.The daemon derives the expected SHA from your freshly built runner, pins it per platform, and re-verifies it before launch (a hash change with unchanged binary identity still fails closed) — no SHA to hand-copy. Rebuilding is picked up on the next launch without a daemon restart: the pin is re-derived when the binary's size or mtime changes, with an INFO log showing the old and new short SHA. It logs a loud WARN that the release-pinned guard is relaxed for the run. Missing or invalid local products fail with an actionable error naming the expected derived-data/products path and the platform-specific rebuild command. For simulators, use
AUTOMOBILE_CTRL_PROXY_IOS_DERIVED_DATA=<derived-data-root> bash scripts/ios/ctrl-proxy-build-for-testing.sh; physical devices require a device build with valid signing and provisioning. Background prefetch records this error without crashing the daemon.To enforce your own SHA, also set
AUTOMOBILE_CTRL_PROXY_IOS_RUNNER_SHA256=<64-hex>(andAUTOMOBILE_CTRL_PROXY_IOS_RUNNER_SHA256_TARGET=runner|xctestto pick the binary; defaults to the release's target). An explicit value overrides local-build mode for hash verification only; it never enables a release download in local-build mode. Unset it if you want auto-derivation.Non-local-build note:
AUTOMOBILE_SKIP_CTRL_PROXY_DOWNLOAD=true(or--skip-ctrl-proxy-download) suppresses release downloads and prefetch, but retains release-pinned runner verification. It is not the local-build setup. Caveat — it is process-wide, not iOS-only: it also suppresses the Android CtrlProxy download/install, so install the freshly built APK on the emulator yourself (adb install -r <fresh apk>) before starting the daemon, or run the Android leg in a separate daemon without the flag.If the daemon reuses a runner it did not launch, it logs a loud WARN (
Reusing an external CtrlProxy runner this daemon did not launch) — treat that as a signal to confirm which runner served the call. -
Verify which runner actually served the call —
grep xctestrun <daemon-log>for the path, andgrep 'need download\|Downloading CtrlProxy bundle' <daemon-log>to confirm the released bundle did not replace your build. Alsogrep 'Local-build mode' <daemon-log>to confirm your local runner's derived SHA was trusted, andgrep 'Reusing an external CtrlProxy runner' <daemon-log>to catch a stale/foreign runner silently serving. -
--embedded-sdk— required forsqlQuery,setPreference/getPreference, in-apphighlight(registration is daemon-side; the CLI must pass the same flag so the reuse check matches, else it restarts the daemon). -
--network-mockable— required formockNetwork/ network error-simulation. -
To test the gated-OFF assertion (criticalSection/executePlan hidden without debug), start WITHOUT
--debug/--embedded-sdk.
-
- Wait ~10-12s, then confirm exactly one daemon and that it carries your flags
(
ps -o command=).
MCP proxy build-skew → CLI fallback. After any daemon restart the connected MCP proxy is rejected by the build-skew guard (
client build != daemon build). In a fresh interactive Claude session the proxy auto-respawns from the current dist and MCP tools work again. On a shared multi-session machine it stays stale. Fallback: drive tools via the CLI — a fresh, build-matched client:bun /path/to/dist/src/index.js [--embedded-sdk --network-mockable] --cli <tool> --<param> <value>. Nested-object params must be JSON:--selector '{"text":"Settings"}'; booleans--raw true. The result JSON is the string atcontent[0].text(python3 -c "import json;print(json.loads(open('F').read())['content'][0]['text'])"). If output begins withRestarting daemon...a competing daemon caused churn — retry once; if persistent, mark the tool BLOCKED.
Phase 3 — Exercise tool calls (Android, then iOS)
Make the target device active and leave the other alone. For each checklist item:
- Bug-fix items: reproduce the original failure condition first, then
confirm it no longer reproduces. Capture the concrete observable AND device-side
ground truth — e.g.
adb shell cmd locale get-app-locales <pkg>for locale,adb -s <id> emu avd name+getprop sys.boot_completedfor startDevice correlation/readiness,dumpsys notificationfor postNotification, raw runner output for observe fields. A tool returningsuccess:trueis not proof. - Feature/spec items: exercise the new tool/param and assert the actual output
field or effect exists (e.g.
occludedByViewIdpopulated with a real node id;tapOn.indexselects distinct instances; per-app locale actually set). - Regression sweep: run the changed-surface tools (observe, tapOn, swipeOn, sendKeys, pressButton, dragAndDrop, pinchOn, rotate, launch/terminate, device state, navigation) and confirm well-formed output on the fresh runners.
Known blockers — record, don't fight:
- iOS in-app SDK features (sqlQuery/execute_sql, mockNetwork error-sim, in-app highlight) need an SDK-embedded app installed on the sim — none ships; BLOCKED unless you install one.
- Physical-device items (pressButton volume/power working-path, changeLocalization lockdown, get/setAppPermissions physical reset, shake-on-physical): BLOCKED when no physical iOS device is attached.
- SDK-flag tools under multi-worktree daemon churn (Phase 2).
Device gotchas:
- Never
pressButton poweron Android — it sleep-locks the emulator behind a keyguard. rotate: test on a landscape-capable screen; iPhone springboard/Settings are portrait-locked, which reads as a false "rotate broken".startDevice: expect readiness churn; theDisconnectMonitormay auto-restart a killed emulator; with ≥2 emulators running, sanity-check the returneddeviceIdagainstadb emu avd nameground truth.postNotificationon Android needs the SDK app foregrounded AND declaringPOST_NOTIFICATIONS(API 33+); the playground fixture declares no permissions, so end-to-end delivery is a fixture gap, not a tool bug.- iOS observe can transiently return an empty hierarchy right after a cold sim boot; retry once.
Phase 4 — Report, file, and confirm
- Write a per-item table:
Item # | Type | Platform | FIXED / PASS / NOT-FIXED / REGRESSED / BLOCKED | Evidence (the field checked). - File a GitHub issue for every regression or not-fixed item: exact repro, the
observed-vs-expected, root cause with
file:linewhere known, and a suggested fix. Reproduce before asserting; distinguish a real defect from a daemon-session/environment artifact. - Comment the verification result on each closed issue / merged PR you confirmed (fixed / not-fixed / blocked, with the evidence).
- Summarize: what's genuinely done, what regressed, what's still blocked and why, and any release-checklist items (e.g. re-cut the ctrlproxy APK / iOS runner so a runner-gated feature reaches users; version bump).
Output discipline
observe returns ~50KB. Never paste hierarchies — extract only the field that
proves the point (element counts, a specific value, a diff mode, ground-truth from
adb/simctl). When delegating a sweep to a subagent, require the same discipline and
a compact PASS/FAIL table back.
Signals
- GitHub stars
- 51
- Forks
- 7
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
manual-test- Source
- github.com/kaeawc/auto-mobile
Related picks
Skill · thedaviddias
The pick for JavaScriptmodern-javascript-patterns
Skill · wshobson
The pick for JavaScriptsetup-ts-deep-modules
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScripthandsontable-playwright-e2e
Skill · handsontable
The pick for End-to-end testingmstar-e2e
Skill · btspoony
The pick for End-to-end testing