maestro-flows
Overview
Maestro automates mobile and web UI from declarative YAML flows (per
maestro-docs). The flow file is the artifact - no compile step, no
language runtime to set up.
When to use
- A team needs cross-platform mobile UI tests with the lowest
setup ceremony.
- Non-engineers (PMs, designers) need to write or read tests -
YAML is more accessible than Swift / Kotlin / JS.
- The product spans iOS + Android (and optionally Flutter / RN /
web).
- Existing Detox / Appium suites would be overkill for the test
count (5-30 critical flows).
If the team needs deep gray-box hooks (per-component intercepts),
detox-testing (RN) or framework-specific
unit/widget tests cover that better.
Step 1 - Install Maestro CLI
curl -Ls "https://get.maestro.mobile.dev" | bash
maestro --version
Cross-platform: macOS, Linux, Windows (WSL recommended).
Step 2 - Author a flow
# .maestro/cart-flow.yaml
appId: com.example.app
---
- launchApp
- tapOn: "Sign in"
- inputText: "qa-test@example.com"
- tapOn: "Password"
- inputText: "test-password"
- tapOn: "Continue"
- assertVisible: "Welcome"
- tapOn: "Add to cart"
- assertVisible:
text: "Cart"
enabled: true
- tapOn: "Cart"
- assertVisible: "1 item"
- tapOn: "Checkout"
The file is YAML with appId declaration + --- separator + a
list of commands. Commands map directly to user actions.
Step 3 - Common commands
| Command | Purpose |
|---|
launchApp | Start the app fresh |
tapOn: "<text>" | Tap an element with visible text |
tapOn: { id: "..." } | Tap by accessibility ID |
inputText: "..." | Type text into the focused field |
assertVisible: "..." | Assert element is visible |
assertNotVisible: "..." | Assert element is NOT visible |
swipe: { direction: UP } | Swipe |
scrollUntilVisible: { element: { text: "..." } } | Scroll until found |
back | Press back / navigate back |
pressKey: ENTER | Synthesize a hardware key |
takeScreenshot | Capture screenshot to artifact dir |
runFlow: "<other.yaml>" | Compose flows |
evalScript: "<js>" | Run JavaScript for complex conditions |
Step 4 - Flow modularity
# .maestro/login.yaml
appId: com.example.app
---
- launchApp
- tapOn: "Sign in"
- inputText:
text: ${EMAIL} # env-variable interpolation
- tapOn: "Password"
- inputText: ${PASSWORD}
- tapOn: "Continue"
- assertVisible: "Welcome"
# .maestro/cart-flow.yaml
appId: com.example.app
---
- runFlow: "login.yaml"
- tapOn: "Add to cart"
- assertVisible: "1 item"
The runFlow composition keeps shared paths (login, navigation)
DRY across the test suite.
Step 5 - JavaScript hooks for complex conditions
- evalScript: |
output.timestamp = new Date().toISOString();
- inputText: ${output.timestamp}
- tapOn: "Save"
# Conditional flow
- runFlow:
when:
visible: "Onboarding"
file: "skip-onboarding.yaml"
evalScript reads + writes to an output object that subsequent
commands can reference. when makes commands conditional.
Step 6 - Run
# Single flow
maestro test .maestro/cart-flow.yaml
# Whole directory
maestro test .maestro/
# With env vars
EMAIL=qa-test@example.com PASSWORD=test maestro test .maestro/
Verify: maestro test exits non-zero if any command fails. If it does,
Maestro writes a screenshot of the failing screen to its output dir - use it
to fix the failing tapOn / assertVisible target, then re-run before
moving on.
Step 7 - Studio for visual authoring
maestro studio
Opens a desktop app showing the connected device + an inspector for
each tapped element. Click an element → Studio generates the
corresponding YAML command. For non-engineers, this dramatically
shortens the authoring loop.
Step 8 - CI integration
# .github/workflows/maestro.yml
jobs:
maestro-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
script: |
curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="$HOME/.maestro/bin:$PATH"
maestro test .maestro/
maestro-ios:
runs-on: macos-15
steps:
- uses: actions/checkout@v5
- run: |
xcrun simctl boot 'iPhone 15'
curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="$HOME/.maestro/bin:$PATH"
maestro test .maestro/
Verify: maestro test exits non-zero on any flow failure, so a failing flow
fails the job. Upload Maestro's screenshot output as a build artifact (e.g.
actions/upload-artifact) so batch failures are debuggable without a local
re-run.
For matrix runs across N devices in parallel, use Maestro Cloud
(per maestro-docs) - handles farm-side parallelism without
local emulator orchestration.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|
tapOn text that's translated | Tests fail in non-English locales. | Use tapOn: { id: "..." } for stable lookups. |
| One mega-flow with 100 steps | Failure mid-flow obscures cause; reruns repeat all 100 steps. | Modularize via runFlow (Step 4). |
| Hard-coded credentials in YAML | Secrets in git. | Env vars + ${VAR} interpolation (Step 4). |
evalScript for everything (writing tests in JS via YAML) | Defeats Maestro's YAML simplicity. | Use evalScript only for genuinely complex conditions. |
swipe without direction / verification | Swipes vary per platform; visible state may not change. | assertVisible after swipe to confirm state changed. |
Skipping appId declaration | Maestro can't tell which app to drive; ambiguous failures. | Always declare appId (Step 2). |
Limitations
- No deep state inspection. Maestro is UI-level; for assertions
on app internals (Redux state, network calls), gray-box frameworks
(
detox-testing) fit better.
- Per-platform UI quirks. Same YAML may behave differently
iOS vs Android (toast positioning, keyboard interaction);
per-platform branching via
evalScript may be needed.
- Limited debugging tools. Failure debugging relies on
screenshots + studio replay; less rich than IDE-debugger
integration.
- Cloud features behind Maestro Cloud. Parallel matrix runs +
per-device farms are paid (per maestro-docs).
References
- mae - Maestro overview: YAML flows, three-component model
(CLI / Studio / Cloud), modular learning path.
detox-testing,
appium-testing - alternative
cross-platform frameworks with deeper hooks.
xcuitest-suite,
espresso-suite - native
alternatives for single-platform.