Use when a team uses or wants Maestro YAML flows for mobile or web UI tests that non-engineers can read: .maestro/*.yaml with tapOn, inputText, assertVisible and swipe, for iOS, Android, Flutter, React Native and web, with nested flows and JavaScript hooks.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
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.
If the team needs deep gray-box hooks (per-component intercepts),
detox-testing (RN) or framework-specific
unit/widget tests cover that better.
curl -Ls "https://get.maestro.mobile.dev" | bash
maestro --versionCross-platform: macOS, Linux, Windows (WSL recommended).
# .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.
| 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 |
# .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.
- 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.
# 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.
maestro studioOpens 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.
# .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-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). |
detox-testing) fit better.evalScript may be needed.detox-testing,
appium-testing - alternative
cross-platform frameworks with deeper hooks.xcuitest-suite,
espresso-suite - native
alternatives for single-platform.