Testland
Browse all skills & agents

maestro-flows

Authors Maestro YAML flow files (`.maestro/*.yaml`) for mobile + web UI automation: declarative `tapOn`, `inputText`, `assertVisible`, `swipe`, supported targets (iOS, Android, Flutter, React Native, web), nested flow imports, JavaScript hooks for complex conditions. Use when the team has already chosen Maestro, is coming from an existing `.maestro/` directory, or explicitly wants YAML-declarative tests readable by non-engineers without a compile step. For framework selection or authoring tests in XCUITest / Espresso / Detox / Appium / Flutter, use mobile-driver-selector or mobile-test-author instead.

maestro-flows

Overview

Per maestro-docs:

"Maestro [is] the simplest and most effective framework for painless mobile and web UI automation using intuitive YAML flows."

The flow file is the artifact - no compile step, no language runtime to set up.

The platform ships three pieces (maestro-docs):

  1. Maestro Studio - A desktop application for visual test creation without IDE setup
  2. Maestro CLI - Command-line interface for installing, managing devices, and running tests
  3. Maestro Cloud - Integration with CI platforms like GitHub Actions for parallel testing

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

CommandPurpose
launchAppStart 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
backPress back / navigate back
pressKey: ENTERSynthesize a hardware key
takeScreenshotCapture 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/

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/

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-patternWhy it failsFix
tapOn text that's translatedTests fail in non-English locales.Use tapOn: { id: "..." } for stable lookups.
One mega-flow with 100 stepsFailure mid-flow obscures cause; reruns repeat all 100 steps.Modularize via runFlow (Step 4).
Hard-coded credentials in YAMLSecrets 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 / verificationSwipes vary per platform; visible state may not change.assertVisible after swipe to confirm state changed.
Skipping appId declarationMaestro 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