For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

E2E tests

Run Maestro end-to-end tests on iOS and Android against a production API build. Covers flows, testIDs, EAS profiles, and CI.

Mobile E2E tests verify real user flows on iOS and Android: welcome screens, sign-in, onboarding, and sign-out. Tests use Maestro against a native app build and a production API server, so you catch issues that only appear on device. See the Maestro docs for the full reference.

Why Maestro?

Maestro uses YAML flows that are easy to read and maintain. It handles waits, retries, and platform quirks (like iOS keychain prompts) without boilerplate. Flows run on real emulators and simulators, giving you confidence that gestures, navigation, and native UI behave correctly.

Maestro test run

Prerequisites

  1. Start services: Postgres must be running for the API server:
pnpm services:setup
  1. Environment files:
cp .env.example .env
cp apps/web/.env.example apps/web/.env.local
cp apps/mobile/.env.example apps/mobile/.env
  1. Install Maestro (first time only):
curl -Ls "https://get.maestro.mobile.dev" | bash
  1. Build the app: E2E tests need a native build. Use the EAS e2e profile:
cd apps/mobile
eas build --platform android --profile e2e --local
# or
eas build --platform ios --profile e2e --local

The e2e profile in eas.json produces an APK (Android) or simulator build (iOS) with the correct EXPO_PUBLIC_SITE_URL for reaching your local API:

PlatformEXPO_PUBLIC_SITE_URLWhy
Android emulatorhttp://10.0.2.2:3000Emulator loopback alias for host machine
iOS simulatorhttp://127.0.0.1:3000Localhost on the Mac host
  1. Start the API server: the mobile app needs a running backend:
pnpm with-env pnpm turbo build --filter=web
pnpm with-env pnpm --filter web start -H 0.0.0.0 -p 3000

Test structure

E2E tests live in apps/mobile/e2e/:

apps/mobile/e2e/
├── config.yaml              # Flow discovery and execution order
├── flows/
│   ├── welcome.yaml         # Welcome screen smoke test
│   └── auth/
│       ├── sign-in-password.yaml
│       └── sign-out.yaml
├── subflows/
│   └── complete-setup.yaml  # Reusable onboarding steps
└── test-results/            # JUnit reports and debug output

Flow execution order

config.yaml defines which flows run and in what execution order:

apps/mobile/e2e/config.yaml
flows:
  - "flows/**"
executionOrder:
  continueOnFailure: false
  flowsOrder:
    - welcome
    - sign-in-password
    - sign-out

Flows run sequentially: welcome → sign-in → sign-out. The sign-out flow depends on the sign-in flow leaving the user on the dashboard.

Example flows

Test IDs

Mobile screens expose testID props for Maestro to target. This keeps flows stable when copy or styling changes.

Add testID to any new interactive element you want to cover in Maestro flows.

Welcome screen

apps/mobile/e2e/flows/welcome.yaml
appId: com.turbostarter.core
---
- launchApp:
    clearState: true
    clearKeychain: true
- extendedWaitUntil:
    visible:
      id: welcome-get-started
    timeout: 120000
- assertVisible:
    id: welcome-get-started
- assertVisible:
    id: welcome-login

clearState and clearKeychain ensure each run starts fresh, with no leftover sessions from previous tests.

Sign in with password

The sign-in flow navigates from welcome → login → onboarding → dashboard:

apps/mobile/e2e/flows/auth/sign-in-password.yaml
appId: com.turbostarter.core
env:
  E2E_USER_EMAIL: me+user@turbostarter.dev
  E2E_USER_PASSWORD: "Pa$$w0rd"
---
- launchApp:
    clearState: true
    clearKeychain: true
- tapOn:
    id: welcome-login
- tapOn:
    id: login-email-input
- inputText: ${E2E_USER_EMAIL}
- tapOn:
    id: login-password-input
- inputText: ${E2E_USER_PASSWORD}
- pressKey: Enter
- runFlow: ../../subflows/complete-setup.yaml
- assertVisible:
    id: dashboard-home

Subflows like complete-setup.yaml handle reusable steps (checkboxes, skip paywall) so you do not duplicate onboarding logic across flows.

Running tests

All flows

pnpm --filter mobile test:e2e

Single flow

pnpm --filter mobile exec maestro test e2e/flows/welcome.yaml

With JUnit output

pnpm --filter mobile exec maestro test e2e \
  --format junit \
  --output e2e/test-results/report.xml

Maestro Studio (interactive)

maestro studio

Maestro Studio

Maestro Studio lets you record, edit, and replay flows visually, which is useful when building new tests.

CI

The CI / E2E / Mobile workflow runs on pull requests labeled e2e or e2e-mobile. It has two jobs:

  • Android: builds the app via EAS, starts an API server, boots a Pixel 6 emulator, and runs Maestro flows
  • iOS: builds via EAS, starts PostgreSQL natively, boots an iPhone simulator, and runs the same flows

Both jobs upload JUnit reports and debug artifacts. Mobile CI requires an EXPO_TOKEN secret for EAS builds.

Resource requirements

Mobile E2E CI is the most resource-intensive pipeline. Android runs need KVM-enabled runners with a 4 GB emulator. iOS runs need macos-latest. Use the e2e-mobile label only when mobile changes need verification.

Writing new flows

  1. Add testIDs to the components you want to interact with
  2. Create a YAML flow in apps/mobile/e2e/flows/
  3. Register it in config.yaml if it should run as part of the suite
  4. Use subflows for steps shared across multiple flows (onboarding, dismiss dialogs)
  5. Handle platform dialogs: iOS "Save Password?" prompts are dismissed with repeat/while blocks in existing flows

Tips for reliable flows

  • Use extendedWaitUntil instead of fixed sleep values
  • Set clearState: true and clearKeychain: true on launchApp for auth flows
  • Use pressKey: Enter to submit forms instead of hunting for submit buttons
  • Mark optional taps with optional: true when an element may not appear (e.g. tab already selected)

Next steps

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter