Maestro drives your installed app on a simulator or emulator with declarative YAML flows - tap, scroll, assert visible text - without writing native test harness code. Pair it with EAS builds so every PR runs the same journeys CI runs locally.
# Local run (after installing Maestro CLI and a debug build on emulator/simulator)maestro test .maestro/home.ymlmaestro test .maestro/# Trigger EAS Workflow manuallynpx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml
What this demonstrates:
appId matches android.package / ios.bundleIdentifier from app.json.
Regex matchers - Tasks.* and Explore.* tolerate minor label changes and platform text differences.
e2e-test build profile - produces .apk (Android) and simulator .app (iOS) without store credentials.
EAS maestro job - installs the build artifact from build_id and runs flow_path flows.
Independent flows - each YAML file is one journey; failures pinpoint the broken screen.
Maestro CLI connects to a running emulator/simulator (or physical device) and launches the app by appId.
Each flow is a ordered list of commands (launchApp, tapOn, inputText, assertVisible, scrollUntilVisible, …) executed against the live UI tree.
Maestro uses accessibility labels and visible text - add testID / accessibilityLabel in React Native when copy alone is ambiguous.
EAS Workflowstype: build produces an installable binary; type: maestro downloads that binary to the runner and executes flows - no manual artifact upload.
Flows are black-box - they do not import your JS; changing implementation does not break tests if the user-visible UI stays the same.
Wrong appId - Flow launches a different app or fails immediately. Fix: Copy android.package / ios.bundleIdentifier from app.json after eas build:configure, not the Expo slug.
Testing in Expo Go with production appId - Expo Go's application id is host.exp.exponent, not your bundle ID. Fix: Install an EAS dev or preview build on the emulator before maestro test.
Mega-flows that are hard to debug - One YAML file covering onboarding → paywall → settings hides which step failed. Fix: One journey per file; share login via runFlow.
Brittle exact-string assertions - "Welcome" fails when copy becomes "Welcome!" or includes trailing space. Fix: Regex assertions (Welcome!, Welcome.*) and testID for critical taps.
Shared backend state between flows - Parallel CI runs mutate the same test user. Fix: Dedicated test tenants, idempotent seed scripts, or per-run accounts.
No wait for async content - assertVisible runs before fetch completes - intermittent failures. Fix:extendedWaitUntil or assertVisible after a stable loading indicator disappears.
Assuming Maestro replaces Jest - E2E is slow and does not cover every branch. Fix: Keep unit/component tests (Jest Setup for Expo, RNTL); use Maestro for thin critical paths.
Follow Installing Maestro CLI. You need a booted emulator/simulator with your app binary installed before maestro test.
Where do flow files live?
Convention: .maestro/ at the project root (sibling to eas.json). Name flows by journey (login.yml, checkout-guest.yml), not by sprint number.
What is the --- separator in YAML flows?
Lines above --- are config (appId, optional name, env). Lines below are the command list executed in order.
How do I run all flows?
maestro test .maestro/
Runs every flow in the directory. In CI, list explicit paths in flow_path so experimental drafts are not picked up accidentally.
How does EAS know which app binary to install?
The maestro job build_id references the artifact from the build job in the same workflow. Build with the e2e-test profile so the output is an installable .apk or simulator .app.
Can I run Maestro on iOS and Android in one workflow?
Use separate workflow files (or jobs) per platform - platform: ios vs platform: android builds produce different artifacts. Share YAML flows when UI parity is close; branch with platform-specific flows when not.
How do I target an element with duplicate text?
- tapOn: id: "submit-button"
Add testID in React Native. Prefer id over coordinate taps - coordinates break across screen sizes.
Keep subflows in .maestro/subflows/ and reference with runFlow.
What build profile should e2e-test use?
withoutCredentials: true - internal CI builds without store signing setup.
Android buildType: "apk" - installable on emulator without Play signing dance.
iOS simulator: true - produces a simulator .app, not an App Store IPA.
Maestro vs Detox for Expo SDK 57?
Maestro: YAML, black-box, EAS Workflow job type, low setup. Detox: JS tests, gray-box sync, native build config - see Detox E2E when you need in-app test IDs and idle synchronization.
How do I reduce flakes in CI?
Pin emulator API level in CI to match local dev.
Seed backend data before flows.
Use extendedWaitUntil for network-bound screens.
Split long flows; retry policies help but fix root timing issues first.
Should I commit .maestro to git?
Yes - flows are source code. Review them like application changes. Exclude local Maestro debug output or screen recordings if your team generates them during development.
How many E2E flows do I need?
Cover critical paths only - auth, pay, data loss, regulatory screens. Exhaustive branch coverage stays in Jest. See Mobile Testing Basics for pyramid balance.