maestro-mobile-testing
Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=bagisto-opensource-ecommerce-mobile-app-agents-skills-maestro-mobile-testing-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name maestro-mobile-testing description Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration version 1.1.0 category toolchain author tovimx license MIT progressive_disclosure {"entry_point":{"summary":"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification","when_to_use":"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps","quick_start":"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test"},"token_estimate":{"entry":150,"full":9000}} context_limit 900 tags ["react-native","expo","testing","maestro","e2e","mobile","ios","android","yaml","ci-cd","mcp"] requires_tools [] 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.
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |