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 a mobile driver-selection or per-flow mobile test-authoring step instead.

Install with skills.sh (any agent)

npx skills add testland/qa --skill maestro-flows
View source

maestro-flows

Overview

Maestro automates mobile and web UI from declarative YAML flows (per maestro-docs (opens in new window)). 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

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/

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 (opens in new window)) - 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 (opens in new window)).

References

  • mae (opens in new window) - 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.

Related skills

appium-testing

Wires Appium for cross-platform mobile UI automation - uses the WebDriver protocol, picks a driver per platform (XCUITest for iOS, UiAutomator2 / Espresso for Android, Mac2 for macOS, Windows for desktop), authors tests in JS / Python / Java / Ruby / .NET, configures `desiredCapabilities`, runs against simulators / emulators / device farms. Use when a single test suite must cover both iOS and Android, or when the team's stack is multi-platform (iOS + Android + Mac + Windows).

detox-testing

Authors React Native E2E tests with Detox (Wix) - gray-box architecture (runs in-process with the app), `element(by.id|by.text|by.label)` matchers, `waitFor()` for explicit sync beyond Detox's automatic async tracking, Jest runner. Use when the app is React Native and speed matters. For Flutter use flutter-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows; for non-RN native use xcuitest-suite or espresso-suite.

espresso-suite

Authors Espresso UI tests for Android - uses `onView(withId(...)).perform(...).check(matches(...))`, leans on Espresso's automatic synchronization (no `Thread.sleep`), wires `IdlingResource` for app-specific async, runs via `./gradlew connectedAndroidTest` and parses the JUnit XML output. Use when an Android app needs UI tests in Google's first-party framework.

flutter-testing

Authors Flutter tests across the three-layer pyramid - unit (`flutter test`), widget (`testWidgets` + `WidgetTester`), integration (`integration_test` on simulator/emulator/device). Picks the right layer per change, mocks via `mockito` + `build_runner`, LCOV coverage, CI with the Flutter Action. Use when the app is Flutter and the team wants its first-party stack. For React Native use detox-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows.

mobile-a11y-test-author

Authors native mobile accessibility tests covering iOS (Accessibility Inspector, XCUITest `performAccessibilityAudit()` introduced in iOS 17, VoiceOver label/trait/hint verification) and Android (Espresso `AccessibilityChecks.enable()`, Accessibility Scanner, TalkBack traversal, `contentDescription` labelling) with WCAG-aligned checks for element labels, 44pt/48dp touch targets, contrast ratios, and focus order. Use when an iOS or Android app needs automated and manual accessibility test coverage beyond what `xcuitest-suite` or `espresso-suite` provide.

mobile-device-matrix-toolkit

Dispatches mobile UI test runs across a 3-tier device matrix (smoke per-PR, regression per-merge, full farm at release) to control CI cost: generates per-target Appium capability configs from a central YAML, parallelises via GitHub Actions matrix strategy, and aggregates JUnit XML into a cross-device pass/fail table. Use when deciding which iOS / Android devices and OS versions to run tests on and at which stage (smoke / regression / full farm), not how to configure a specific test framework (for that, use xcuitest-suite, espresso-suite, etc.).

mobile-web-emulation-runner

Builds a workflow to run web E2E tests under mobile viewports + DPRs (device pixel ratios): Playwright's `devices` catalog (iPhone 15, Pixel 7), suite run per-device as matrix shards, per-device screenshots, mobile assertions (`.tap()`, viewport-conditional layout). Use when a responsive web app needs mobile-breakpoint regression without a real-device farm. Mobile WEB only - for native apps use appium-testing, detox-testing, or flutter-testing; for cross-shard aggregation use mobile-device-matrix-toolkit; for gesture sequences use touch-gesture-tester.

mobile-web-perf-budget

Pure-reference skill for mobile-web performance budgets - Core Web Vitals at the 75th percentile mobile (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1; FID retired March 2024 in favor of INP), Lighthouse mobile profile config, per-route resource budgets (JS bundle, image weight, font load). Use as the team's reference for "what should the mobile perf gate enforce" - paired with `lighthouse-perf` (the runner) and `lighthouse-budget-author` (the per-route author).

touch-gesture-tester

Verifies touch-gesture handlers (tap, double-tap, long-press, swipe, pinch, rotate, pan) work as expected under both mobile-emulation (Playwright) and native (XCUITest / Espresso / Detox) - distinguishes "mouse click handler also fires on tap" from "real touch event fired with correct properties." Use when the app has bespoke gesture handlers (custom carousels, sliders, drag-drop, pull-to-refresh) and the team needs targeted gesture verification beyond generic UI assertions.

xcuitest-suite

Authors XCUIest UI tests for iOS / iPadOS / tvOS - uses the three-class XCUIApplication / XCUIElement / XCUIElementQuery pattern, sets accessibility identifiers on production code, runs via `xcodebuild test` with destination, parses the `xcresult` bundle. Use when an iOS app needs UI tests in Apple's first-party framework (no external runtime; native to Xcode).