AGENT SKILL

e2e-maestro

Writes and runs Maestro E2E flows on simulator or emulator - pinned install, testIDs, debug links, no-network check, screenshot baselines. Use when adding a flow or e2e or screenshot runs fail. Not for Jest (unit-and-component-tests), pixel goldens (golden-tests) or parity (toybox-visual-parity).

License 许可
MIT
最近更新
2026-11-05
Stars
★ 0
Forks
0

01 前言 Front Matter

name
e2e-maestro
description
Writes and runs Maestro E2E flows on simulator or emulator - pinned install, testIDs, debug links, no-network check, screenshot baselines. Use when adding a flow or e2e or screenshot runs fail. Not for Jest (unit-and-component-tests), pixel goldens (golden-tests) or parity (toybox-visual-parity).

02 资源文件 500

03 SKILL.md 正文

End-to-end flows with Maestro

Every Pocket Arcade game proves its wiring on a real Release simulator build: Maestro 2.10.0 flows drive the Shell's journeys and the game's own level, the app is shown to make no network traffic, the simulator cold start, memory and 200 % text are measured in the same run, and every screen is captured in 4 languages × light/dark × phone/tablet against committed baselines. Android runs the same flows (e2e:android, screenshots:android). This skill sets up the tooling, writes and runs the flows; its scripts prove the set-up, the flows and each run's reports.

Rules that must hold

  1. Install Maestro only with packages/tooling/scripts/install-maestro.sh (2.10.0 zip from GitHub, SHA-256 29b675e1…d991 checked, into the ignored tools/maestro/), and run it with Java 17 and MAESTRO_CLI_NO_ANALYTICS, MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED, MAESTRO_DISABLE_UPDATE_CHECK set to true. Why: Homebrew and curl | bash are unpinned, and by default Maestro sends analytics and checks for updates.
  2. Run flows only through npm run e2e:ios -- --app <game-id>, against a Release simulator build of the test variant with ads off, on dedicated named simulators (e07-e2e-phone, e07-e2e-tablet, e07-shots-phone, e07-shots-tablet, or this session's e07-<purpose>), never booted or another agent's device. The runner refuses any other build (requireAdsOffTestBuild): with ads off the app never asks Google's consent form or Apple's tracking prompt. The evidence run has four steps: flows, cold start, memory (with the feedback evidence) and large text; --flows-only is for iterating and is never evidence. Android: npm run e2e:android on a test/off Release APK (readApkApp), on e07-android-* emulators. Why: debug builds talk to Metro and ads open sockets on purpose, which hides a real leak; other agents' simulators get broken.
  3. Every Maestro run names its simulator and its own driver port, before the command: maestro --device <udid> --driver-host-port <port> test ..., built in code with maestroGlobalArgs (packages/tooling/src/e2e/maestro-args.ts) and a free port per run (freeDriverPort(), or the session's --driver-port); never the per-command --udid alone and never the default port 7001. Every simctl call names the UDID. Android: every adb call starts -s <serial> (deviceAdbArgs, adbOn), every Maestro run androidMaestroGlobalArgs (android-serial). Why: in round 3 another session's XCTest driver answered a run that named this session's simulator.
  4. Select elements by id: only, with ids from the testID contract (assets/screen-testids.json, assets/e2e-testids.json, or the game's apps/<game-id>/e2e/testids.json). Text selectors are allowed only for OS-owned UI, under a # system-ui: <why> comment. Never select a crop-only part (the map's parent or a11yHidden entries, such as levels.level-tile.1.stars-3): Maestro lists only accessible elements, so select the coveredBy element and match its label with text:. Why: flows must run unchanged in en, de, fa and ckb, and an unknown or unreachable id should fail before a simulator waits 15 s for it.
  5. Start each flow with launchApp: { clearState: true } and set state only through subflows/debug-setup.yaml (the test build's <scheme>://debug/setup?... link, QUERY + WAIT_FOR; it waits for a screen root first, so one link right after the launch works, firstRun=0&...&screen=game included; it accepts Apple's "Open in “<app name>”?" alert guarded on that title, never a bare 'Open', which also matches an ad banner's "OPEN" button: check-flows rule open-alert-guard). Send action= in a link of its own once the run is on screen (it ends any active run: a level, today's daily or an endless run). Never openLink in a flow and never tap through menus to reach a state; the one tap allowed is the key under test on the screen the link opened (S9's Play key, Home's endless card, the Result's continue key, the level-2 tile). Why: setting up through the UI is slow and flaky, and it ties every flow to every screen.
  6. Every smoke flow ends with runFlow: <…>/subflows/assert-no-network.yaml, and the run's network.txt is empty. Android adds real airplane mode for the offline journeys and a sampler by uid; a one-platform step sits under when: platform: (platform-guard). Why: spec N3 says the app's own code never uses the network; iOS simulators have no airplane mode, so the JS guard counter (debug.network-attempts = 0) and the socket sampler are the offline proof.
  7. Never give a flow an env: block for APP_ID, APP_SCHEME, LANG or THEME. Why: in Maestro 2.10.0 the flow's env: value wins over -e (verified), so every run would silently use the flow's value.
  8. Flows live in <workspace>/e2e/flows/<area>/<nn>-<name>.yaml (Shell 01-09, games 10+), shared steps in packages/shell/e2e/subflows/, the matrix in packages/shell/e2e/screenshots/, and the premium-purchase skill's Tier 2 StoreKit flows (tag storekit, no clearState) in packages/shell/e2e/storekit/, which only its harness runs. Why: Maestro runs only the top-level files of a folder it is given, so a nested flow never runs and a sub-flow under flows/ runs on its own and fails.
  9. No AI commands and nothing that uploads (assertWithAI, assertNoDefectsWithAI, extractTextWithAI, maestro test --analyze, maestro cloud, maestro login, maestro record without --local). Why: they send screenshots or the run to a third-party service.
  10. A flaky flow is a bug: wait on ids (extendedWaitUntil), never sleep; no retry: around app steps; a non-smoke flow may be quarantined for at most 7 days with # quarantine YYYY-MM-DD: <reason>; a smoke flow is never quarantined. Why: retries and sleeps hide timing bugs players will hit.
  11. Screenshots come only from a still, deterministic screen: status bar 9:41, reduceMotion=1, ads off, seed 42 and date 2026-09-26, iPhone 17 Pro Max and iPad Pro 13-inch (M5) on iOS 26.5, tolerance 0.2% of pixels. Android: phone and tablet AVDs, animation scales 0, demo status bar 9:41, font_scale 2.0 for large text, the same 0.2%. Never raise the tolerance. Why: an animating screen differed by 1.08% between two captures one second apart (verified).
  12. Baselines change only on purpose: --update for the sets you mean, every new or changed PNG (and its diff) opened with the Read tool, then a commit with a Gate-Change: trailer. The same holds for perf-baselines/cold-start-sim-<game-id>.json and cold-start-emu- (--write-perf-baseline). Why: an unread baseline freezes a bug for every later run.
  13. The debug switches and the debug link reach services only through debug-services.ts: offline=0|1 flips the SimulatedConnectivity the whole app was built with, so every ConnectivityPort subscriber hears it; date=YYYY-MM-DD sets the SimulatedClock the whole app was built with (only today() moves); premium=0|1 writes the save, then dispatches debug-premium-set; geo=eea|other (setConsentGeography) resets Google's UMP answer (ads on only) and the composition root's consent port (services.consentFor(port)) asks UMP with that geography from the next consent moment on; a debug consent port comes from createConsentPort. Why: a flip only the debug screen sees leaves the store, the ads and S12 in the old state, and a UMP call in an ADS_MODE=off build fails the network check.
  14. One path for the link and S15: debug-link-handler.ts (handleUrl for links, apply and importSave for S15's tools). It listens to Linking from createDebugParts on and queues links until the navigator's onReady calls start(Linking); a save write that switches the navigator's group opens screen= on the next navigation state. The flags outside the save live in the test-only key-value store (debug.overrides, debug.pending-screen). Why: a link sent while nothing listened is lost (round 2: every first link after clearState), a screen opened before its group is mounted never opens, and a flag held only in memory is lost at the direction reload and after killApp.
  15. The level-1 flow proves the real board, then wins through the game host. One real move by taps (from print-level-line.ts), a kill and relaunch that resumes it, then action=win-level (GameHost.debugControls().playTo), asserting the stars the game's example win earns (result.stars-<n>, listed in the game's e2e/testids.json), then Levels shows those stars on levels.level-tile.1 and the level-2 tile opens level 2 (progress-after-win). Why: taps prove input and persistence; the host's one run-end path proves stars are saved like a real win, without a hand-typed 12-move line.
  16. Each game mode has its flow (check-flows rule mode-flows, read from game.config.ts): modes.daily plays today's daily and checks the streak the next day (journeys/11-daily.yaml), isContinueAllowed continues after a loss with the Premium continue (12-continue-premium.yaml; the rewarded continue is admob-ads' ads-on smoke test), modes.endless loses an endless run as an ads-off player without Premium (premium=0, never premium=1) and sees the endless result with New best at once, with no continue offer, then Home's best (13-endless.yaml; lead decision L11: never strand a finished run, hidden always means unavailable). Why: the daily, continue and endless paths have their own saves and screens that a level flow never reaches, and a run nothing can rescue must still end.
  17. The evidence run proves the win feedback was asked for: reports/e2e/<game-id>/feedback.json holds the win sound and the success haptic from the perf log (check-e2e-report rule feedback-evidence). Why: Jest fakes prove the wiring and the WAV preview the sound, but only this shows the simulator app asked for them; how they sound and feel stays the owner's own check (never waited for).
  18. The evidence run proves S15's save benchmark: smoke flow 04-debug-performance.yaml runs "Run save benchmark", the runner keeps the entry in save-benchmark.json, check-e2e-report (save-benchmark) needs 300 writes and p95 under 5 ms. Why: otherwise the budget is measured only in Jest.
  19. S15 keeps design parity (L12, L13): use-debug-model.ts opens debug-ads-always-test once on mount through the "Always show test ads" switch's handler, reading parityFrameState() from app/parity/parity-session.ts (never useParityOpener: an import loop through the gate), and formats its numbers in the language's digits (never String(n)); labels stay English. Why: the frame draws the switch on and Persian digits.
  20. admob-ads' packages/shell/e2e/ads-smoke/ flows run by hand on an ADS_MODE=test build started with xcrun simctl launch (Maestro's launchApp grants the tracking answer; Android: adb -s <serial> shell am start), geo= in their first link, Apple's prompt under when: platform: iOS; check-flows checks them, the runner never lists them. Why: they need ads on and the real prompts.

Workflow

  1. Check the set-up from the repo root: node ${CLAUDE_SKILL_DIR}/scripts/check-e2e-setup.mjs .. For each missing file copy the template of the same path from templates/ (the Files table below), with its test. The perf layer the cold-start, feedback and benchmark steps read comes from performance-budgets (JS half at Shell step 7, native half at step 8; rule perf-layer, runner reference). The debug kit is Shell core, installed with the composition root at Shell step 7 whatever the slice (not-yet-due SKIP lines before); only use-debug-model.ts and the sub-flows wait for S15 (SKIP lines while S15 is outside shell-slice.json). npm run e2e:ios -- --help lists every runner option (e2e-cli.ts). Add the npm scripts and the exact pins pixelmatch 7.2.0, pngjs 7.0.0 and @types/pngjs 6.0.5, and make .gitignore list /tools/ and reports/. Read references/maestro-setup.md and references/runner-and-network.md first, and when the install or the runner fails. Wire the kit as "Wiring" in references/debug-deep-link.md shows (createDebugParts(...) in the composition root, parts.feedback, services?.consentFor(port), DebugServicesProvider, the handler started from onReady). use-debug-model.ts is the hook toybox-screens' debug-screen.tsx renders; copy it after that screen and app/use-game-extra.ts exist. If neither /usr/libexec/java_home -v 17 nor Android Studio's JBR gives Java 17, stop and ask the owner to install a Java 17 JDK.
  2. Get the build: a Release simulator app of the test variant with ads off, npm run build:ios:sim -- --app <game-id> --variant test --ads off (building belongs to ios-simulator-build). The runner finds it in apps/<game-id>/build/dd/Build/Products/Release-iphonesimulator/.
  3. Write or change a flow: read references/flows.md once per session, and references/debug-deep-link.md for every QUERY parameter.
    • Shell journeys: copy from templates/packages/shell/e2e/flows/ (smoke/01-first-launch.yaml, journeys/02-core-journey-offline.yaml, rtl/03-language-switch.yaml, smoke/04-debug-performance.yaml) at Shell step 10, once every screen they reach is built (in a partial Shell check-flows prints SKIP ... [slice] for them).
    • A game: run node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON packages/tooling/src/e2e/print-level-line.ts --app <game-id> --level 1, then copy templates/apps/__GAME_ID__/e2e/flows/smoke/10-level-1.yaml to apps/<game-id>/e2e/flows/smoke/10-level-1.yaml and fill __GAME_ID__, __GAME_NAME__, the region ids, rows and columns of the printed first move and __WIN_STARS__ (the stars action=win-level earns, also printed). List result.stars-<n> with that reason in apps/<game-id>/e2e/testids.json. Without the host's debugControls yet, play the printed bot line by taps instead (flows reference, "The level-1 flow").
    • The game's mode flows: copy templates/apps/__GAME_ID__/e2e/flows/journeys/11-daily.yaml (when modes.daily), 12-continue-premium.yaml (when isContinueAllowed) and 13-endless.yaml (when modes.endless) and fill __GAME_ID__ and __GAME_NAME__ (flows reference, "A game's mode flows").
    • An id the game adds only for flows goes into apps/<game-id>/e2e/testids.json in the same change as the code that renders it. No : inside an evalScript (YAML reads a mapping): use arithmetic such as Number(cond).
  4. Check the flows without a simulator: node ${CLAUDE_SKILL_DIR}/scripts/check-flows.mjs . and fix every FAIL line (file, rule, fix). Once Maestro is installed, add --syntax (Maestro's own check-syntax, about 3 s per file). No flow yet: a SKIP ... [flows-missing] line while shell-slice.json marks a partial Shell, else a flows-missing failure.
  5. Run them: npm run e2e:ios -- --app <game-id> (add --sim <purpose> to use this session's own e07-<purpose> simulator, and e07-<purpose>-tablet for the iPad's large-text step; --driver-port <n> only when the session must hold one port; when a boot fails for lack of resources, shut down only your own e07-* simulators, never another session's): the flows (then save-benchmark.json), 6 cold launches against perf-baselines/cold-start-sim-<game-id>.json (the first run writes it: commit it with a Gate-Change: trailer), the game's smoke flow with feedback.json, a relaunch and footprint (≤ 150 MB), and the a11y flows at 200 % text in en and fa on phone and iPad. While iterating, -- --flows-only --include-tags smoke is fine; the evidence run has no filter. Then run node ${CLAUDE_SKILL_DIR}/scripts/check-e2e-report.mjs . --app <game-id>. Android (step 7): npm run e2e:android -- --app <game-id>, then --platform android (runner reference, "Android"). For a failure, open reports/e2e/<game-id>/ and rerun that flow alone; if it then passes it is flaky (flows reference, "Flaky flows and quarantine"). A line in network.txt is a spec N3 bug: find the code that opened it. Open every screenshot under reports/e2e/<game-id>/large-text/ with the Read tool (clipping, overlap, rows that should stack).
  6. Screenshot matrix (after any UI change and before a release): read references/screenshot-matrix.md. npm run screenshots:ios -- --app <game-id> --sim <purpose> (first time, or for an intended change: --update narrowed with --devices and --langs). Open every new or changed PNG and its diff with the Read tool: check direction, clipping, overlap, truncation and colours. Android: screenshots:android, --platform android. Then run node ${CLAUDE_SKILL_DIR}/scripts/check-e2e-report.mjs . --app <game-id> --screenshots and commit the baselines with Gate-Change: <which screens and why>. For a new or redesigned screen, run the design parity check (toybox-visual-parity) before accepting its baseline.
  7. Before a release: add the screenshot matrix at 200 % text (--text-size accessibility-extra-extra-extra-large, checked by eye) and run check-e2e-setup.mjs . --baselines (all 16 device/language/theme sets).
  8. Report and clean up: paste the evidence: lines that check-e2e-report.mjs prints (flows passed, quarantined, network, screenshots) into the report, with at most five gallery rows worth the owner's look. For a release, open reports/screenshots/index.html for the owner. Shut down the simulators the run created (xcrun simctl shutdown <udid>, e07-* only).
  9. Rerun the checks until each prints RESULT: PASS.

Definition of done

  • [ ] Every new flow is in flows/<area>/<nn>-<name>.yaml, selects by id, sets state through debug-setup.yaml, and was seen passing on the simulator.
  • [ ] The debug kit's and the tooling's Jest tests pass (npx jest packages/shell/src/screens/debug 'packages/shell/src/app/(create-)?debug-' packages/tooling/src/e2e test/integration/save/sqlite-kv-debug-store-adapter.test.ts --ci --selectProjects unit --coverage --collectCoverageFrom='packages/shell/src/screens/debug/**/*.ts' --coverageThreshold='{}'), a lang=fa&date=...&offline=1&screen=levels link lands on Levels after the reload with the date and offline flag still set, and one link after launchApp: { clearState: true } on a fresh install (firstRun=0&level=1&boardLayout=1&screen=game) opens Game.
  • [ ] npm run e2e:ios -- --app <game-id> passed with no tag filter and without --flows-only (13-endless included), no smoke flow is quarantined, and the large-text screenshots were read; check-e2e-report judges the rest.
  • [ ] Every Maestro line of the run log names this session's simulator and its own driver port (maestro: device <udid>, driver port <port>: ...).
  • [ ] After UI changes, npm run screenshots:ios ran, every changed PNG and its diff was opened, and every baseline change has a Gate-Change: trailer. The tolerance is unchanged.
  • [ ] Simulators the run created are shut down, and nothing ran on another agent's simulator.
  • [ ] node ${CLAUDE_SKILL_DIR}/scripts/check-e2e-setup.mjs . prints RESULT: PASS (with --baselines before a release).
  • [ ] node ${CLAUDE_SKILL_DIR}/scripts/check-flows.mjs . --syntax prints RESULT: PASS.
  • [ ] node ${CLAUDE_SKILL_DIR}/scripts/check-e2e-report.mjs . --app <game-id> prints RESULT: PASS (and with --platform android from Android step 7)

Anti-patterns

  • tapOn: 'Play' or assertVisible: 'Level 1'. It breaks in German, Persian and Kurdish, and it breaks again whenever copy changes. Use id: 'home.play-button'.
  • Tapping Settings → Language → … to reach a state. It is slow, and every screen change breaks unrelated flows. Use debug-setup.yaml with a QUERY.
  • xcrun simctl openurl for a debug link from a script. iOS answers a custom scheme opened from outside with an "Open in <app>?" prompt that nobody accepts, so the app never gets the link (and the queued prompt hides the app from the next flow). Launch the app and run debug-setup.yaml through Maestro (debugSetupArgs), as the cold-start step does; check-e2e-setup fails openurl in the tooling (openurl-prompt).
  • brew install maestro or the curl | bash installer. The version and checksum are unknown and analytics are on. Run install-maestro.sh (the runner calls it anyway).
  • Running on the booted simulator or a Debug build. You may break another agent's device, and Metro's WebSocket makes the network check meaningless. Use the runner with a Release test build.
  • An env: block with APP_ID "so the flow runs on its own". It overrides the runner's -e in 2.10.0. Pass -e APP_ID=… -e APP_SCHEME=… when running a flow alone.
  • retry: or a longer timeout to make a red flow green. That hides a race the player will hit. Wait on the id that proves the state, or fix the app.
  • --update for every device and language, or a bigger MAX_DIFF_RATIO, to clear a diff. That accepts regressions nobody looked at. Open the diff, fix the screen, and update only the sets you meant to change.
  • assertNoDefectsWithAI for "visual QA". It uploads screenshots. Open the PNGs with the Read tool instead.
  • A "Simulate offline" switch that only changes what isOnline() returns, or a debug Premium switch that dispatches premium-granted. Subscribers never hear the flip, and the save and the store disagree. Use debugServices.setOffline and setPremium.
  • --write-perf-baseline to clear a slower cold start. That hides the regression. Find the start-up work that grew; a slower baseline needs the owner.
  • A shared sub-flow saved under flows/. Maestro runs it on its own and it fails. Shared steps belong in packages/shell/e2e/subflows/.
  • A debug flag kept in a module variable or React state. The direction reload and killApp drop it; keep it in debug-services.ts, which writes debug.overrides at once.
  • Reading a debug flag, the simulated day or a log straight from its service in S15's model. An app build's React Compiler keeps the first value (verified: the offline switch stayed off, the perf summary stayed "empty"). Read them through readDebugState(..., version).
  • A guard or a tap on a bare 'Open'. It also matches an ad banner's "OPEN" button; guard on Apple's alert title 'Open in .*\?' (open-alert-guard).
  • Tapping a row right after scrollUntilVisible. A tap while the list still glides only stops the scroll; add waitForAnimationToEnd first.
  • A second debug path for S15 (its own save writes, navigation.navigate to a jump target, Alert prompts). Send the tool's request through links.apply(request); ESLint bans Alert, so S15 uses the system action and share sheets.

Files in this skill

File What it is Read/run when
references/maestro-setup.md Pins, the installer, the no-telemetry environment, verified and banned commands, check-syntax, moving the pin Workflow step 1, or when an install or command fails
references/runner-and-network.md What the run needs, what the runner does, simulator helpers, layer F (JS guard + socket sampler), reading the reports Workflow steps 1-2 and 5
references/flows.md Folders, names, header and tags, id selectors, debug setup, the sub-flows, Shell and game flows, board taps, waits, quarantine Workflow step 3
references/debug-deep-link.md The debug/setup parameters, what the handler does in order, what survives the reload, the debug services, the wiring, S15's model hook, game.board-layout, debug.network-attempts Workflow steps 1 and 3, for every QUERY
references/screenshot-matrix.md The 176-shot matrix, still screens, running, baselines, review, gallery, the 200% pass Workflow steps 6-7
templates/packages/tooling/scripts/install-maestro.sh Pinned, checksum-verified, idempotent Maestro installer Copy in step 1
templates/packages/tooling/src/e2e/run-e2e-ios.ts npm run e2e:ios: flows (then S15's save benchmark), cold start, memory and large text under the socket sampler; JUnit, network.txt, save-benchmark.json, perf.json Copy in step 1
templates/packages/tooling/src/e2e/e2e-cli.ts The runner's command line, pure, on node:util parseArgs: --help with every option, usage errors (exit 2, no stack), driverPortOf (the driver port range maestroGlobalArgs accepts, shared with screenshots-cli.ts), readOptions (the token loop android-cli.ts shares) Copy in step 1
templates/packages/tooling/src/e2e/e2e-cli.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/flow-files.ts What e2e:ios and e2e:android share: allFlows and largeTextFlows (the top-level flows, listed one by one), reportArgs (the JUnit report) and mergeJunitInto (a later call's test cases join the step's junit.xml) Copy in step 1
templates/packages/tooling/src/e2e/flow-files.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/save-benchmark-evidence.ts The newest save-benchmark entry of the perf log; save-benchmark.json's text; flowCallsOf, the flows step's two Maestro calls (the benchmark flow alone, after the others) Copy in step 1
templates/packages/tooling/src/e2e/save-benchmark-evidence.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/junit-merge.ts mergeJunitReports: the benchmark call's test case joins the flows step's junit.xml (one Maestro report) Copy in step 1
templates/packages/tooling/src/e2e/junit-merge.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/sampler-evidence.ts The end of the run: trackSampler (how a sampler ended) and proveSamplersAlive (two reads of each status, the verdict in network-samplers.json, a failure line per dead sampler) Copy in step 1
templates/packages/tooling/src/e2e/sampler-evidence.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/sim-perf-steps.ts The cold-start and memory steps (perf log via sqlite3, the data container looked up once per step; smoke flows, relaunch, 10 s settle, footprint; the committed baseline) Copy in step 1
templates/packages/tooling/src/e2e/sim-perf-steps.test.ts Its test: the app is relaunched before footprint, never measured after a failed flow Copied with it
templates/packages/tooling/src/e2e/sim-perf.ts Pure judges: cold-start median vs baseline × 1.2, phys_footprint vs 150 MB, budgets from quality-gates.json Copy in step 1
templates/packages/tooling/src/e2e/sim-perf.test.ts Its Jest test (synced from the library) Copied with it
templates/packages/tooling/src/e2e/maestro-args.ts maestroGlobalArgs (--device <udid> --driver-host-port <port> before the command), freeDriverPort, driverPortFor, maestroRunLine (synced from the library, identical to ios-simulator-build's copy; do not edit here) Copy in step 1
templates/packages/tooling/src/e2e/maestro-args.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/feedback-evidence.ts Splits the perf log's feedback entries into sounds and haptic cues; feedback.json's text Copy in step 1
templates/packages/tooling/src/e2e/feedback-evidence.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/simulator.ts Dedicated simulators on ios-simulator-build's simulator layer (e2eSimctl: simctl under the pinned Xcode; startSimulator, waitForBoot; --sim: e2eSimulatorName, e2eTabletName; a full Mac: bootFailureMessage), its status bar (statusBarArgs), text size, appearance, launch/terminate, a debug link through debug-setup.yaml (debugSetupArgs), the app's pid in any bundle folder and its data folder, maestroEnv(), runMaestro(device, args) (device and a driver port of its own first), findSimulatorBuild (the one build lookup of every E2E run and capture, which refuses an ads-on build: requireAdsOffTestBuild) Copy in step 1
templates/packages/tooling/src/e2e/simulator.test.ts Its test (the pure helpers) Copied with it
templates/packages/tooling/src/e2e/print-level-line.ts Prints a level's first move and the bot's line as tap targets, and the stars action=win-level earns Step 3, per game
templates/packages/tooling/src/e2e/level-line.ts Its pure logic (tapsFor through the game's intentToMove, the bot's line from game-kit's playBotLine, starsFor) Copied with it
templates/packages/tooling/src/e2e/level-line.test.ts Its test over a one-tap and a tap-then-tap game Copied with it
templates/packages/tooling/src/e2e/capture-screenshots-ios.ts npm run screenshots:ios: capture, compare, always summary.json and the gallery (a failed combination is listed, exit 1; a bad command line or a missing build, exit 2 without a stack) Copy in step 1
templates/packages/tooling/src/e2e/screenshot-compare.ts The compare core both capture scripts share: MAX_DIFF_RATIO (0.2 %), combosOf, jobOf, rawDirOf, checkShot, each platform's folders (ShotLayout) Copy in step 1
templates/packages/tooling/src/e2e/screenshot-compare.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/screenshot-matrix.ts The matrix to its end: one job per combination, a failed one becomes a capture-failed row and the next still runs; the counts, the failure list and the exit code (matrixOutcome) Copy in step 1
templates/packages/tooling/src/e2e/screenshot-matrix.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/screenshots-cli.ts Its command line, pure: --help, usage errors, --sim <purpose> (this session's own simulators) Copy in step 1
templates/packages/tooling/src/e2e/screenshots-cli.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/write-gallery.ts The owner's baseline / this run / diff gallery (reports/screenshots/, or reports/screenshots-android/ for screenshots:android) Copy in step 1
templates/packages/tooling/src/e2e/run-e2e-android.ts npm run e2e:android: the same four steps on e07-android-* emulators under one socket sampler per emulator; writes reports/e2e-android/<game-id>/ Copy at Android step 7
templates/packages/tooling/src/e2e/android-flow-steps.ts Its flows step (the offline journeys under real airplane mode, then the save benchmark) and its large-text step (font_scale 2.0, en and fa, phone and tablet) Copied with it
templates/packages/tooling/src/e2e/airplane-mode.ts underAirplaneMode, pure: the offline journeys between airplane mode on and off, the off run whatever happens (a failed on run, flows that throw), a failed off run failing the step Copied with it
templates/packages/tooling/src/e2e/airplane-mode.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/emu-perf-steps.ts Its cold-start step (am start -W -S against perf-baselines/cold-start-emu-<game-id>.json) and memory step (dumpsys meminfo private dirty + swap, feedback.json) Copied with it
templates/packages/tooling/src/e2e/android-cli.ts The command lines of e2e:android and screenshots:android, pure (--help, usage errors, --avd, --driver-port), and apkVariantProblem (test/off only, through simulator.ts' e2eBuildProblem) Copied with it
templates/packages/tooling/src/e2e/android-cli.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/android-device-args.ts Every adb argument list (-s <serial> first: deviceAdbArgs), androidMaestroGlobalArgs, the VIEW-intent debug link, cold launch, meminfo, /proc/net, font scale, night mode, stillScreenCommands Copied with it
templates/packages/tooling/src/e2e/android-device-args.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/android-e2e-device.ts Emulators on android-emulator-build's layer (startE2eEmulator, adbOn), readApkApp (refuses an APK that is not test/off), runMaestroAndroid (with the run's toolchain env) Copied with it
templates/packages/tooling/src/e2e/android-socket-sampler.ts Pure: the app's uid, /proc/net/tcp, tcp6, udp, udp6 parsed, non-loopback connections of that uid Copied with it
templates/packages/tooling/src/e2e/android-socket-sampler.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/sample-sockets-android.ts The sampler process (<package> <report> <serial>), one per emulator, with the iOS sampler's status file Copied with it
templates/packages/tooling/src/e2e/android-cold-start.ts Pure: am start -W TotalTime, the perf log's cold-start entry, the emulator baseline file Copied with it
templates/packages/tooling/src/e2e/android-cold-start.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/android-memory.ts Pure: dumpsys meminfo private dirty + swap PSS (the iOS footprint's like) against perf.memoryFootprintMbMax (150 MB), total PSS beside it as information Copied with it
templates/packages/tooling/src/e2e/android-memory.test.ts Its test Copied with it
templates/packages/tooling/src/e2e/capture-screenshots-android.ts npm run screenshots:android: the matrix on a phone and a tablet AVD, baselines in apps/<game-id>/e2e/baselines-android/, the same 0.2% Copy at Android step 7
templates/packages/shell/e2e/subflows/airplane-mode.yaml Real airplane mode on Android (AIRPLANE enabled or disabled, each step under platform: Android), run by e2e:android around the offline journeys Copy at Android step 7
templates/packages/shell/e2e/subflows/kill-app.yaml A real process death on both platforms: killApp on iOS, stopApp (am force-stop) on Android, where killApp is am kill and spares the foreground app; every kill-and-relaunch flow runs it Copy in step 1, with the flows that use it
templates/packages/tooling/src/visual/compare-png.ts pixelmatch comparison against a baseline Copy in step 1
templates/packages/tooling/src/audit/network-runtime-layer.ts lsof parsing, the pids of the app's copies on the run's own simulators only (appPids(appName, udids), appProcessPattern: any bundle folder, since Maestro's clearState reinstall renames it), socket sampling; synced from the library (identical to privacy-and-network-audit's copy), do not edit here Copy in step 1
templates/packages/tooling/src/audit/network-runtime-layer.test.ts Its Jest test Copied with it
templates/packages/tooling/src/audit/sample-sockets.ts The sampler process the runner spawns once per simulator of the run (<AppName> <report> <udid>...: never another session's copy; synced from the library) Copy in step 1
templates/packages/shell/src/screens/debug/ The S15 debug kit, each file with its Jest test: network-guard.ts (synced from the library; do not edit here), simulated-connectivity.ts, simulated-clock.ts, debug-services.ts (setOffline, setDate, setPremium, createConsent, board layout, ads and seed overrides, restored from the key-value store; perfLog), debug-overrides.ts and fake-debug-store.ts, debug-link.ts (the parser), debug-save-recipe.ts, debug-save-import.ts (S15's import through the save codec), debug-tools.ts, debug-sheets.ts, debug-actions.ts, debug-perf.ts (the Performance section's contract and summary) with its Jest fake fake-debug-perf.ts, use-debug-model.ts (S15's model hook: rows, switches, importField, importSave, openFontTest, perf) Copy in step 1
templates/packages/shell/src/app/ debug-link-handler.ts (listen, start, the group-switch wait, importSave), debug-link-intake.ts (links queued until the navigator is ready), debug-link-routes.ts (DebugGameControls: playTo returns whether a run was on screen; resetToFirstRoute leaves a live run through the exit it is given first, and debugRouteOpener(exit) binds the test build's live-run exit for every link), create-debug-parts.ts (the composition root's one call: guard, perf log with S15's Performance actions, the feedback recorders, services, handler, early listen), debug-services-context.tsx (useDebugServices, useOptionalDebugServices, useDebugLinks), each with its test Copy in step 1
templates/packages/shell/src/services/save/sqlite-kv-debug-store-adapter.ts The test-only key-value store (sync expo-sqlite/kv-store) Copy in step 1
templates/test/integration/save/sqlite-kv-debug-store-adapter.test.ts Its test on the real kv-store code over node:sqlite (flags survive a JS reload) Copied with it
templates/packages/shell/e2e/subflows/debug-setup.yaml Waits for a screen root, applies a debug QUERY, waits for WAIT_FOR (synced from the library, identical to premium-purchase's copy; do not edit here) Copy in step 1
templates/packages/shell/e2e/subflows/assert-no-network.yaml Scrolls to debug.network-attempts (below the fold on a phone) and asserts it is 0 Copy in step 1
templates/packages/shell/e2e/subflows/shoot-screen.yaml Opens screen SCREEN, waits for its root id ROOT, screenshots it Copy in step 1
templates/packages/shell/e2e/subflows/win-and-next.yaml Wins the level on screen (action=win-level), taps Result's Next, waits for NEXT_ROOT Copy with the flows that use it
templates/packages/shell/e2e/screenshots/matrix.yaml The 11-screen capture flow Copy in step 1
templates/packages/shell/e2e/flows/smoke/01-first-launch.yaml S1 → S2 → tutorial, no network Step 3, Shell flows
templates/packages/shell/e2e/flows/journeys/02-core-journey-offline.yaml Offline core journey, survives a kill Step 3, Shell flows
templates/packages/shell/e2e/flows/rtl/03-language-switch.yaml English → Persian restart keeps progress (synced from the library, identical to rtl-and-direction's copy; do not edit here) Step 3, Shell flows
templates/packages/shell/e2e/flows/smoke/04-debug-performance.yaml S15's Run save benchmark on the simulator, its p95 shown, no network (a Maestro call of its own after the other flows) Step 3, Shell flows
templates/apps/__GAME_ID__/e2e/flows/smoke/10-level-1.yaml A game's level-1 flow: one link, one move by taps, kill, resume, action=win-level, the example win's stars, then Levels shows them and level 2 opens Step 3, per game
templates/apps/__GAME_ID__/e2e/flows/journeys/11-daily.yaml Today's daily won through S9's Play key, then the next day: streak 1, today not done Step 3, games with modes.daily
templates/apps/__GAME_ID__/e2e/flows/journeys/12-continue-premium.yaml A loss continued with the Premium continue: the same level plays again Step 3, games with isContinueAllowed
templates/apps/__GAME_ID__/e2e/flows/journeys/13-endless.yaml An endless run lost without Premium in an ads-off build: the endless result with New best at once, no continue offer, then Home's best Step 3, games with modes.endless
assets/debug-link-params.json Every debug-link parameter and its allowed values Read by check-flows.mjs and check-e2e-setup.mjs; update with debug-link.ts and the reference table
assets/e2e-testids.json The test-build-only ids (debug.network-attempts, game.board-layout, game.moves-label, game.board-frame) and who draws them, and gameFlowIds: the screen-map ids the flow templates rely on Read by check-flows.mjs
assets/screen-testids.json The Toybox screen testID contract (synced from the library) Look up an id in step 3; never edit here
assets/shared.json Declares the shared files this skill copies in When adding a shared file
scripts/check-e2e-setup.mjs Checks installer pin, Maestro env, no uploads, no simctl openurl in the tooling, every Maestro spawn naming its device and driver port (maestro-device), the ads-off build guard (e2e-ads-off), the feedback evidence step, runner and sampler (sampler-own-simulators: one sampler per simulator of the run, never another session's copy), the runner's four steps, the S15 debug kit (network guard installed, debug services, link handler listening at once and started, the group-switch wait, flags kept across the reload, model hook), the perf layer (perf-layer: its JS half due at Shell step 7, its native half at step 8), simulators, capture settings and tolerance, sub-flows, scripts, pins, .gitignore, baseline sets (baselines/, and baselines-android/ from Android step 7); SKIP lines for S15 and matrix screens outside shell-slice.json and for each perf half before its step; Android step 7: the Android set (android-runner-missing), adb and Maestro calls naming the emulator serial (android-serial), and the Android lines of the sampler, steps, ads-off, tolerance, still-screen and script rules (not-yet-due SKIP lines before any Android file) Step 1 and the definition of done
scripts/check-flows.mjs Checks flow places (admob-ads' ads-smoke/ included: no launchApp, geo= first, a testID step), names, headers, tags, the Open-alert guard, run commands naming --device and a driver port, id selectors against the contract (count ids such as result.stars-<n>, id patterns, ids and queries built inside sub-flows from passed values; crop-only ids fail as unreachable-testid), a game flow's stars listed in its testids.json (win-stars), the stars reaching Levels after a win (progress-after-win), one flow per game mode (mode-flows), debug queries, first step, smoke ending, quarantine, AI commands, env: shadowing, and no flows at all (flows-missing); with shell-slice.json a flow that reaches a screen outside the slice is a SKIP ... [slice] line; one-platform steps under when: platform: and killApp only under iOS (platform-guard, due at Android step 7); --syntax runs Maestro Step 4 and the definition of done
scripts/check-e2e-report.mjs Reads junit.xml, network.txt and network-samplers.json (network-sampler: every sampler alive to the end), perf.json, feedback.json (feedback-evidence), save-benchmark.json (save-benchmark), the large-text reports and (--screenshots) summary.json (screenshot-capture-failed: a combination that captured nothing); --platform android reads reports/e2e-android/<game-id>/ (cold-start.json, memory.json with private dirty + swap) and reports/screenshots-android/; prints the evidence lines Steps 5-6 and the definition of done
scripts/lib/maestro-yaml.mjs Zero-dependency reader for Maestro YAML with line numbers Used by the checkers
scripts/lib/android-e2e.mjs When the Android E2E set is due (any of its files or scripts exists; before that, not-yet-due SKIP lines) and its file list Used by check-e2e-setup.mjs and check-flows.mjs
scripts/lib/maestro-spawns.mjs The maestro-device rule: every spawn of the Maestro binary in the repo tooling names --device and a driver port first (synced from the library, identical to ios-simulator-build's copy; do not edit here) Used by check-e2e-setup.mjs
scripts/selftest.mjs Proves the three checkers pass good fixtures and catch every planted bug, including the partial-Shell suites After changing a checker
scripts/check-lib.mjs Shared script helper, synced from the library (do not edit here) Never by hand
tests/fixtures/ Good and planted-bad repos and reports for the self-test (built from the templates); check-flows-empty/, check-flows-slice/ and check-e2e-setup-slice/ hold the no-flows and shell-slice.json cases When adding a rule to a checker

Related skills

  • ios-simulator-build - builds the Release test-variant app the flows run against.
  • toybox-visual-parity - proves a new screen matches its Toybox design before its baseline is accepted.
  • privacy-and-network-audit - the static network layers A-E and the release audit around layer F.
  • performance-budgets - the perf log, useColdStartMark and the budgets the cold-start and memory steps judge.
  • accessibility - the a11y large-text flow and what to look for in its screenshots.
  • unit-and-component-tests - Jest tests for everything a flow should not have to prove.
  • golden-tests - data and board pixel goldens, and the gated-path policy that baselines share.
  • toybox-screens - the screens, their testIDs and the debug screen the flows drive.
  • git-commits-and-reporting - the Gate-Change trailer and the evidence report.

04 同仓库更多 Skills

zakariaf
zakariaf/pocket-arcade

unit-and-component-tests

Sets up Jest and writes Pocket Arcade test code - three projects, RNTL 14 async via renderWithShell, root SDK mocks, fakes, fast-check, coverage, Stryker. Use when a test needs the Shell wrapper, a mock or a fake, or Jest fails. Not for the red-green loop (tdd-workflow) or goldens (golden-tests).

★ 0
zakariaf
zakariaf/pocket-arcade

typescript-and-lint-rules

Enforces Pocket Arcade's strict TypeScript 6 tsconfigs, ESLint 9 flat config, Prettier and size limits. Use when writing or splitting .ts/.tsx or fixing a tsc or lint error. Not for gate wiring (quality-gates), names (naming-conventions) or import zones (architecture-and-boundaries).

★ 0
zakariaf
zakariaf/pocket-arcade

demo-skill

Builds a demo. Use when testing the pitfall checker.

★ 0
zakariaf
zakariaf/pocket-arcade

demo-skill

Builds a demo. Use when testing the pitfall checker.

★ 0
zakariaf
zakariaf/pocket-arcade

troubleshooting-playbook

Diagnoses and fixes Pocket Arcade failures with a catalogue of 300+ known ones (symptom, cause, fix). Use when something errors, crashes, hangs or renders wrong, or an error or log needs explaining. Not for gate setup (quality-gates) or running builds (ios-simulator-build, ios-release-testflight).

★ 0
zakariaf
zakariaf/pocket-arcade

toybox-visual-parity

Proves a built Pocket Arcade screen matches its Toybox design screenshot (light/dark, en/fa) and drives the fixes: capture, element gates, sheets, sign-off. Use when any screen is built or changed, or for visual parity, design match, screenshot compare. Not for building screens (toybox-screens).

★ 0