Black-box mobile UI testing with Maestro YAML flows - launchApp, tapOn, inputText, assertVisible, reusable runFlow subflows, environment parameters, JUnit reporting, and emulator-based CI pipelines.
npx skills add https://github.com/PramodDutta/qaskills --skill Maestro Mobile Testing
This skill makes an AI agent write Maestro flows - declarative YAML files that drive iOS and Android apps black-box style with built-in waiting, no test harness compiled into the app, and no WebDriver session management. Trigger it when a repository contains a .maestro/ directory or *.yaml flows with appId: headers, or when the user wants fast, low-maintenance smoke tests for a native, React Native, or Flutter app without the weight of Appium.
tapOn and assertVisible polls until the element appears or a timeout expires. Reach for extendedWaitUntil for slow screens; never add fixed delays as a first resort.checkout-with-saved-card.yaml that does exactly that is debuggable. A 200-line regression.yaml is not. Compose bigger journeys from subflows with runFlow.tapOn: id: "submit-button" survives copy changes and localization; text selectors are acceptable for stable, single-language labels only.launchApp with clearState: true resets the app between flows; tests that inherit the previous flow's logged-in session fail in random orders.env: blocks or -e KEY=value CLI arguments so the same flow runs against dev, staging, and CI builds.# Install the Maestro CLI
curl -fsSL "https://get.maestro.mobile.dev" | bash
export PATH="$PATH:$HOME/.maestro/bin"
maestro --version
# Run one flow against the booted simulator/emulator
maestro test .maestro/login.yaml
# Run the whole suite with JUnit output for CI
maestro test .maestro/ --format junit --output maestro-report.xml
# Interactive selector explorer while authoring
maestro studio
# .maestro/login.yaml
appId: com.example.shop
env:
EMAIL: [email protected]
PASSWORD: Str0ngPass!
---
- launchApp:
clearState: true
- tapOn: 'Log in'
- tapOn:
id: 'email-input'
- inputText: ${EMAIL}
- tapOn:
id: 'password-input'
- inputText: ${PASSWORD}
- hideKeyboard
- tapOn:
id: 'login-submit'
- assertVisible: 'Welcome back'
- assertNotVisible: 'Invalid email or password'
- takeScreenshot: logged-in-home
# .maestro/subflows/login.yaml -- reusable building block
appId: com.example.shop
---
- launchApp:
clearState: true
- tapOn: 'Log in'
- tapOn:
id: 'email-input'
- inputText: ${EMAIL}
- tapOn:
id: 'password-input'
- inputText: ${PASSWORD}
- tapOn:
id: 'login-submit'
- assertVisible:
id: 'home-screen'
# .maestro/checkout.yaml -- the journey composes the subflow
appId: com.example.shop
env:
EMAIL: [email protected]
PASSWORD: Str0ngPass!
---
- runFlow: subflows/login.yaml
# Dismiss the push-permission prompt only if it appears
- runFlow:
when:
visible: 'Allow notifications?'
commands:
- tapOn: 'Allow'
- tapOn: 'Deals'
- scrollUntilVisible:
element:
text: 'Wireless Headphones'
direction: DOWN
timeout: 20000
- tapOn: 'Wireless Headphones'
- tapOn:
id: 'add-to-cart'
- tapOn:
id: 'cart-icon'
- assertVisible: 'Wireless Headphones'
- assertVisible:
text: 'Checkout'
enabled: true
# .maestro/order-status.yaml
appId: com.example.shop
---
- launchApp
- tapOn:
id: 'tab-orders'
# Wait up to 15s for async content instead of a blind sleep
- extendedWaitUntil:
visible:
id: 'orders-list'
timeout: 15000
# Pull-to-refresh until the order flips to Shipped, max 5 tries
- repeat:
while:
notVisible: 'Shipped'
times: 5
commands:
- swipe:
direction: DOWN
duration: 400
- assertVisible:
text: 'Shipped'
- copyTextFrom:
id: 'order-number'
- assertTrue: ${maestro.copiedText != ''}
# .github/workflows/maestro-android.yml
name: maestro-android
on: [pull_request]
jobs:
flows:
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Build debug APK
run: ./gradlew assembleDebug
- name: Install Maestro
run: |
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Run flows on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
profile: pixel_7
script: |
adb install app/build/outputs/apk/debug/app-debug.apk
maestro test .maestro/ --format junit --output maestro-report.xml \
-e [email protected] -e PASSWORD="${{ secrets.TEST_USER_PASSWORD }}"
- name: Publish report
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-report
path: |
maestro-report.xml
~/.maestro/tests
For hosted device execution, the same suite uploads unchanged: maestro cloud --api-key "$MAESTRO_CLOUD_API_KEY" app-debug.apk .maestro/ runs every flow on cloud devices and returns a pass/fail exit code CI can gate on.
maestro studio to discover ids and text before writing selectors by hand; it shows exactly what the accessibility tree exposes.testID in React Native, accessibilityIdentifier on iOS, contentDescription/resource-id on Android) as part of feature work.subflows/ and one top-level flow per journey; flows read top to bottom like a manual test script, keep them that readable.tags: [smoke] and run maestro test --include-tags smoke on every PR, the full suite nightly.takeScreenshot at journey milestones; screenshots plus the JUnit report make CI failures diagnosable without a local repro.- waitForAnimationToEnd and long extendedWaitUntil timeouts everywhere to paper over an app that never settles; fix the spinner, not the test.tapOn: point: 50%,30% or index-based taps) for elements that have ids; coordinate taps break on every screen size.env: plus CI secrets..maestro/ directory, YAML files starting with appId:, or maestro commands in CI workflows.Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take pramoddutta/maestro mobile testing from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.