Mobile E2E Testing skill for Codex
Few journeys, stable ids, green on every run. Codex picks Maestro, Detox or native UI test frameworks based on the stack, makes the app testable with stable ids, launch arguments and API-seeded data, then writes a small set of critical journeys that wait on elements instead of sleeping. It wires the suite into CI on macOS simulators or KVM Android emulators with JUnit output, screenshots and smoke tags.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- mobile-e2e-testing Installs to ~/.agents/skills/mobile-e2e-testing/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install mobile-e2e-testing The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project mobile-e2e-testing Installs to .agents/skills/mobile-e2e-testing/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/mobile-e2e-testing.zipunzip -o mobile-e2e-testing.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) mobile-e2e-testing PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $mobile-e2e-testing, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes and stabilises mobile end-to-end tests with Maestro YAML flows and Detox for React Native, choosing between them and native XCUITest, Espresso or Appium - stable testID selectors, deterministic app state and test accounts, waits instead of sleeps, simulator and emulator setup, and CI on macOS runners or Android emulators with JUnit reports. Use when adding E2E tests to an iOS, Android, React Native, Expo or Flutter app, when mobile UI tests are flaky or slow, or when wiring Maestro or Detox into CI.
Use it when
- Adding E2E coverage to an iOS, Android, React Native or Flutter app
- Mobile UI tests are flaky, slow or tied to text and coordinates
- Wiring Maestro or Detox into CI with reports and screenshots
Use something else when
- You are testing a web app in a desktop or mobile browser: use playwright-e2e
- You need to set up mobile build and release pipelines: use mobile-ci-fastlane
How to check the result
Each new flow passes three consecutive runs from a clean install, and renaming a tapped element's id produces a clear failure with a screenshot.
The mistake it prevents
Selecting elements by visible text, index or coordinates, so copy changes, locales and screen sizes break the suite.
A mobile E2E suite is healthy when it covers a handful of critical journeys (sign in, purchase, core create/edit flow), starts every test from a known app state, finds elements by stable ids rather than text or coordinates, and runs green in CI on pinned simulator and emulator images. Device E2E is the most expensive layer; keep it small.
Inspect first
- Read
AGENTS.md,package.json,app.json/app.config.*,ios/andandroid/folders,pubspec.yaml. Identify: native, React Native (bare or Expo), Flutter. - Look for existing suites:
.maestro/,e2e/,.detoxrc.js,*UITeststargets,androidTest/. - Check what can run here: iOS simulators need macOS with Xcode (
xcrun simctl list devices); Android needs an SDK and emulator (emulator -list-avds). In a Codex sandbox, booting simulators, installing CLIs (curl -Ls "https://get.maestro.mobile.dev" | bash) and Gradle/CocoaPods downloads need network and usually approval. If no device can run, write the tests and the CI job, and say they are unverified locally. - Find how test users and backend data are provisioned (seed endpoint, staging accounts).
Choose the tool
| Situation | Default | Why |
|---|---|---|
| Any stack, black-box journeys, quick to write | Maestro | YAML flows, built-in tolerance for delays, works for native, RN, Flutter, web views |
| React Native, need synchronisation with JS, network and animations | Detox | Gray-box: waits for the app to be idle, fewer explicit waits |
| Pure native team already in Xcode / Android Studio | XCUITest / Espresso (Compose UI tests) | Best tooling and speed per platform |
| Real-device farm, WebDriver ecosystem, many languages | Appium | Broad device-cloud support; slower and more setup |
Default to Maestro unless the app is React Native and the team wants Detox’s idle synchronisation, or the team is native-only.
Make the app testable first
- Add stable ids to every element a test touches: React Native
testID, FlutterKey/Semantics(identifier:), iOSaccessibilityIdentifier, AndroidtestTag(Compose, withtestTagsAsResourceId = true) or view ids. - Provide a test entry: launch arguments or a deep link that sets the API base URL, disables animations and onboarding, and signs in a test user.
- Seed backend data per run through an API, not by tapping through setup screens.
- Disable or stub third-party SDK prompts (tracking consent, push permission) in test builds, or grant permissions at launch.
Maestro
# .maestro/checkout.yaml
appId: com.example.shop
tags:
- smoke
---
- launchApp:
clearState: true
arguments:
e2e: "true"
- runFlow: subflows/sign-in.yaml
- tapOn:
id: "product-card-0"
- tapOn:
id: "add-to-cart"
- tapOn:
id: "cart-tab"
- assertVisible: "1 item"
- tapOn: "Checkout"
- extendedWaitUntil:
visible: "Order confirmed"
timeout: 15000
- takeScreenshot: order-confirmed
# .maestro/subflows/sign-in.yaml
appId: com.example.shop
---
- tapOn:
id: "email-input"
- inputText: ${TEST_EMAIL}
- tapOn:
id: "password-input"
- inputText: ${TEST_PASSWORD}
- hideKeyboard
- tapOn:
id: "sign-in-button"
- assertVisible:
id: "home-screen"
maestro test .maestro/checkout.yaml -e [email protected] -e TEST_PASSWORD="$E2E_PASSWORD"
maestro test .maestro/ --include-tags=smoke --format junit --output build/maestro-junit.xml
maestro studio # interactive element inspector
Keep test credentials for dedicated test accounts in CI secrets and env vars, never in flow files.
Detox (React Native)
// e2e/checkout.test.js
describe("checkout", () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true, launchArgs: { e2e: "true" }, permissions: { notifications: "YES" } });
});
beforeEach(async () => {
await device.reloadReactNative();
});
it("buys one item", async () => {
await element(by.id("product-card-0")).tap();
await element(by.id("add-to-cart")).tap();
await element(by.id("cart-tab")).tap();
await expect(element(by.text("1 item"))).toBeVisible();
await element(by.text("Checkout")).tap();
await waitFor(element(by.text("Order confirmed"))).toBeVisible().withTimeout(15000);
});
});
npx detox build -c ios.sim.release
npx detox test -c ios.sim.release --record-logs failing --take-screenshots failing
npx detox test -c android.emu.release --headless
.detoxrc.jsdefinesapps(binary path and build command),devices(simulator type, AVD name) andconfigurationsjoining them. Pin the simulator model and OS.- Use release builds in CI so tests do not depend on Metro; debug builds for local iteration.
- Android needs the Detox instrumentation test class and Gradle changes from the Detox setup guide; Expo apps need a prebuild or dev client plus the Detox config plugin.
- If idle sync hangs on a looping animation or long-polling request, scope the fix
(
device.setURLBlacklist([...]), disable the animation in e2e mode) before reaching fordevice.disableSynchronization().
Flake control
| Cause | Fix |
|---|---|
| Fixed sleeps | extendedWaitUntil (Maestro) / waitFor().withTimeout() (Detox) on the target element |
| Text selectors break with copy or locale changes | Use ids; set device locale explicitly |
| Leftover state from previous test | clearState: true / newInstance: true plus device.clearKeychain() where needed (iOS) |
| System dialogs (permissions, keyboard, rate-app) | Grant permissions at launch, hideKeyboard, disable prompts in e2e builds |
| Shared backend test account mutated by parallel runs | One account per shard or per run |
| Slow cold emulator | Boot and warm up before tests; snapshot AVDs; disable animations (adb shell settings put global animator_duration_scale 0 and the window/transition scales) |
CI
- iOS: macOS runners with a pinned Xcode version and simulator; cache the built app between build and test jobs.
- Android: Linux runners with KVM can run the emulator (for example
reactivecircus/android-emulator-runneron GitHub Actions); pin API level and system image. - Hosted options (Maestro Cloud, EAS Workflows, device farms) avoid simulator upkeep; uploading builds to them needs network and account tokens.
- Publish JUnit XML, screenshots and videos as artifacts on failure.
- Run the smoke tag on every PR; the full suite on merge or nightly.
Verification
- Run each new flow three times in a row locally or in CI; all green.
- Break the journey on purpose (rename the button id) and confirm a clear failure with a screenshot.
- Confirm the test leaves no state that changes the next run’s outcome (run the suite twice without reinstalling).
Gotchas
- Coordinates and index-based taps break on a different screen size; never commit them.
- React Native
testIDon a custom component does nothing unless it is passed down to a native view. - Expo Go is not a test target; build the actual app (dev client or release build).
- iOS simulator keychain survives app reinstalls; signed-in state can leak between runs.
- Running E2E against production backends creates real orders and emails; use a staging or mocked backend and dedicated test accounts.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Mobile E2E Testing"
short_description: "Few journeys, stable ids, green on every run"
default_prompt: "$mobile-e2e-testing Add Maestro tests for sign-in and checkout to our Expo app" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.