AgentSkillsCN

maestro-ui-testing

在编写、调试或维护 Maestro E2E UI 测试时使用此技能。严格遵循最佳实践,打造无 flakiness、且在持续集成环境中稳定运行的移动端 UI 测试。

SKILL.md
--- frontmatter
name: maestro-ui-testing
description: Use this skill when writing, debugging, or maintaining Maestro E2E UI tests. Enforces best practices for flakiness-free, CI-stable mobile UI tests.

Maestro UI Testing Best Practices

This skill ensures all Maestro E2E tests follow best practices for reliability, especially in CI/CD environments like GitHub Actions.

When to Activate

  • Writing new Maestro E2E tests
  • Debugging flaky tests in CI/CD
  • Refactoring existing tests
  • Setting up Maestro test structure
  • Fixing "Assertion is false" errors

Core Principles

1. One Flow = One User Scenario

NEVER test your entire app in a single long Flow.

DODON'T
01-onboarding.yaml (single scenario)full-app-test.yaml (entire app)
02-paywall.yaml (single feature)All scenarios in one file

Why:

  • If any command fails, the entire test halts
  • Can't run tests in parallel
  • Hard to identify which scenario failed

2. Use accessibilityIdentifier, NOT Text or Point Selectors

PrioritySelector TypeReliabilityExample
1st (Best)id: (accessibilityIdentifier)Stableid: "onboarding-welcome-cta"
2ndtext: (fallback)Localization-dependenttext: "許可|Allow"
3rd (Avoid)point:EXTREMELY FLAKYpoint: "28,78"

SwiftUI Implementation:

swift
Button("Get Started") {
    action()
}
.accessibilityIdentifier("onboarding-welcome-cta")

Maestro Usage:

yaml
- tapOn:
    id: "onboarding-welcome-cta"

3. Never Use Static Waits

DODON'T
extendedWaitUntil with visibility- wait: 5000 (sleep)
assertVisible for short waitsFixed delays
yaml
# GOOD - Wait for element to appear
- extendedWaitUntil:
    visible:
      id: "onboarding-welcome-cta"
    timeout: 30000

# BAD - Fixed sleep
- wait: 5000

4. Handle System Dialogs Properly

System dialogs (permissions, alerts) are non-deterministic. Always use optional: true.

yaml
# Permission dialog - may or may not appear
- tapOn:
    text: "許可|Allow"
    optional: true

# iOS system notification permission
- tapOn:
    id: "com.apple.springboard:Allow"
    optional: true

Multi-language Support (Japanese/English):

yaml
- tapOn:
    text: "許可|Allow"
    optional: true

5. CI/CD Environment Alignment

IssueSolution
Slower CI machinesIncrease timeouts (15s → 30s)
Different screen sizesUse id: selectors, not point:
State inconsistencyAlways clearState + clearKeychain
Simulator not readyAdd initial extendedWaitUntil

Flow Structure Template

yaml
# Feature: [Description]
# Tests: [scenario description]
# Dependencies: [what state is required]

appId: com.example.app
name: Descriptive flow name
tags:
  - smokeTest      # Fast critical path tests for PR
  - featureName    # Feature-specific tag
  - phase6         # Version/phase tag

---
# ALWAYS start with clean state
- clearState
- clearKeychain
- launchApp

# Wait for app to fully load (critical for CI)
- extendedWaitUntil:
    visible: true
    timeout: 15000

# Step 1: [Description]
- extendedWaitUntil:
    visible:
      id: "element-id"
    timeout: 30000

- tapOn:
    id: "element-id"

# Handle optional system dialogs
- tapOn:
    text: "許可|Allow"
    optional: true

# Final verification
- assertVisible:
    id: "expected-result-element"

# Take screenshot for debugging
- takeScreenshot: "flow-complete"

Tag Strategy for CI/CD

TagPurposeWhen to Run
smokeTestCritical path, fastEvery PR
regressionFull test suiteNightly
phase6Phase-specific testsPhase release
utilHelper flows (exclude)Never standalone

config.yaml:

yaml
flows:
  - "*/*"
excludeTags:
  - util

GitHub Actions:

yaml
# PR - Smoke tests only
maestro test maestro/ --include-tags smokeTest

# Nightly - Full suite
maestro test maestro/

Debugging Flaky Tests

Step 1: Identify the Flakiness Pattern

SymptomLikely CauseSolution
"Element not found"Timing issueIncrease extendedWaitUntil timeout
"Assertion is false: X is visible"Element not loaded yetAdd extendedWaitUntil before assertion
Works locally, fails in CIEnvironment differenceUse id: selectors, increase timeouts
Random failuresPoint selectorsReplace point: with id:

Step 2: Debug Commands

bash
# Run with debug output
maestro test --debug maestro/01-onboarding.yaml

# View hierarchy to find elements
maestro hierarchy

# Interactive testing with Maestro Studio
maestro studio

Step 3: Common Fixes

Problem: Assertion is false: "true" is visible

yaml
# BEFORE - Broken (checking literal "true")
- extendedWaitUntil:
    visible: true
    timeout: 15000

# AFTER - Fixed (checking for actual element)
- extendedWaitUntil:
    visible:
      id: "main-screen-element"
    timeout: 15000

Problem: Button not found after navigation

yaml
# Add wait after navigation
- tapOn:
    id: "navigate-button"

- extendedWaitUntil:
    visible:
      id: "destination-element"
    timeout: 10000

- tapOn:
    id: "destination-element"

iOS-Specific Best Practices

SwiftUI accessibilityIdentifier Naming

swift
// Use semantic, hierarchical naming
.accessibilityIdentifier("onboarding-welcome-cta")
.accessibilityIdentifier("onboarding-struggles-next")
.accessibilityIdentifier("nudge-card-primary-button")

// For dynamic elements, include the identifier
ForEach(problems) { problem in
    Button(problem.name) { ... }
        .accessibilityIdentifier("onboarding-struggle-\(problem.id)")
}

Handling iOS Permission Dialogs

yaml
# Notification permission (Japanese/English)
- tapOn:
    text: "許可|Allow"
    optional: true

# ATT permission
- tapOn:
    text: "許可|Allow Tracking"
    optional: true

# Location permission
- tapOn:
    text: "Appの使用中は許可|Allow While Using App"
    optional: true

Paywall Dismissal (Superwall/RevenueCat)

yaml
# Dismiss paywall if shown (tap close button or back)
- tapOn:
    id: "paywall-close-button"
    optional: true

# Or tap outside/back button coordinates (last resort)
- tapOn:
    point: "28,78"
    optional: true

File Organization

code
maestro/
├── config.yaml              # Global configuration
├── README.md                # Documentation
├── 01-onboarding.yaml       # Core flow (numbered for order)
├── 02-paywall.yaml          # Core flow
├── phase6/                  # Feature-specific directory
│   ├── 01-llm-nudge.yaml
│   └── 02-feedback.yaml
└── common/                  # Shared utility flows
    └── login.yaml           # Tagged with 'util', excluded from standalone runs

Checklist Before Merge

CheckDescription
[ ] Uses id: selectors (not point:)Reliability
[ ] Has appropriate timeouts (30s for CI)CI stability
[ ] Uses optional: true for dialogsNon-deterministic handling
[ ] Starts with clearState + clearKeychainClean state
[ ] Has correct tags (smokeTest, etc.)CI filtering
[ ] Takes screenshots at key pointsDebugging
[ ] Runs locally 3x without failureFlakiness check

Quick Reference

Common Commands

yaml
# Wait for element
- extendedWaitUntil:
    visible:
      id: "element-id"
    timeout: 30000

# Tap by ID
- tapOn:
    id: "element-id"

# Tap by text (fallback)
- tapOn:
    text: "Button Text"

# Optional tap (for dialogs)
- tapOn:
    text: "Allow"
    optional: true

# Assert visibility
- assertVisible:
    id: "element-id"

# Input text
- inputText: "Hello World"

# Clear and type
- clearText
- inputText: "New Text"

# Scroll
- scroll

# Screenshot
- takeScreenshot: "filename"

Timeout Guidelines

ContextTimeout
App launch15000ms
Screen transition5000-10000ms
Element visibility30000ms (CI)
Animation completionUse waitForAnimationToEnd

Troubleshooting: Simulator Build Issues

問題: 古いビルドがシミュレータに残っている

症状:

  • 削除した画面(ATT等)がまだ表示される
  • コード変更が反映されない
  • fastlane build_for_simulator で「Could not find .app bundle」エラー

原因: fastlane がビルドしても .app がインストールされないことがある

解決策:

bash
# 1. DerivedData をクリーン
rm -rf aniccaios/build/DerivedData

# 2. 再ビルド
cd aniccaios && fastlane build_for_simulator

# 3. 手動でインストール
xcrun simctl install "iPhone 16 Pro" "build/DerivedData/Build/Products/Debug-iphonesimulator/aniccaios.app"

問題: DEBUG ビルドで Paywall がスキップされる

症状: sandbox ユーザーでオンボーディング後に Paywall が表示されない

原因: sandbox の過去トライアルで isEntitled=true が残っている

解決策: DEBUG では entitlement に関係なく Paywall を強制表示

swift
#if DEBUG
showPaywall = true
return
#endif

問題: Maestro で要素が見つからない

症状: Element not found: Text matching regex: .*次へ.*

解決策: 必ず inspect_view_hierarchy で実際のテキストを確認してからセレクターを決定


Remember: The goal is zero flakiness. Every test should pass 100% of the time in CI. If a test is flaky, fix the test, not the retry count.