maestro-mobile-testing

安装量: 76
排名: #10224

安装

npx skills add https://github.com/tovimx/maestro-mobile-testing-skill --skill maestro-mobile-testing

Maestro Mobile E2E Testing Overview Maestro is a declarative YAML-based mobile E2E testing framework. It provides automatic waiting, built-in retry logic, and fast execution without boilerplate. It's more stable than Detox or Appium for React Native apps. Key Features Declarative YAML — no imperative test code, just steps Automatic waiting — no manual sleep() or flaky waits Built-in retry — reduces test flakiness Fast execution — runs quickly without setup overhead Maestro Studio — interactive test builder ( maestro studio ) Sub-flows — reusable YAML sequences for DRY tests JavaScript scripting — GraalJS runtime for HTTP calls and data manipulation Maestro Cloud — real device testing in CI without local simulators Quick Start Install curl -Ls "https://get.maestro.mobile.dev" | bash brew install openjdk@17 export JAVA_HOME = /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home Minimal test appId : com.myapp


- launchApp - tapOn : id : "my-button" - assertVisible : "Expected Text" Run maestro test .maestro/smoke-test.yaml maestro test --debug .maestro/smoke-test.yaml

step through

maestro studio

interactive builder

Core Patterns 1. Selector Strategy: testID vs Text Choose your selector approach based on project context. Both are valid — the right choice depends on whether your app is localized and your team's testing philosophy. Context Recommended Selector Rationale Multi-language / i18n id: (testID) Stable across translations Single language Text labels Human-readable, self-documenting tests Agent-maintained tests Either — ask the developer Readability matters less for AI-maintained flows System dialogs Text (always) No testID possible on native alerts

testID selector — stable across translations

- tapOn : id : "submit-button"

Text selector — human-readable, self-documenting

- tapOn : "Submit" When to prefer testIDs: App supports multiple languages or will be translated UI text is dynamic or frequently changes Multiple elements share the same visible text When to prefer text selectors: Single-language app with stable copy Readability and self-documentation are a priority Testing user-visible behavior exactly as it appears In React Native, add testID props when using ID-based selectors: < TouchableOpacity testID = " submit-button " onPress = { handleSubmit }

< Text

{ t ( 'submit' ) } </ Text

</ TouchableOpacity

testID Naming Convention When using ID-based selectors: {component}-{action/type}[-{variant}] Examples: - auth-prompt-login-button - product-card-{id} - otp-input-0 - tab-home - dashboard-loading 2. Auth Pre-Flight Pattern Prevent race conditions where Maestro interacts with the UI before auth state resolves. Add a zero-size auth-loaded marker that only renders when auth loading completes: // In your tab bar or root layout { ! isLoading && < View testID = " auth-loaded " style = { { width : 0 , height : 0 } } /> } Then in every test: - launchApp

Prevent XCTest crash on cold boot (iOS)

- swipe : direction : DOWN duration : 100

Wait for auth state to resolve

- extendedWaitUntil : visible : id : "auth-loaded" timeout : 15000

Now safe to interact

- tapOn : id : "tab-home" 3. Adaptive Tests (Handle Both Auth States) Tests should work regardless of whether the user is authenticated:

Auth flow — only runs if login prompt is visible

- runFlow : when : visible : "Sign In" file : flows/auth - flow.yaml

Already authenticated — proceed directly

- runFlow : when : visible : id : "tab-home" file : flows/authenticated - action.yaml 4. Testing Optimistic Updates Use short timeouts to verify UI changes happen before server response:

Trigger mutation

- tapOn : id : "action-button"

OPTIMISTIC: UI must change within 3s (not waiting for server)

- extendedWaitUntil : visible : id : "undo-button" timeout : 3000

Verify derived UI state

- extendedWaitUntil : visible : id : "user-indicator" timeout : 5000 Action Expected Change Timeout Mutation trigger Button state flips < 3s List update Item appears/disappears < 5s Re-do action Proves persistence < 3s 5. Dismissing Native Alerts React Native Alert.alert() creates native dialogs that block the UI: - tapOn : id : "action-button"

Wait for expected state change first

- extendedWaitUntil : visible : id : "new-state-element" timeout : 5000

Dismiss alert (optional in case it already closed)

- tapOn : text : "OK" optional : true

Brief delay for alert animation

- swipe : direction : DOWN duration : 300 6. Sub-Flows for Reusability Break repeated sequences into sub-flow files: .maestro/ ├── flows/ │ ├── auth-and-return.yaml │ ├── complete-purchase.yaml │ └── verify-result.yaml ├── smoke-test.yaml └── feature-test.yaml

In main test

- runFlow : file : flows/auth - and - return.yaml 7. Deep Links (Expo) Use the Expo scheme from app.json , not the bundle ID:

WRONG

- openLink : "com.myapp://profile/settings"

CORRECT

- openLink : "myapp://profile/settings" Deep links must be registered in your app's deep link handler. Unregistered routes silently fail. 8. Platform-Specific Logic - runFlow : when : platform : ios file : flows/ios - specific.yaml - runFlow : when : platform : android file : flows/android - specific.yaml 9. Environment Variables appId : com.myapp env : TEST_EMAIL : maestro - test@example.com API_BASE_URL : http : //localhost : 3000


- inputText : $ { TEST_EMAIL } 10. Selector State Properties Use enabled , selected , checked , and focused to target elements by their current state. This is useful for validating interactive element states before or after actions.

Only tap the submit button if it's enabled

- tapOn : id : "submit-button" enabled : true

Assert a checkbox is checked

- assertVisible : id : "terms-checkbox" checked : true

Wait for an input to be focused

- extendedWaitUntil : visible : id : "email-input" focused : true timeout : 3000 Property Values Use Case enabled true / false Buttons that disable during submission or until form is valid checked true / false Checkboxes, toggle switches selected true / false Tab items, segmented controls focused true / false Input fields with auto-focus 11. Relative Position Selectors Distinguish between similar elements by their spatial relationship to other elements. This is more idiomatic and resilient than index-based selection.

BAD — fragile, breaks if order changes

- tapOn : text : "Add to Basket" index : 1

GOOD — contextual, self-documenting

- tapOn : text : "Add to Basket" below : text : "Awesome Shoes" Available relative selectors:

Target element below another

- tapOn : text : "Buy Now" below : "Product Title"

Target element that is a child of a parent

- tapOn : text : "Delete" childOf : id : "item-card-42"

Target a parent that contains a specific child

- tapOn : containsChild : "Urgent"

Target by multiple descendants

- tapOn : containsDescendants : - id : title_id text : "Specific Title" - "Another descendant text"

Horizontal positioning

- tapOn : text : "Edit" rightOf : "Username" Selector Meaning below: Element is positioned below the referenced element above: Element is positioned above the referenced element leftOf: Element is to the left of the referenced element rightOf: Element is to the right of the referenced element childOf: Element is a direct child of the referenced parent containsChild: Element contains a direct child matching the reference containsDescendants: Element contains all specified descendant elements Authentication Testing Architecture Testing OTP or magic-link authentication in E2E requires capturing emails programmatically. The general pattern: ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Maestro │────▶│ Auth │────▶│ Email Capture │ │ Test │ │ Provider │ │ Service │ └─────────────┘ └──────────────┘ └─────────────────┘ │ │ │ ┌──────────────────────────────┘ │ ▼ │ ┌─────────────┐ └──▶│ REST API │ ─── GET /api/v1/messages │ (email) │ ─── Extract OTP code └─────────────┘ Common email capture services: Mailpit , MailHog , Ethereal . OTP Fetch Script Fetch OTP codes from your email capture service using Maestro's GraalJS runtime: // CRITICAL: Maestro uses GraalJS — NO async/await, NO fetch() var email = typeof EMAIL !== "undefined" ? EMAIL : "test@example.com" ; var emailServiceUrl = typeof EMAIL_SERVICE_URL !== "undefined" ? EMAIL_SERVICE_URL : "http://localhost:8025" ; var response = http . get ( emailServiceUrl + "/api/v1/messages" ) ; if ( ! response . ok ) { throw new Error ( "Failed to fetch emails: " + response . status ) ; } var data = json ( response . body ) ; // Find the latest email and extract OTP code var body = data . messages [ 0 ] . Content . Body ; var match = body . match ( / ( \d {6} ) / ) ; output . OTP_CODE = match [ 1 ] ; OTP Input Strategy OTP components with auto-focus need individual digit entry. Tap each input before typing:

Split OTP into digits via helper script

- runScript : file : scripts/split - otp.js env : OTP_CODE : $ { output.OTP_CODE }

Enter each digit by tapping its input

- tapOn : id : "otp-input-0" - inputText : $ { output.OTP_0 } - tapOn : id : "otp-input-1" - inputText : $ { output.OTP_1 }

... repeat for all digits

For provider-specific implementations (Supabase + Mailpit, Firebase Auth, Auth0), create a project-level skill that extends this one. GraalJS Script Rules Maestro uses the GraalJS runtime. These constraints are non-negotiable: Feature Status async/await NOT supported fetch() NOT supported http.get() , http.post() Use these instead json() Use to parse response bodies output.VAR Set variables for use in YAML flow var declarations Required (use var , not const / let for safety) // Script template var response = http . get ( "http://localhost:8025/api/endpoint" ) ; if ( ! response . ok ) { throw new Error ( "Request failed: " + response . status ) ; } var data = json ( response . body ) ; output . RESULT = data . value ; Critical Gotchas clearState Does NOT Clear iOS Keychain clearState: true clears the app sandbox (UserDefaults, files, caches) but does NOT clear the iOS Keychain. Auth tokens stored via expo-secure-store (or any Keychain-based storage) persist across clearState resets and even app reinstalls.

WRONG — user may still be authenticated

- launchApp : clearState : true - assertVisible : "Welcome"

Fails if Keychain has tokens

CORRECT — wait for auth resolution, then adapt

- launchApp - extendedWaitUntil : visible : id : "auth-loaded" timeout : 15000 Rules: Never rely on clearState to produce guest state on iOS For auth tests: skip clearState , use auth-loaded pre-flight For guest tests: use adaptive flows that handle both states Never assert guest-only UI after clearState Note: On Android, clearState: true fully resets app data including credentials. This is an iOS-only gotcha. XCTest kAXErrorInvalidUIElement Crash (iOS) The XCTest driver may crash if Maestro interacts with the accessibility tree before the first render cycle completes on cold boot. Fix: Add a no-op swipe immediately after launchApp : - launchApp - swipe : direction : DOWN duration : 100 API Server Dependency Mobile apps calling backend APIs on localhost need either the full server or a mock server running. Without it, all API-dependent screens show loading spinners or empty states (queries fail silently). Fix: Start a mock API server before running Maestro tests:

Start mock server (serves canned responses on your API port)

npx tsx scripts/mock-api-server.ts &

Then run tests

maestro test .maestro/my-test.yaml Create a lightweight mock that returns canned JSON for each endpoint your app calls. This is faster and more deterministic than running your full backend. Auth-Aware Tab Bars Tab bars that show different tabs for guest vs authenticated users will cause selector failures: State Typical Tabs Guest home, search, cart, profile Auth home, feed, create, messages, profile Only assert tabs that exist in both states, or use adaptive when: conditions. Test File Template

{Feature} {Action} Test

Tests:

Prerequisites:

- Simulator/emulator running with app installed

- Backend or mock server running (if API-dependent)

appId : com.myapp env : TEST_EMAIL : maestro - { feature } @example.com EMAIL_SERVICE_URL : http : //localhost : 8025


==========================================

STEP 1: LAUNCH + AUTH PRE-FLIGHT

==========================================

- launchApp - swipe : direction : DOWN duration : 100 - extendedWaitUntil : visible : id : "auth-loaded" timeout : 15000 - takeScreenshot : 01 - initial - state

==========================================

STEP 2:

==========================================

- tapOn : id : "target-element"

==========================================

STEP 3: VERIFY

==========================================

- extendedWaitUntil : visible : id : "expected-result" timeout : 5000 - takeScreenshot : 02 - final - state Folder Structure .maestro/ ├── README.md # Quick reference + testID inventory ├── config.yaml # Shared configuration ├── flows/ # Reusable sub-flows │ ├── auth-and-return.yaml │ ├── complete-action.yaml │ └── verify-result.yaml ├── scripts/ # GraalJS helpers │ ├── fetch-otp.js │ └── split-otp.js ├── smoke-test.yaml # Guest navigation ├── auth-signin.yaml # OTP sign-in flow ├── feature-screenshots.yaml # Screenshot capture flows └── feature-action.yaml # Feature-specific tests scripts/ ├── mock-api-server.ts # Lightweight mock for E2E └── run-e2e.sh # Orchestration script Naming Conventions Type Pattern Example Main test {feature}-{action}.yaml checkout-purchase.yaml Sub-flow {action}-{context}.yaml auth-and-return-to-dashboard.yaml Script {verb}-{noun}.js fetch-otp.js Infrastructure Mock Server for API-Dependent Tests Create a lightweight mock server that serves canned responses for your API layer. This is faster and more deterministic than running your full backend during E2E tests.

Start mock server before running Maestro tests

npx tsx scripts/mock-api-server.ts & Orchestration Script Automate the full E2E setup with a shell script that: Starts backend services (database, auth) Seeds test data Starts mock API server Runs Maestro tests Cleans up all services bash scripts/run-e2e.sh Seed Data Tests that depend on specific data require seeded databases. Keep seed scripts alongside your test infrastructure and run them before each test suite. Android-Specific Patterns Emulator Setup Android tests require an emulator or a USB-connected physical device. Maestro auto-detects connected devices.

List available system images

sdkmanager --list | grep system-images

Create emulator

avdmanager create avd -n maestro_test \ -k "system-images;android-34;google_apis;arm64-v8a"

Start emulator

emulator -avd maestro_test iOS vs Android Differences Aspect iOS Android Device type Simulator only (no physical) Emulator + physical via ADB clearState Does NOT clear Keychain Fully resets app data Cold boot crash XCTest kAXError (add swipe delay) No equivalent issue Performance Runs natively (fast) ARM emulation (slower on x86) Permission dialogs System alerts System dialogs with different text ADB Debugging adb devices

List connected devices

adb shell am start -n com.myapp/.MainActivity

Launch app

adb logcat | grep Maestro

Filter Maestro logs

adb shell input keyevent 82

Unlock screen

Android Permission Handling Android permissions appear as system dialogs. Dismiss with optional taps: - tapOn : text : "Allow" optional : true - tapOn : text : "While using the app" optional : true CI/CD Integration GitHub Actions with Maestro Cloud Maestro Cloud provides real devices in CI without local simulators. Use the official action: name : Mobile E2E Tests on : push : branches : [ main ] pull_request : branches : [ main ] jobs : maestro-e2e : runs-on : ubuntu - latest steps : - uses : actions/checkout@v4

Build Android APK

- uses : actions/setup - java@v4 with : java-version : '17' distribution : 'temurin' - name : Build Android APK run : | cd apps/mobile npx expo prebuild --platform android --no-install cd android && ./gradlew assembleRelease - name : Run Maestro Cloud Tests id : maestro uses : mobile - dev - inc/action - maestro - cloud@v2 with : api-key : $ { { secrets.MAESTRO_API_KEY } } app-file : apps/mobile/android/app/build/outputs/apk/release/app - release.apk workspace : .maestro include-tags : ci

Access results

${{ steps.maestro.outputs.MAESTRO_CLOUD_CONSOLE_URL }}

${{ steps.maestro.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}

${{ steps.maestro.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}

Tag-Based Flow Filtering Use tags to control which tests run in CI vs locally:

In your flow file header

appId : com.myapp tags : - ci - smoke


- launchApp

... test steps

Run only CI-tagged flows locally

maestro test --include-tags ci .maestro/

Exclude work-in-progress flows

maestro test --exclude-tags wip .maestro/ Local CI with Docker (Android Only) FROM openjdk:17-slim RUN curl -Ls "https://get.maestro.mobile.dev" | bash ENV PATH= "/root/.maestro/bin:${PATH}" COPY .maestro/ /app/.maestro/ WORKDIR /app CMD [ "maestro" , "test" , ".maestro/" ] Note: iOS tests cannot run in Docker (requires macOS). Use Maestro Cloud for iOS in CI. Maestro Cloud Maestro Cloud runs tests on real devices without local simulator setup. Setup Create account at cloud.maestro.dev Generate API key from dashboard Store as MAESTRO_API_KEY secret in your CI provider Running from CLI

Upload and run on Maestro Cloud

maestro cloud --api-key $MAESTRO_API_KEY \ --app-file ./app-release.apk \ .maestro/

With tag filtering

maestro cloud --api-key
$MAESTRO_API_KEY
\
--app-file ./app-release.apk
\
--include-tags smoke
\
.maestro/
Key Points
iOS testing
Supported on Maestro Cloud (not on local physical devices)
Android testing
Both local physical devices and Maestro Cloud
Results
Dashboard with video recordings, logs, and screenshots CI outputs : MAESTRO_CLOUD_CONSOLE_URL , MAESTRO_CLOUD_FLOW_RESULTS Maestro MCP Server The Maestro MCP server exposes Maestro's full command set as Model Context Protocol tools, letting AI agents execute tests and interact with devices directly — not just write YAML. How It Complements This Skill This Skill Maestro MCP Role Teaches correct patterns Provides runtime execution Layer Authoring (write good YAML) Execution (run, tap, assert, screenshot) Output Better test files Live device interaction Use both together: this skill ensures the AI writes correct tests; the MCP lets it run them immediately, see failures, and iterate. Setup The MCP ships with the Maestro CLI — no extra install needed:

Verify it's available

maestro mcp Claude Code — add to project .mcp.json or global settings: { "mcpServers" : { "maestro" : { "command" : "maestro" , "args" : [ "mcp" ] } } } Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS): { "mcpServers" : { "maestro" : { "command" : "maestro" , "args" : [ "mcp" ] } } } Also supported on Cursor, Windsurf, VS Code, and JetBrains IDEs. See the Maestro MCP docs for IDE-specific setup. Key Capabilities The MCP server exposes 47 tools organized by category: Category Tools Examples UI Interaction tap, swipe, scroll, long press tapOn , scrollUntilVisible Text Input type, erase, paste, copy inputText , eraseText Assertions visibility, AI-powered assertVisible , assertWithAI App Lifecycle launch, stop, clear state launchApp , clearState Device Control location, orientation, airplane setLocation , hideKeyboard Flow Control run flows, repeat, eval scripts runFlow , evalScript Media screenshots, recording takeScreenshot , startRecording AI-Powered visual assertions, defect detection assertWithAI , assertNoDefectsWithAI Write-Run-Fix Loop With both this skill and the MCP active, the AI can: Write a test YAML using patterns from this skill Run it via MCP's runFlow / launchApp + interaction tools See failures via screenshots and assertions Fix the YAML and re-run — all in one conversation Debugging maestro test --debug .maestro/test.yaml

Step through interactively

maestro record .maestro/test.yaml

Record as video

maestro studio

Interactive UI builder

maestro hierarchy

View element tree

Screenshots saved to ~/.maestro/tests/{timestamp}/ . Checklist for New Tests [ ] Unique test email (maestro-{feature}@example.com) [ ] Selector strategy chosen (testID for i18n apps, text for single-language — see Pattern 1) [ ] Selectors use state properties where relevant (enabled, checked — see Pattern 10) [ ] Similar elements distinguished with relative selectors, not index (see Pattern 11) [ ] Auth pre-flight pattern used (auth-loaded) [ ] Post-launch swipe added (iOS crash prevention) [ ] Both auth states handled (adaptive flows) [ ] Native alerts dismissed after mutations [ ] Short timeouts for optimistic updates (3-5s) [ ] Sub-flows created for reusable sequences [ ] Descriptive screenshots at key points [ ] Header comment with prerequisites [ ] Added to README.md test table [ ] Mock API server started if backend-dependent [ ] Tags added for CI filtering (ci, smoke, wip) Common Errors Error Cause Fix "Unable to locate Java Runtime" Java not in PATH export JAVA_HOME=/opt/homebrew/opt/openjdk@17/... "Element not found" after tap Native alert blocking Add tapOn: text: "OK" optional: true OTP digits not entering Auto-focus interference Use individual otp-input-N testIDs Test passes but nothing happened optional: true misused Only use optional for truly optional actions "Assertion is false" on visibility Element not rendered yet Increase timeout or verify testID exists Script output empty Wrong JS API Use http.get() not fetch() Auth state inconsistent after clearState iOS Keychain not cleared Don't use clearState , use adaptive flows kAXErrorInvalidUIElement crash Cold boot race (iOS) Add post-launch swipe delay Loading spinners / empty screens No API server running Start mock API server before tests Permission dialog blocking (Android) System dialog not dismissed Add tapOn: text: "Allow" optional: true Resources Maestro Documentation Maestro Selectors Reference Maestro Cloud Maestro MCP Server Maestro GitHub GraalJS HTTP Requests Conditions & Adaptive Flows GitHub Actions Integration BrowserStack: Maestro Testing Guide DEV.to: Tips & Tricks for Maestro + React Native

返回排行榜