π§ͺ E2E Testing (Maestro) β
End-to-end smoke tests for the mobile app, driven by Maestro. Flows run locally against an Android emulator or iOS Simulator, and in CI on workflow dispatch. The smoke suite is the release gate β run it green before submitting a staging build.
NOTE
Flows, the full tag matrix, and per-flow status live in apps/mobile/.maestro/README.md. This page is the setup + usage guide; that README is the authoring reference.
How it fits together β
- Flows are YAML files in
apps/mobile/.maestro/. Top-levelnn-name.yamlare individual scenarios;00-authed-suite.yamlis an orchestrator that logs in once and runs ~13 sub-flows fromscenarios/. Shared steps live inhelpers/. - Tags select what runs. Default is
smoke(cross-platform critical path). Override withMAESTRO_INCLUDE_TAGS. - Pipelines are shell scripts (
scripts/e2e.shfor Android,scripts/e2e-ios.shfor iOS) wrapped bypnpmscripts. They build a staging-variant release of the app with the JS bundle baked in (no Metro server needed), install it, and run the flows.
IMPORTANT
E2E always builds the staging app variant (com.tellia.mobile.staging, APP_VARIANT=preview) pointed at the staging backend β never your local dev build. Test accounts only exist in the tell-ia-staging Firebase project.
One-time setup β
1. Simulator / emulator β
You need a working iOS Simulator (Xcode + a runtime + CocoaPods) or Android emulator (Android Studio + an AVD + ANDROID_HOME). See Local Dev Setup β simulators β same toolchain.
2. Install the Maestro CLI β
pnpm --filter @tellia-solutions/mobile-app e2e:install-cli # curl -Ls https://get.maestro.mobile.dev | bashAdd Maestro to your PATH if the installer prompts you to (export PATH="$PATH:$HOME/.maestro/bin" in ~/.zshrc). Verify:
maestro --version3. Test accounts + env β
Pipelines source apps/mobile/.maestro/.env. Copy the example and fill it in:
cp apps/mobile/.maestro/.env.example apps/mobile/.maestro/.enviOS and Android use separate staging accounts (each owns its own company) so parallel runs don't share company-scoped drafts/reports. The pipeline picks the matching suffix via E2E_PLATFORM; flow YAMLs read the unsuffixed canonical names.
| Variable | Purpose |
|---|---|
MAESTRO_TEST_EMAIL_ANDROID / _PASSWORD_ANDROID | Android staging smoke account |
MAESTRO_TEST_EMAIL_IOS / _PASSWORD_IOS | iOS staging smoke account |
EXPO_PUBLIC_MAPBOX_ACCESS_TOKEN | pk.* token β baked into the bundle so map tiles load (else blank map) |
MAESTRO_TEST_PHONE_* / MAESTRO_TEST_OTP | Only for phone-auth flows; needs Firebase test phone numbers |
IMPORTANT
The account passwords and Mapbox token come from the Mobile ENV 1Password item. Create the staging accounts in the Firebase Console first β see E2E_TEST_ACCOUNTS.md.
Running the suite β
Full pipeline β
full runs doctor β boot β build β install β run end to end. First run builds the native release (several minutes); later runs reuse it.
# Android (emulator must have an AVD configured)
pnpm --filter @tellia-solutions/mobile-app e2e:android
# iOS (Xcode + Simulator runtime + CocoaPods)
pnpm --filter @tellia-solutions/mobile-app e2e:iosRe-run flows only β
After the app is already built + installed, skip straight to the flows β fast iteration loop:
pnpm --filter @tellia-solutions/mobile-app e2e:android:run
pnpm --filter @tellia-solutions/mobile-app e2e:ios:run
# A single flow or a tag
pnpm --filter @tellia-solutions/mobile-app e2e:ios:run .maestro/05-login-wrong-password.yaml
MAESTRO_INCLUDE_TAGS=group-1-auth pnpm --filter @tellia-solutions/mobile-app e2e:android:runPipeline subcommands β
Each stage is also exposed individually (handy when debugging a specific step):
pnpm script (per platform) | What it does |
|---|---|
e2e:<platform>:doctor | Check prerequisites (SDK, adb/xcrun, maestro, device) |
e2e:<platform>:boot | Start an emulator/simulator if none running (idempotent) |
e2e:<platform>:build | Build the staging release with JS bundled |
e2e:<platform>:install | (Re-)install the build on the device |
e2e:<platform>:run [path|tag] | Execute flows with reports + debug artifacts |
e2e:<platform>:studio | Launch Maestro Studio against the running app |
e2e:<platform>:clean | Uninstall the app from the device |
e2e:<platform>:kill | Shut down the emulator/simulator |
TIP
Start with doctor β it tells you exactly which prerequisite is missing (SDK path, no AVD, maestro not on PATH) before you wait on a build.
Useful env overrides β
| Variable | Effect |
|---|---|
MAESTRO_INCLUDE_TAGS | Tag filter (default smoke). e.g. smoke,permissions-deny |
IOS_SIMULATOR | Pin the iOS device, e.g. IOS_SIMULATOR="iPhone 17 Pro" (iOS only) |
AVD_NAME | Pick a specific AVD (Android only; default = first emulator -list-avds) |
ANDROID_HOME | Android SDK root (default ~/Library/Android/sdk) |
Reports β
Each run writes to apps/mobile/.maestro/reports/<timestamp>/, with an HTML report, device logs, and per-flow screenshots on failure. .maestro/reports/latest points at the most recent run. Open the report:
open apps/mobile/.maestro/reports/latest/report.htmlMaestro Studio (recorder / inspector) β
Studio walks the running app and records taps/asserts as YAML, and its "Inspect" view confirms testID β accessibilityIdentifier parity (important for Mapbox / ActionSheet wrappers on iOS).
pnpm --filter @tellia-solutions/mobile-app e2e:ios:studio # auto-installs+launches, pins to the simulator UDID
pnpm --filter @tellia-solutions/mobile-app e2e:android:studio # auto-picks the running emulatorStudio opens at http://localhost:9999.
Pre-submit gate β
Before eas submit --profile staging --platform <android|ios>, run the matching e2e:<platform> pipeline and confirm green. The smoke suite is the release gate today β there's no automated enforcement, just the convention. If a flow fails, fix it (or document a Maestro bug) before submitting; don't submit on red.
Authoring flows (essentials) β
Full guidance is in the .maestro/README.md β the load-bearing rules:
testIDneeds a button role onPressables. Bare<Pressable testID="x">doesn't surfaceaccessibilityIdentifierreliably β addaccessibilityRole="button"(the sharedButtonalready does).- Inside an ActionSheet, never call
hideKeyboardbetweeninputTextandtapOn. Maestro maps it to Android BACK, which dismisses the sheet. - Cross-platform first. Default to one
nn-name.yaml; only fork into*.android.yaml/*.ios.yamlwhen platform commands genuinely diverge. - Tag every new flow β at minimum
smoke(cross-platform) orplatform-<os>-only. - Strip newlines from labels and typed text β Maestro's
text:substring match silently fails when the target contains a\n.
Troubleshooting β
| Symptom | Fix |
|---|---|
doctor reports maestro not found | Re-run e2e:install-cli, add ~/.maestro/bin to PATH |
| "No AVD configured" (Android) | Create a Pixel API 33+ device in Android Studio Device Manager |
| Map renders blank / reload icon | EXPO_PUBLIC_MAPBOX_ACCESS_TOKEN missing in .maestro/.env (must be pk.*) |
| Login fails on every flow | Wrong/empty MAESTRO_TEST_* creds, or account doesn't exist in staging Firebase |
text: assertion never matches | Target string contains a newline β keep labels/transcripts single-line |
| App installs but is the wrong env | E2E must build the preview/staging variant β use the pipeline, not a dev build |