mobile-flows-maestro · diff
git:20260708.586577e to git:20260725.6c1b23d
67 added, 97 removed. Audit A to A.
---
name: mobile-flows-maestro
- description: Use this skill when writing or running Maestro flows for iOS or Android apps, driving the Maestro MCP server, diagnosing flaky mobile UI tests, or onboarding a new team to mobile e2e testing. Trigger whenever the user mentions Maestro, mobile e2e testing, iOS Simulator flows, Android emulator flows, "test my iOS app", "test my Android app", YAML test flows, GraalJS test scripting, assertVisible / tapOn / launchApp, deep links from tests, or "why is my Maestro test flaky". NOT for web, desktop, or backend testing — those have their own validators. Applies to Swift/SwiftUI, React Native, Flutter, Kotlin, Java, .NET MAUI, Ionic, and Capacitor apps. Prefer this skill over letting Claude write Maestro flows from scratch — Maestro YAML looks deceptively simple but has many platform-specific gotchas (GraalJS constraints, iOS Keychain persistence, ASWebAuthenticationSession system sheets, permission timing) that first-drafts get wrong.
+ description: >-
+ This skill should be used when Maestro is explicitly requested or already
+ present and the task is to author, run, or debug iOS/Android Maestro flows;
+ use Maestro MCP; or handle Maestro selectors, system UI, permissions,
+ Keychain, JavaScript, waits, device state, flakiness, or CI. Evidence includes
+ a .maestro directory or flow YAML with appId. Not for XCUITest, Appium, web,
+ desktop, backend, or unit tests.
---
# Maestro Mobile Flows
- Use for authoring, running, and debugging Maestro flows. Maestro's own docs cover the command surface; this skill captures the non-obvious stuff that makes flows fail.
-
- ## Framing before you write a flow
-
- Before picking tap sequences and selectors, work the change as a thinking exercise — YAML comes after.
-
- 1. **What feature does this change represent?** State it as a user-visible effect, not a function signature. Flows that don't map to a user story end up brittle and meaningless.
- 2. **What does success look like?** Be concrete — "after tapping Create, the new session appears at the top of the sidebar within 2s" beats "it works." The assertion comes directly from this sentence.
- 3. **Validate in two passes.** User-lens: walk the screen as a real user would, observe transitions, error states, loading feedback. System-lens: when the flow calls a backend, check that backend did the right thing (Hurl or direct DB) — the UI can show "success" while the data is wrong.
- 4. **Check 2–3 adjacent paths.** A new-session flow probably affects the session list, any active viewer, the empty state. Pick the obvious neighbors and verify them in the same flow or a sibling flow.
- 5. **Cover realistic failure modes** — offline, permission denied, sign-in expired mid-flow. One test per failure class, not exhaustive coverage.
- 6. **Skip what can't plausibly break from this change.** A color tweak doesn't need an auth-flow regression. A core navigation change probably does.
-
- ## When in doubt, check the live surface
-
- Maestro's YAML vocabulary shifts between releases and the flag set changes too. Before writing a non-trivial step or using a command you haven't used recently, run `maestro test --help`, or call the Maestro MCP's `listDocumentation` / `query` tool if connected, or open `https://docs.maestro.dev` — don't guess from training data.
-
- ## MCP vs CLI — when to use which
-
- Maestro 2.4+ ships an MCP server (`maestro mcp`) that registers as an MCP in `.mcp.json`. Use it when:
- - Exploring a new app's UI — Claude can read the hierarchy, screenshot, iterate
- - Generating a new flow from natural language
-
- Use the plain CLI (`maestro test flow.yaml`) when:
- - Running an existing flow suite (deterministic, CI-compatible)
- - Debugging why CI fails but local passes
-
- They compose — MCP for authoring, CLI for running.
-
- ## Prefer text-based locators over coordinates
-
- `tapOn: "Visible Text"` survives layout changes; pixel coordinates don't. When text isn't stable, use testIDs (`tapOn: { id: "submit-button" }`). Coordinates are a last resort and fail across device sizes.
-
- ## iOS gotchas — the ones that bite first
-
- ### Permissions must be granted at launch
+ Use Maestro's live surface as the source of truth. CLI flags come from the
+ installed `maestro --help` and subcommand help. Flow YAML and JavaScript come
+ from current Maestro documentation. When MCP is connected, inspect its actual
+ tool inventory instead of naming remembered tools.
- ```yaml
- - launchApp:
- permissions: { all: allow }
- # or granular: notifications, camera, microphone, photos (Limited), location (Always/WhileUsing)
- ```
+ ## Choose the interface
- Permission prompts that surface mid-flow (after `launchApp`) are often invisible to Maestro. Grant at launch and move on. `cross-website tracking` is not controllable (Apple hides that API).
+ Use MCP or Studio for interactive hierarchy inspection, authoring, and feedback.
+ Use the CLI for scriptable execution, exit codes, reports, artifacts, and CI.
+ Confirm that the installed version supports `maestro mcp`, then register it
+ through the active host's MCP mechanism.
- ### iOS Keychain survives `clearState`
+ Hierarchy and text are cheap; repeated image payloads are not. When a host
+ retains images in the long-lived conversation, keep screenshots in a bounded
+ visual task and return only its text finding.
- `clearState` wipes app data but leaves Keychain intact. If the app caches auth tokens in Keychain, a previous flow's credentials leak into the next. Either use `clearKeychain: true` (Maestro 2.x+) or erase the simulator between independent flows:
+ ## Select a device deliberately
- ```bash
- xcrun simctl erase <device-id>
- ```
+ Inspect booted iOS simulators and connected Android devices. Ignore
+ offline/unauthorized Android entries. When more than one compatible device is
+ available, target one explicitly with the installed CLI's syntax--currently
+ `maestro --device <serial-or-udid> test <flow>`. Do not silently choose a
+ platform, emulator, or physical device.
- ### ASWebAuthenticationSession (Google Sign-In, Sign in with Apple)
+ ## Prefer observable selectors
- These launch a system sheet that lives outside the app's view tree. Maestro can sometimes drive them, but it's flaky. The robust pattern:
+ Prefer visible text, then accessible identifiers or descriptions, then
+ coordinates. Maestro text matchers are regular expressions, so escape
+ metacharacters and disambiguate labels that can match multiple elements. An ID
+ works only when the app exposes it to the platform accessibility hierarchy;
+ inspect the live hierarchy rather than assuming a SwiftUI identifier, Android
+ resource ID, Compose tag, React Native testID, or Flutter key is visible.
- 1. Debug-build flag that short-circuits `GIDSignIn` / `ASAuthorizationController` with a pre-built test identity token
- 2. Deep-link into the signed-in state — `openLink` is a YAML flow command, not a CLI subcommand:
+ ## State and system surfaces
- ```yaml
- - openLink: "myapp://mock-signed-in"
- ```
- 3. Let Maestro assert on the post-auth UI
+ Configure supported iOS and Android permissions through `launchApp.permissions`
+ or `setPermissions`; use platform tools only for special cases current Maestro
+ cannot represent.
- Do not try to drive the live Google consent page through `ASWebAuthenticationSession` in CI. You will regret it.
+ iOS app-state clearing does not necessarily clear Keychain. For independent
+ authentication flows, use the current `clearKeychain` command or
+ `launchApp.clearKeychain` and remember that it is iOS-only.
- ### Biometrics
+ External authentication and other system-owned surfaces can be unstable in
+ unattended CI. Prefer a test-build-only bypass or seeded identity when
+ authentication itself is not under test. Keep a controlled integration test
+ when the real handoff is the requirement.
- `xcrun simctl io booted biometric enroll` once during setup, then `xcrun simctl io booted biometric match` from a pre-step hook during the flow. Maestro has no first-class biometric control.
+ Biometric simulation changes across Xcode and device tooling. Check current
+ Maestro docs and `xcrun simctl help` before suggesting a command; do not preserve
+ old `simctl` syntax as doctrine.
- ## GraalJS constraints (`runScript:` blocks)
+ ## JavaScript is a separate file
- Maestro's `runScript:` runs JavaScript via **GraalJS**, not Node. The differences bite people new to Maestro:
+ `runScript` executes a JavaScript file relative to the flow. Keep scripts in the
+ uploaded flow directory for cloud execution.
- - **No `async`/`await`** — synchronous only
- - **No `fetch`** — use `http.get(url)` / `http.post(url, options)`
- - **No `require` / ES modules** — top-level code only
- - **No `setTimeout`** — flow is synchronous
+ Maestro JavaScript is not Node: do not assume `require`, ES modules, filesystem
+ access, or browser `fetch`. Use the HTTP and output interfaces documented by the
+ installed/current Maestro version.
- Correct:
```yaml
- - runScript: |
- const r = http.get('https://api.example.com/health');
- if (r.status !== 200) throw new Error('health failed: ' + r.status);
- output.healthy = true;
+ - runScript: scripts/health.js
- assertTrue: ${output.healthy}
```
- Wrong:
- ```yaml
- - runScript: |
- const r = await fetch('https://api.example.com/health'); # fails
- ```
-
- ## Race conditions — always `extendedWaitUntil`
-
- After any action that triggers navigation, a network call, or an animation, bare `assertVisible` races the UI transition. Use `extendedWaitUntil` with a sensible timeout:
-
- ```yaml
- # Brittle:
- - tapOn: "Submit"
- - assertVisible: "Success"
-
- # Robust:
- - tapOn: "Submit"
- - extendedWaitUntil:
- visible: "Success"
- timeout: 10000
- ```
-
- This is the #1 flakiness source in real-world Maestro suites.
+ ## Wait for product behavior
- ## Flow layout convention
+ Maestro assertions already retry for a default window. Use
+ `extendedWaitUntil` when a legitimate outcome exceeds that window, with a
+ timeout tied to expected product behavior rather than a blanket larger value.
+ A larger timeout proving successful establishes timing sensitivity, not its
+ cause.
- One flow per user journey: `launch.yaml`, `signin-google.yaml`, `create-session.yaml`. Don't combine multiple journeys into a single file — failure isolation is worth the extra files.
+ On failure, classify before editing: app defect, selector problem, timing race,
+ test-state leak, or environment failure. Do not rewrite a flow merely to make
+ the current run pass.
- ## References
+ Keep independently meaningful journeys separate when that improves diagnosis
+ or reuse. Combine steps that form one product outcome; do not split files to
+ satisfy a universal one-journey rule.
- - `references/maestro-gotchas.md` — extended flakiness debugging, CI patterns, simctl cookbook, Android specifics
+ Load `references/maestro-gotchas.md` for CI, platform state, system surfaces,
+ and failure diagnosis.