Browser

maestro-mobile-test

Try it

Write and run E2E tests for React Native / Expo apps using Maestro CLI — the open-source tool that drives the **real native app** on an Android emulator or iOS simulator, not a web build. Covers what browser-based e2e (Playwright, Expect, e2e-verify) cannot reach: native components, biometrics, secure storage, push notifications, platform-specific modules. Write YAML flows, run headed for authoring or headless for CI. Scripts currently automate the Android workflow end-to-end (Java + SDK detection, emulator boot, adb reverse); iOS simulator setup requires Xcode + `xcrun simctl` and is documented but not automated by the setup script. Use this skill whenever the user says "test my react native app", "e2e test the mobile app", "write maestro tests", "test the expo app", "automate emulator testing", or "/maestro-mobile-test" — even if they don't name the skill. Not for web apps (e2e-verify), unit/component tests (write-tests with RNTL + Jest), or API contract tests.

What it does

The browser is the wrong tool for a React Native app — it renders to native components (UIView / android.view), not a DOM. Maestro drives the **actual native binary** on a real emulator/simulator: real taps, real native assertions, real platform behavior. It's the open-source (Apache-2.0) standard…

The skill document

Maestro Mobile Test

The browser is the wrong tool for a React Native app — it renders to native components (UIView / android.view), not a DOM. Maestro drives the actual native binary on a real emulator/simulator: real taps, real native assertions, real platform behavior. It's the open-source (Apache-2.0) standard for RN E2E, and it writes tests as YAML flows — no test framework setup, no instrumentation in your app code.

The discipline is identical to e2e-verify's Route B (durable tests): ration by journey risk, selectors a user would recognize, prove red-capable, quote the run summary. What changes is the surface — native instead of browser — and the harness-specific pitfalls that will eat your time if you don't know them.

When to use this skill

  • The user says "test my react native app", "e2e the mobile app", "write maestro tests", "test the expo app", "automate emulator testing", "/maestro-mobile-test".
  • A React Native / Expo feature just shipped and needs runtime proof beyond Jest component tests.
  • A flow involves native modules (expo-local-authentication, expo-secure-store, expo-notifications) that no browser-based tool can reach.

Do not auto-trigger for web app e2e (e2e-verify — Playwright/Expect), unit/component authoring (write-tests — RNTL + Jest), or API contract tests.

Prerequisites

RequirementHow to checkInstall
Java 17+java -versionbrew install --cask temurin@17 (macOS), sudo apt install openjdk-17-jdk (Linux), winget install Microsoft.OpenJDK.17 (Windows) — or run scripts/setup.sh which auto-installs
Android SDKecho $ANDROID_HOMEAndroid Studio
AVD (emulator image)emulator -list-avdsAndroid Studio → AVD Manager
Maestro CLImaestro --versioncurl -Ls https://get.maestro.mobile.dev | bash — or run scripts/setup.sh
Built APK (Android)Check android/app/build/outputs/apk/cd android && ./gradlew assembleDebug

Environment setup

The scripts/setup.sh handles everything — it detects your OS (macOS, Linux, Windows/git-bash) and installs the prerequisites in the right locations:

  • Java JDK 17+ — auto-installed via Homebrew (macOS), apt/dnf (Linux), or winget (Windows)
  • Android SDK — detected from standard locations (ANDROID_HOME, or ~/Library/Android/sdk, ~/AppData/Local/Android/Sdk, /opt/android-sdk)
  • Maestro CLI — installed to ~/.maestro/bin
bash skills/maestro-mobile-test/scripts/setup.sh

After setup, activate before running tests:

source ~/.maestro/activate.sh

The pitfalls that will cost you hours

These are not theoretical. Each one was learned by hitting it in a real Expo app.

⚠️ Pitfall 1: Dev builds are fragile under Maestro — use a debug APK

An Expo dev build (the kind you get from npx expo start) needs a live Metro bundler connection. Maestro's launchApp cold-starts the app, which drops the Metro connection. The app then shows the Expo Dev Launcher instead of your screens.

Strategy A — Dev build (local authoring only):

  1. Start npx expo start, connect the app to Metro before running Maestro
  2. Never use launchApp with clearState: true
  3. Use extendedWaitUntil to give the app time to reconnect
  4. WARNING: Even connected, dev builds break: back exits to the launcher, hideKeyboard backgrounds the app, rapid hot reloads crash native modules. Fine for authoring. Not fine for reliable runs.

Strategy B — Debug APK (recommended for reliable runs and CI):

cd android && ./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Now Maestro can launchApp freely — the JS bundle is compiled into the APK. No Metro, no fragility.

⚠️ Pitfall 2: The emulator cannot reach your localhost

The Android emulator runs in its own network namespace. localhost:8888 inside the emulator means the emulator itself — not your machine's backend.

Symptom: App shows "Connection error" / "Couldn't load" even though your backend runs fine on localhost:8888.

Fix — adb reverse (standard pattern, run before launching the app):

adb reverse tcp:8888 tcp:8888   # map emulator localhost:8888 → host localhost:8888
adb reverse tcp:8081 tcp:8081   # Metro, if using a dev build

This resets on emulator reboot — re-run it each session.

⚠️ Pitfall 3: timeout is NOT a property of assertVisible

# WRONG — "Unknown Property: timeout"
- assertVisible:
    text: "Welcome"
    timeout: 10000

# CORRECT — use extendedWaitUntil for timed waits
- extendedWaitUntil:
    visible: "Welcome"
    timeout: 10000

⚠️ Pitfall 4: Icon-only buttons need accessibility labels

MaterialIcons buttons have no text for Maestro to match. Add testID and accessibilityLabel to the source:

// Before — Maestro cannot tap this reliably (no text to match)

  


// After — Maestro taps by accessibility label

  

Then: - tapOn: "Add Item"

⚠️ Pitfall 5: hideKeyboard and back exit dev builds

On dev builds, both can send the app to the home screen. Dismiss the keyboard by tapping a non-interactive element instead:

# WRONG — can exit the app on dev builds
- hideKeyboard
- back

# CORRECT — tap a header to blur the input
- tapOn: "Screen Title"
- waitForAnimationToEnd

Maestro YAML command reference

appId: com.example.app
---
# Assertions
- assertVisible: "Text"
- extendedWaitUntil:
    visible: "Text"
    timeout: 20000

# Taps and input
- tapOn: "Button Label"
- tapOn:
    id: "add-button"
- doubleTapOn: "Text"
- longPressOn: "Text"
- inputText: "Hello"

# Navigation
- back
- swipe:
    direction: UP
- scrollUntilVisible:
    element: "Target Text"

# Waits
- waitForAnimationToEnd

# App control
- launchApp:
    appId: com.example.app
    clearState: false
- killApp: com.example.app

# Conditionals / branching (runFlow with when condition)
- runFlow:
    when:
      visible: "Welcome"
    commands:
      - tapOn: "Get Started"

# Repeat a sequence
- repeat:
    times: 3
    commands:
      - tapOn: "Next"

# Screenshots
- captureScreenshot: "step-name"

# Deep links
- openLink: myapp://screen/path

# Sub-flow inclusion (essential for suite patterns)
- runFlow: ../setup.yaml          # run another flow file
- runFlow: ../config.yaml         # shared config

# Visibility negation
- assertNotVisible: "Loading..."

# Text extraction (store for later assertions)
- copyTextFrom:
    id: "total-amount"
- assertTrue: ${output.total-amount} == "$54.20"

# Input helpers
- inputRandomEmail                # generates and types a random email
- pasteText                       # paste from clipboard

# Location mocking (travel)
- travel:
    points: "37.7749,-122.4194"

# Media injection (camera roll)
- addMedia:
    - "./test-assets/photo.jpg"

Directory convention

project-root/
├── .maestro/
│   ├── navigation/
│   │   ├── tab-nav.yaml
│   │   └── dashboard-elements.yaml
│   ├── features/
│   │   ├── create-item.yaml
│   │   └── view-list.yaml
│   └── suites/
│       ├── smoke.yaml            # fast: navigation + visibility (pre-merge)
│       └── regression.yaml       # full: all flows (pre-release)

Suite levels:

  • Smoke (~30s): structural checks — tabs work, screens load, elements visible. Every PR.
  • Regression (~3-5 min): full lifecycle tests — create/edit/delete. Before release.

Running tests

Headed (authoring/debugging — you watch the taps)

# Boot emulator
$ANDROID_HOME/emulator/emulator.exe -avd my-phone -no-snapshot-load &
adb wait-for-device
adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'

# Run a flow
maestro test .maestro/navigation/tab-nav.yaml

# Run a suite
maestro test .maestro/suites/regression.yaml

Headless (CI)

# Android: boot emulator headless
$ANDROID_HOME/emulator/emulator.exe -avd my-phone -no-window -no-audio -no-snapshot-load &
# Then run the same flows
maestro test .maestro/suites/regression.yaml

GitHub Actions CI (uses reactivecircus/android-emulator-runner to boot the emulator):

# .github/workflows/maestro-e2e.yml
jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          script: |
            # Install Maestro + app, then run flows
            curl -Ls "https://get.maestro.mobile.dev" | bash
            adb install -r android/app/build/outputs/apk/debug/app-debug.apk
            $HOME/.maestro/bin/maestro test .maestro/suites/smoke.yaml

iOS CI: Maestro supports iOS simulator in CI but requires a macOS runner with Xcode. Use xcrun simctl boot to boot the simulator instead of the Android emulator runner.

Writing durable flows (same discipline as e2e-verify Route B)

("Ration by journey risk" means: test only the user-facing flows that matter — login, checkout, CRUD — not every internal code path; push business logic to unit tests. "Prove red-capable" means: break the behavior, run, confirm the test fails; revert, run, confirm it passes. A test that's never been seen failing is unproven.)

  1. Ration flows by journey risk. Each flow is slow and has flake surface. Test through the UI only what is about the flow: the user journey, navigation, the wiring. Business-logic variations belong in unit tests.
  2. Selectors a user would recognize. accessibilityLabel > testID > text > coordinates. Never raw coordinates — they break on any layout change.
    • ❌ tapOn: { point: 93%,9% } — breaks on any layout change, catches nothing a user cares about.
    • ✅ tapOn: "Add Item" (backed by accessibilityLabel) — breaks only when the user's world breaks.
  3. No blind sleeps. waitForAnimationToEnd and extendedWaitUntil auto-wait.
  4. Prove red-capable (non-negotiable): break the behavior or the assertion target, run, quote the red; revert, quote the green. A flow never seen red is a decoration.
  5. Quote the run summary. A result you didn't observe is not run, never "passed."

Examples

Example 1: post-feature smoke → durable suite

User: "I just finished the item creation flow — test it on the emulator."

Claude: Builds a debug APK (not dev build — "dev builds are fragile under Maestro"), installs it, boots the emulator headed. Writes .maestro/features/create-item.yaml: navigate to Items → tap "Add Item" (via accessibilityLabel) → fill title + description → save → verify on list. Proves it red by running with a wrong assertion target, quotes both runs. Adds it to the smoke suite.

Example 2: "test my expo app"

User: "Test my expo app."

Claude: Checks for .maestro/ — none exists. Checks the app ID from app.json. Scaffolds the directory structure, writes a tab-nav.yaml smoke flow first (fastest signal), runs it headed so the user sees the taps. Then writes feature flows for the core journeys identified from the navigation structure.

Anti-patterns

  • ❌ Testing a dev build for reliable runs — Metro drops on cold launch, back exits the app. Use a debug APK.
  • ❌ Raw coordinate taps (point: 93%,9%) — weld the test to today's layout. Use accessibilityLabel.
  • ❌ assertVisible with a timeout — use extendedWaitUntil.
  • ❌ Writing a flow for every case a unit test could cover — ration by journey, push logic to Jest.
  • ❌ Shipping a flow never seen red — break it, quote the red, revert, quote the green.
  • ❌ Forgetting adb reverse — the app can't reach your backend and every flow reports "connection error."
  • ✅ Debug APK → adb reverse → headed authoring → accessibility-label selectors → prove red-capable → quote the run.

Troubleshooting

ProblemCauseFix
java not foundJava not installedInstall JDK 17+
App shows "Connection error"Emulator can't reach backendadb reverse tcp: tcp:
launchApp breaks dev buildCold start drops MetroUse debug APK, or don't use clearState: true
Unknown Property: timeoutassertVisible doesn't accept itUse extendedWaitUntil: { visible: ..., timeout: ... }
Flaky tap failuresElement not loaded yetAdd waitForAnimationToEnd or extendedWaitUntil before tapping
hideKeyboard exits the appDev build treats it as backTap a non-interactive element to blur

Scripts

This skill ships with reusable scripts in scripts/:

ScriptPurpose
scripts/setup.shOne-time install: Java 17, Maestro CLI, verifies Android SDK + AVDs
scripts/run_flow.shBoots emulator (if needed), sets up adb reverse, runs a flow
scripts/flow_template.yamlCopy-and-edit template for new Maestro flows

Quick start with scripts

# 1. One-time setup
bash skills/maestro-mobile-test/scripts/setup.sh

# 2. Run a flow (boots emulator if needed, sets up adb reverse)
source ~/.maestro/activate.sh
bash skills/maestro-mobile-test/scripts/run_flow.sh my-avd .maestro/navigation/tab-nav.yaml 8888

Related skills

Fetch raw ad creative, app, ranking, and revenue data from AdMapix as structured JSON.

by fly0pants4.3k installs296 stars

Generate and edit Draw.io, Mermaid, and Excalidraw diagrams from natural language using a structured JSON spec.

by nssa.io1.0k installs47 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai22 installs8 stars

Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.

by Iván559 installs18 stars

Find why your productivity system keeps failing, then apply the smallest fix — capacity math, bottleneck routing, durable local notes.

by Iván855 installs69 stars

More from dennisrongo

Browse all skills

Surface architectural friction in a codebase and propose **deepening opportunities** — refactors that turn shallow modules into deep ones, informed by the project's `CONTEXT.md` glossary and `docs/adr/` decisions. Walks the codebase with an Explore sub-agent, applies the **deletion test** to suspected pass-through modules, presents a numbered list of candidates with files / problem / solution / benefits, and drops into a grilling loop once the user picks one — naming new concepts into `CONTEXT.md` inline and offering an ADR only when a rejection is load-bearing. Writes no production code. Use this skill whenever the user says "improve architecture", "improve the architecture", "architecture review", "find refactoring opportunities", "find deepening opportunities", "find shallow modules", "make this more testable", "this code is hard to navigate", or invokes `/improve-codebase-architecture` — even if they don't name the skill.

by dennisrongo3 installs

Verify a change end-to-end in a real browser, routed by one question — who needs this check to run again? Ephemeral checks run via Expect (millionco/expect) or browser-use (AI agent driving headless Chromium — references/browser-use.md) when installed, else Claude driving the browser directly; regression-critical flows (auth, money, signup, checkout, deletion) get durable Playwright tests under write-tests discipline. Evidence rule either way — an AI-walked flow yields "no issues found in the paths walked" with observations quoted, never "e2e passes". Safety gate: never production, never real user cookies. Use this skill whenever the user says "verify this in the browser", "test it end to end", "test my web app", "e2e test this", "write playwright tests", "run expect", "run browser-use", "smoke test the UI", or "/e2e-verify" — even if they don't name the skill. Not for native mobile (maestro-mobile-test), unit/integration authoring (write-tests), or debugging a failing e2e test (diagno

by dennisrongo1 installs

Pre-commit SQL code review for uncommitted database changes. Detects antipatterns that cause real production incidents — broken TRY/CATCH error handling, swallowed errors, retry patterns without loops, tables missing PK/indexes, type-mismatch truncation, dynamic-SQL injection, NOLOCK in write paths, UPDATE/DELETE without WHERE, hardcoded env values, cross-DB refs, and more (full catalog in the skill body). Reports BLOCKER/WARN/INFO findings with file:line citations and fixes. Never edits SQL. Use this skill whenever the user says "/sql-review", "review my SQL", "review the SQL diff", "lint the SQL", "check my SQL changes", "SQL pre-commit check", "audit my stored proc", or asks about SQL antipatterns in a diff — even if they don't name the skill. Distinct from code-review (general review) — this carries SQL-specific incident patterns.

by dennisrongo1 installs

Scaffold a new Tauri 2 desktop app (Rust backend + TypeScript/React frontend) using a thin-frontend / rich-Rust-backend architecture with modular commands, trait-based platform abstractions, encrypted secrets at rest, single-instance enforcement, a self-hosted updater, and a cross-platform CI matrix. Three modes: full project scaffold, add a Tauri command slice, add a Rust module slice. Use this skill whenever the user says "create a new Tauri app", "scaffold a Tauri 2 project", "new desktop app with Tauri", "Tauri + React project", "add a Tauri command end-to-end", "add a Rust module to my Tauri app", "my Tauri conventions", or "/tauri-2-app" — even if they don't name the skill. Full good-pattern catalog and pitfall list live in the skill body.

by dennisrongo1 installs

Turn a feature idea, conversation context, or rough notes into researched, detailed, dependency-ordered checkbox tasks appended to the project's roadmap/backlog file — exactly the format goal-runner consumes, so a fresh unattended session can execute them. Grounds every task in the actual codebase before writing it (grep/read or Explore scouts; think-like-fable rigor if installed), puts what + done-when on the checkbox line with verified files, acceptance criteria, and a verification command in plain sub-bullets, and labels assumptions. Appends to the existing ROADMAP.md/BACKLOG.md/TODO.md; when none exists it asks once and defaults to ROADMAP.md. Use this skill whenever the user says "add this to the backlog", "add it to the roadmap", "plan these tasks", "break this down into tasks", "capture this as roadmap tasks", "turn these notes into a task list", or "/backlog-planner" — even if they don't name the skill. Not for executing tasks (goal-runner, task-executor, autopilot) — this writ

by dennisrongo1 installs

Produce a durable onboarding artifact for a codebase — writes ONBOARDING.md covering a "read this first" minimum, system overview, dependency map, startup flow, auth flow, and 5-15 important files, every claim backed by file:line citations; refreshes an existing onboarding doc instead of rewriting it. Use this skill whenever the user says "explain this codebase", "explain the codebase", "onboard me", "give me a tour", "tour this repo", "what does this repo do", "where do I start", "codebase overview", "read this first", "I haven't looked at this in months", or "/codebase-explainer" — even if they don't name the skill.

by dennisrongo1 installs