Imported from podverse/podverse (
.cursor/skills/mobile-e2e-screenshots/SKILL.md). Install upstream withnpx skills add podverse/podverse --skill mobile-e2e-screenshots. Copyright stays with the author.
Mobile E2E screenshot reports
Use when you modify visual or interactive UI in apps/mobile/src/** or mobile E2E specs under
apps/mobile/e2e/**.
Mobile E2E uses Maestro (Track 5 lock) — not Playwright. Do not suggest web
make e2e_* targets; those are web and management-web only (see e2e-run-with-make-only rule).
Report layout (OS + form factor)
Each run writes a hub, a compact failures.json, plus slot summaries and per-flow pages:
.artifacts/mobile-e2e-reports/<timestamp>/
index.html # hub (fails callout + slot cards)
failures.json # machine index — preferred agent entrypoint
ios-phone/
index.html # slot summary (fails-first links)
flows/<slug>/index.html # one flow: error, steps, screenshots
flows/<slug>/failure.png # copy of primary ❌ shot when present
maestro.html # raw Maestro HTML (optional)
android-phone/...
ios-tablet/... # when `npm run mobile:e2e:test -- tablet` runs
android-tablet/...
latest symlinks to the newest timestamp. Flow pages keep web-parity screenshot chrome
(Prev/Next Shot | Test | Error). Slot index stays concise for scanning; do not dump full
Maestro accessibility hierarchies into HTML (raw commands-*.json remains for deep dive).
Do not collapse platforms into a single screenshot page. Open the slot / flow that failed.
Full suite vs focused verify
- Full suite (operator regression):
npm run mobile:e2e:test:all— discovers every top-levelapps/mobile/e2e/<area>.yaml(notshared/). Documented first in HOW-TO-RUN.md. Requires API-backed stack (mobile:dev:e2e+mobile:e2e:api+ deps/seed + E2E installs). - Feature / PR verify: keep using the narrowest
npm run mobile:e2e:test -- <area>(baremobile:e2e:test=hello-worldonly). Do not default agent verify endings to:all. - UI-only Metro symptom: API-backed /
:allruns withmobile:dev(notmobile:dev:e2e) show Network Error / “Could not sign in” / missingtab-home— the app still points at:3000.e2e-test.shfail-fasts when it can read Metro’s env andEXPO_PUBLIC_MOBILE_E2E=1is absent. Fix: restart Mobile Metro withnpm run mobile:dev:e2e, reload/reinstall, then re-run.
For first-failure debugging, isolate the device platform so one failing iOS flow does not trigger a wasted Android pass. Run the first flow on iOS, fix it until it passes, then run that same flow on Android before selecting the next flow:
npm run mobile:e2e:test -- --platform ios <area>
npm run mobile:e2e:test -- --platform android <area>
The default npm run mobile:e2e:test -- <area> remains the deliberate two-platform regression
run. Each platform-scoped run creates only its selected report slot.
Use --reset-data only when the flow requires an empty local SQLite database or prior app state is
contaminating the result; it is not the default for focused or full-suite verification.
When adding a new top-level Maestro flow
- Add
apps/mobile/e2e/<area>.yaml— it is auto-included inmobile:e2e:test:all. - If the flow needs
:4230when run alone, add<area>toflow_needs_e2e_apiinscripts/mobile/e2e-test.sh. - If the flow needs real media (
tools/test-assetson:2111), add<area>toflow_needs_test_assetsin the same script. - Keep HOW-TO-RUN.md § Run all as the operator entry for the
full process (prep + leave-running +
:all). Update that section if prep/stack steps change. - Keep FULL-REPO-VERIFICATION-COMMANDS.md synchronized whenever a top-level mobile flow is added or removed. Update its full-suite option, focused-flow command list, and tablet opt-in section when applicable.
Operator verification (mobile UI / feature work)
Same habit as web UI work (ui-e2e-screenshot-report): agents do not run E2E during implementation. For mobile feature/UI PRs, instruct the operator to generate slot reports:
- Narrowest Maestro flow under
apps/mobile/e2e/<area>.yaml(add/update when behavior changes). - Assume / point to HOW-TO-RUN.md and label tabs from
.vscode/terminals.json(vscode-terminals-commands rule):- UI-only: Mobile Metro (
mobile:dev) + Mobile iOS / Mobile Android installs + Mobile Maestro. - API-backed / full suite: Mobile one-shots (
make mobile_e2e_deps/mobile_e2e_seed), then leave-running Mobile Metro (mobile:dev:e2e) + Mobile E2E API, and for playback flows also Mobile E2E test-assets (mobile:e2e:test-assetson:2111), installs, then Mobile Maestro (mobile:e2e:test -- <area>ormobile:e2e:test:all).
- UI-only: Mobile Metro (
- Tell them where to review after they run:
- Failures index:
.artifacts/mobile-e2e-reports/latest/failures.json - Hub:
.artifacts/mobile-e2e-reports/latest/index.html - iOS phone:
.artifacts/mobile-e2e-reports/latest/ios-phone/index.html - Android phone:
.artifacts/mobile-e2e-reports/latest/android-phone/index.html
- Failures index:
Never chain leave-running processes in one verify bash block
npm run mobile:dev, npm run mobile:dev:e2e, npm run mobile:e2e:api, and
npm run mobile:e2e:test-assets block the shell.
Do not put them in the same fenced bash block as mobile:e2e:test (or other one-shot
commands) as if the operator can paste the whole list into one terminal. That forces Ctrl+C on Metro
(“Stopped server”) before later steps run.
Mobile E2E API is leave-running independently of Metro. Restarting Mobile Metro does not
require restarting the API if :4230 is already healthy. Auth/tab/api-health flows do need the API
up (see mobile-maestro-timeouts). Mobile E2E test-assets (:2111) is leave-running the same
way for playback flows (add-by-rss play).
Timeouts: prefer the shared TIMEOUT_* ladder (mobile-maestro-timeouts); default to the fastest
reasonable tier.
Final response bash blocks should contain only one-shot commands (prep Make targets if
needed, Maestro, open report paths). For leave-running Metro/API, name the tabs (Mobile Metro,
Mobile E2E API) in prose — do not paste blockers into the same verify block. Optional:
mobile:e2e:api:bg + mobile:e2e:api:health in Mobile when background API is intentional.
Agents: read reports when debugging
When mobile E2E fails (operator paste, CI artifact, or local .artifacts/mobile-e2e-reports/):
- Read
failures.jsonfirst (compact list of failing flows + paths). - Open only the listed flow HTML pages (
<slot>/flows/<slug>/index.html) and theirfailure.png/ ❌ screenshots. - Use slot
index.htmlfor a fails-first overview; use rawcommands-*.jsononly when hierarchy / deep step detail is required. - Use that evidence before suggesting unrelated fixes (Metro, wrong device, locator, Expo launcher still showing, etc.).
Expo Dev Client contract (required after launchApp)
launchApp with clearState: true:
- Lands on the Expo Dev Client launcher until Metro is entered.
- After the JS bundle loads, re-shows the one-time developer menu (“Continue”) because clearState resets Expo’s “seen menu” flag. That sheet occludes app UI.
Every Maestro flow that needs app UI must use runFlow: shared/launch-and-connect.yaml before
asserting app testIDs. That shared flow wraps launchApp + shared/connect-dev-client.yaml in a
Maestro retry (iOS mid-suite relaunches can blank out before “Development servers”). The connect
steps tap the Metro URL (iOS localhost / Android 10.0.2.2), tap Continue to dismiss the
onboarding card, then close the dev-menu bottom sheet it reveals (tapping the dimmed scrim above
the sheet), then wait for hello-world-screen (or the flow’s root once Home replaces that screen).
Tapping Continue alone is insufficient — it only opens the full dev menu (Reload / Go home / …),
which still occludes the app. Do not invent a parallel connect path that skips the sheet close.
The runner executes each flow once per platform by default. Set MOBILE_E2E_FLOW_RETRIES to a
positive number to opt into end-of-suite retries of only failed flow YAMLs. HTML reports prefer
the latest pass for a flow title when both failed and retry commands-*.json exist.
Maestro flow authoring gotchas (keyboard, secure input, silent failures)
These recur when a flow reaches a form (login/signup) and then stalls or fails on a post-submit assertion. Check them before blaming locators or timeouts:
-
hideKeyboardis flaky on iOS — it throwsCouldn't hide the keyboardbecause iOS has no guaranteed dismiss affordance. On iOS the primary button is usually visible above the keyboard, so you do not need it. Guard it to Android only:- runFlow: when: platform: Android commands: - hideKeyboardOn Android the soft keyboard often occludes the submit button after
inputText, so Android does need the dismiss. Prefer this platform-guarded form over an unconditionalhideKeyboard. -
secureTextEntryblocks MaestroinputText— iOS Strong-Password autofill over a secure field leaves the value empty. The app renders password fields as plaintext whenEXPO_PUBLIC_MOBILE_E2E=1(set byscripts/mobile/dev-e2e.sh); confirm the failure screenshot shows the typed value before deeper debugging. -
Silent submit = swallowed error in the screen, not a Maestro bug. If Submit is tapped but the screen stays put with no error text (and the assertion times out), the async handler likely threw and was swallowed. Every mobile async submit/handler must
catchand set a visible,testID-bearing error (see mobile-surface-async-errors rule). A visible error turns an opaque assertion timeout into a diagnosable message and lets the flow assert the failure directly. -
Reachability differs by platform. iOS simulator reaches the host at
localhost; Android emulator uses10.0.2.2. To isolate a network problem from a form/logic problem, run theapi-healthflow (it hits the same base URL with a GET): healtherroron one platform points at reachability/cleartext, not the form.
Response format
Mandatory for mobile UI / feature implementation responses (parity with web ui-e2e-screenshot-report / end-with-targeted-make-report-verify):
- End with a fenced
bashblock containing the most focused report command for the changed surface — prefernpm run mobile:e2e:test -- <area>mapped to the flow you added/updated. Use barenpm run mobile:e2e:testonly when the defaulthello-worldsmoke is truly the right scope. - Include where to open results (failures.json + hub + affected slots):
.artifacts/mobile-e2e-reports/latest/failures.json.artifacts/mobile-e2e-reports/latest/index.html.artifacts/mobile-e2e-reports/latest/ios-phone/index.html.artifacts/mobile-e2e-reports/latest/android-phone/index.html
- If Metro / E2E installs / API are not already assumed running, name the leave-running tabs
(Mobile Metro, Mobile E2E API, Mobile iOS / Mobile Android) in prose and link
HOW-TO-RUN.md — do not paste leave-running commands into the
final verification
bashblock.
Example ending (UI-only — Mobile Maestro; assume Mobile Metro + installs already up):
# Mobile Maestro
npm run mobile:e2e:test -- hello-world
open .artifacts/mobile-e2e-reports/latest/failures.json
open .artifacts/mobile-e2e-reports/latest/ios-phone/index.html
open .artifacts/mobile-e2e-reports/latest/android-phone/index.html
Example ending (API-backed — Mobile Maestro; assume Mobile Metro=mobile:dev:e2e and
Mobile E2E API already up):
# Mobile Maestro
npm run mobile:e2e:test -- api-health
open .artifacts/mobile-e2e-reports/latest/failures.json
open .artifacts/mobile-e2e-reports/latest/ios-phone/index.html
open .artifacts/mobile-e2e-reports/latest/android-phone/index.html
One-shot prep (Mobile) when the operator has not seeded yet:
# Mobile
make mobile_e2e_deps
make mobile_e2e_seed
Track 5.17–5.20 API+DB harness is complete. Auth login/logout Maestro remains Track 6
(210 / 211). See TEST-ENV.md.
When this skill does not apply
- Docs-only changes under
apps/mobile/*.mdwith no RN source. - Shared package-only changes with no mobile UI impact: follow response-ending-make-verify.
- Web or management-web UI: use ui-e2e-screenshot-report instead.
Related
- HOW-TO-RUN.md
- TEST-ENV.md — UI-only vs API-backed; harness 5.17–5.20
- APPS-MOBILE.md
- mobile-feature-requires-e2e rule
- mobile-master-plan-phasing — Track 5 E2E harness (5.17–5.20 complete)
- The completed harness outcome is captured in the mobile app E2E documentation; completed plan archives are removed after their outcomes are retained.