toolkit/packages/skills/mobile-e2e-mcp/SKILL.md
End-to-end mobile testing of Expo/React Native apps via claude-in-mobile MCP + mcporter. Android emulator preferred (iOS needs WebDriverAgent). Covers full setup: emulator boot, Metro start, Firebase auth, MCP tool usage, tap/screenshot patterns. Use when asked to "test functionality" on a mobile app, "walk through the app", or run E2E validation of user journeys on an Expo/React Native project.
npx skillsauth add stevengonsalvez/agents-in-a-box mobile-e2e-mcpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Verified workflow for running functional (not render-only) tests on Expo/React Native apps using mcporter CLI + claude-in-mobile MCP server. Derived from a real debugging session on biolift/JSTLFT — 24/24 functional tests passed against real production Firebase on Android emulator.
mcporter CLI installed globally: npm install -g mcporterclaude-in-mobile MCP server registered in ~/.mcporter/mcporter.json:
{
"mcpServers": {
"mobile": {
"command": "npx",
"args": ["-y", "claude-in-mobile@latest"],
"description": "Native app testing via simulator/emulator (iOS + Android)"
}
}
}
Medium_Phone_API_35)~/Library/Android/sdk/platform-tools/adbtmux new-session -d -s emu
tmux send-keys -t emu "~/Library/Android/sdk/emulator/emulator -avd <avd-name> -no-snapshot-save -no-boot-anim" C-m
~/Library/Android/sdk/platform-tools/adb wait-for-device
~/Library/Android/sdk/platform-tools/adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; echo boot-completed'
Grep the repo for production config files: find . -name "*-config.json" -not -path "*/node_modules/*". Common names: firebase-applet-config.json, credentials.json, google-services.json. Copy credentials into mobile/.env.local (gitignored). Don't waste time with dummy Firebase keys — they get rejected with auth/api-key-not-valid at the Auth layer.
tmux new-session -d -s metro -c /path/to/mobile
tmux send-keys -t metro "EXPO_NO_TELEMETRY=1 npx expo start --android --clear" C-m
# Expo shows interactive auth prompt — answer it programmatically:
sleep 15
tmux send-keys -t metro Down C-m # selects "Proceed anonymously"
~/Library/Android/sdk/platform-tools/adb reverse tcp:8081 tcp:8081
Without this the emulator can't reach Metro and Expo Go shows "Something went wrong / Failed to download remote update".
mcporter call mobile.device_set --args '{"deviceId": "emulator-5554", "platform": "android"}'
On first launch, Expo Go shows a bottom sheet with "Continue" and an X. Tap the X at top-right (approx 966, 1740), not Continue — Continue advances to the Tools page instead of dismissing.
# Screenshot (always decode base64 via python)
mcporter call mobile.screen_capture --args '{"platform": "android"}' --output json > /tmp/mcp.json
python3 -c "
import json, base64
with open('/tmp/mcp.json') as f: d = json.load(f)
for item in d.get('content', []):
if item.get('type') == 'image':
with open('/tmp/shot.jpg', 'wb') as f: f.write(base64.b64decode(item['data']))
"
# Find elements precisely (never guess coordinates)
mcporter call mobile.ui_find --args '{"platform": "android", "text": "Get Started", "clickable": true}'
# Returns: [27] <ViewGroup> desc="Get Started" (clickable) @ (540, 1620)
# Tap at returned coordinates
mcporter call mobile.input_tap --args '{"platform": "android", "x": 540, "y": 1620}'
# Type into focused field
mcporter call mobile.input_text --args '{"platform": "android", "text": "chicken"}'
input_tapx = (1080/N)/2 + index*(1080/N)Production credentials (fastest if available):
*-config.jsonmobile/.env.localFirebase emulator:
firebase emulators:start --only auth,firestore --project <dummy-project>connectAuthEmulator / connectFirestoreEmulator wiring in the app10.0.2.2 as host loopback alias (not localhost)FIREBASE_USE_EMULATOR=true to gate the wiringMock auth bypass:
EXPO_PUBLIC_DEV_MOCK_AUTH env var check in your Auth providerUser object, pretend onboarding completedPick the first one that applies — production creds are almost always the simplest.
| Symptom | Cause | Fix |
|---|---|---|
| auth/api-key-not-valid | Dummy Firebase config in .env.local | Copy real creds from repo config file |
| "Failed to download remote update" | No adb reverse | adb reverse tcp:8081 tcp:8081 after both emulator + Metro are up |
| Metro halts at "Log in / Proceed anonymously" | Interactive prompt blocks auto-execution | tmux send-keys <session> Down C-m selects anonymous |
| Tap does nothing visible | Expo dev menu overlay absorbing taps | Tap the X at top-right, NOT "Continue" |
| Inspector mode activated accidentally | Tapped "Toggle element inspector" in Tools page | Reload app (send r to Metro tmux) |
| VirtualView*NativeComponent red screen | RN version mismatch with Expo SDK | npx expo install --fix to realign dep matrix |
| Cannot find native module 'ExponentAV' | expo-av removed in SDK 54+ | Migrate to expo-audio (createAudioPlayer, setAudioModeAsync) |
| expo-notifications: Android Push notifications removed | SDK 53+ removed expo-notifications from Android Expo Go | Create platform-split shim: notificationsShim.android.ts (noop stubs) + notificationsShim.ios.ts (re-export) + default notificationsShim.ts |
| MCP tools not available in Claude Code session | Added mid-session, schemas not loaded | Use mcporter call from shell — bypasses schema requirement |
| iOS tap returns "WDA not found" | WebDriverAgent not compiled + installed | Switch to Android. Don't go down the WDA rabbit hole unless iOS-specific testing is mandatory |
| mcporter config add --scope home mobile fails | Flag parser treats --scope as positional name | Edit ~/.mcporter/mcporter.json directly instead |
A great indicator that the full stack is working: the cross-tab integration test.
If this works, Firestore reads/writes are healthy and the app's realtime subscriptions are wired correctly.
ui_find every time. Coordinates drift between devices, orientations, and theme changes.adb shell pm list packages ... exited with non-zero code: 1. Wait for boot_completed first.r during an auth prompt session. Each reload re-triggers the auth prompt. Do all reloads after the prompt is already answered.This skill was derived from a real 2-hour debugging session testing the biolift (JSTLFT) mobile app on Android via mcporter. 24 functional tests passed end-to-end against real production Firebase, including:
Full test log: search the project for research/*functional-test-results.md.
documentation
Report reflect drain spend over a time window — tokens split by cached (cache_read), uncached writes (cache_creation), and io (input+output), with a $ estimate, grouped by day / outcome / model / transcript. Reads the drainer's cost log and surfaces outlier runs and cache-reuse health (the 41.5M-token failure mode = low cache reuse + high cache writes). Use to answer "what is reflection costing me" for the last day / week.
development
Show fleet status — every claude session running on the host, merged across ainb + claude-peers broker + background jobs. Use when you need to enumerate sessions before composing an action, see which sessions have a peer registered (broker-routable) vs tmux-only, check the `summary` of each session, or pipe the list into jq for filtering. Default output: text table. Pass --format json for LLM consumption.
testing
Ordered multi-step prompts to fleet targets, ack-gated between steps via JSONL assistant-turn-end detection. Use for cycles like disconnect→reconnect→verify, or any flow where step N+1 requires step N to have completed first. The skill BLOCKS until each target's transcript shows the next assistant turn finishing OR per-step timeout fires (default 300s).
development
Center control panel — enumerate every claude session that is blocked waiting on something: a user answer (AskUserQuestion fired), an API error retry, an idle assistant turn-end with no follow-up, or an explicit WAITING: marker. Returns rich JSON with signal kind + context per session. Use this when you've stepped away from the fleet and want one place to see everything that wants your attention and answer it.