mobile-e2e-mcp
Mobile E2E Testing via MCP
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.
When to use
- User wants functional validation of a mobile app (set logging, form submission, navigation + state persistence, etc.)
- Expo Go SDK 54+ or a custom dev build
- App uses Firebase (Auth + Firestore) or a similar cloud backend
- Target: Android emulator (strongly preferred) or iOS simulator (requires WDA setup — see Gotchas)
Prerequisites
mcporterCLI installed globally:npm install -g mcporterclaude-in-mobileMCP 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)" } } }- Android SDK with at least one AVD created (e.g.
Medium_Phone_API_35) - ADB in PATH or at
~/Library/Android/sdk/platform-tools/adb - Real credentials for the app's backend (or Firebase emulator, or a mock-auth code path)
Android workflow (recommended)
1. Boot emulator in tmux
tmux 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'
2. Configure app to use real backend
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.
3. Start Metro in tmux
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"
4. Port forward (critical)
~/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".
5. Set mcporter target device
mcporter call mobile.device_set --args '{"deviceId": "emulator-5554", "platform": "android"}'
6. Dismiss first-launch dev menu
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.
7. Screenshot + tap loop
# 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"}'
Screen coordinate reference
- Original resolution on Medium_Phone_API_35: 1080×2400 — use these in
input_tap - Screenshot preview display: 540×1200 (×2 scale factor)
- Bottom tab bar y ≈ 2310
- For N tabs across 1080 width: tab N center at
x = (1080/N)/2 + index*(1080/N)
Three ways to unblock Firebase auth
-
Production credentials (fastest if available):
- Grep repo for
*-config.json - Copy to
mobile/.env.local - Works for real auth + real Firestore
- Grep repo for
-
Firebase emulator:
firebase emulators:start --only auth,firestore --project <dummy-project>- Requires JDK 21+ (firebase-tools 15+)
- Requires
connectAuthEmulator/connectFirestoreEmulatorwiring in the app - Android emulator uses
10.0.2.2as host loopback alias (notlocalhost) - Set env flag like
FIREBASE_USE_EMULATOR=trueto gate the wiring
-
Mock auth bypass:
- Add an
EXPO_PUBLIC_DEV_MOCK_AUTHenv var check in your Auth provider - Skip real sign-in, inject a fake
Userobject, pretend onboarding completed - Fastest iteration but limited to UI-only testing (Firestore calls will still fail)
- Add an
Pick the first one that applies — production creds are almost always the simplest.
Common Gotchas
| 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 |
Firestore integration smoke test
A great indicator that the full stack is working: the cross-tab integration test.
- Complete a workout in the Workout tab (log sets → finish)
- Navigate to Progress tab
- Assert the PR count incremented by the number of new PRs you set
If this works, Firestore reads/writes are healthy and the app's realtime subscriptions are wired correctly.
Anti-patterns to avoid
- Guessing tap coordinates from screenshots. Use
ui_findevery time. Coordinates drift between devices, orientations, and theme changes. - Declaring "tested" on render. A screen can render perfectly and still have broken onClick handlers. Always tap through.
- Starting Metro before the emulator is ready. Metro crashes with
adb shell pm list packages ... exited with non-zero code: 1. Wait forboot_completedfirst. - Reloading via Expo CLI
rduring an auth prompt session. Each reload re-triggers the auth prompt. Do all reloads after the prompt is already answered. - Trying iOS first. WDA setup burns hours. Default to Android.
Evidence
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:
- Firebase Anonymous Auth (real production sign-in)
- Onboarding wizard (sport → position → program)
- Live workout execution with inline PR detection + rest timer
- Nutrition meal logging with macro ring updates
- Theme switch with AsyncStorage cross-screen persistence
- Cross-tab PR count verification
Full test log: search the project for research/*functional-test-results.md.
More from stevengonsalvez/agents-in-a-box
frontend-design
Frontend design skill for UI/UX implementation - generates distinctive, production-grade interfaces
31commit
Create well-formatted git commits for changes made during the session
14reflect
|
2plan-tdd
Create a test-driven development plan for the project
1oracle
Best practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
1gh-issue
Analyze and fix the specified GitHub issue
1