I keep a Maestro YAML cheat sheet pinned next to my editor because the same dozen commands cover about ninety percent of my mobile automation work. When I built our Maestro workspace from zero, the hard part wasn't syntax — it was knowing which command fits which situation. This is the reference I wish I'd had on day one.
The Maestro YAML basics: launch, tap, type
Every flow starts with launchApp. I almost always pass clearState: true so the app boots fresh; leftover sessions are the top cause of flaky runs I've debugged.
appId: com.example.app
---
- launchApp:
clearState: true
- tapOn: "Sign in"
- tapOn:
id: "email_input"
- inputText: "qa.user@example.com"
When to use which selector: plain-text tapOn is readable and fine for single-language screens, but prefer id the moment localization enters the picture. Text selectors break instantly on the Arabic build, and I test that build a lot. Two honorable mentions from the same family: scrollUntilVisible for long lists, and back on Android — both come up more often than you'd expect once flows get realistic.
Assertions that fail usefully
assertVisible is my checkpoint after every meaningful action. Assert on what the user actually cares about, not whatever element is easiest to find — a flow that taps five things and only asserts at the end tells you almost nothing when it fails.
- assertVisible: "Welcome back"
- assertVisible:
id: "cart_badge"
text: "3"
- assertNotVisible: "Loading"
assertNotVisible deserves more love: it's how I catch spinners that never dismiss and error banners that shouldn't be there.
Reuse: runFlow, repeat, and conditionals
runFlow keeps flows DRY — login lives in one file, and forty tests call it. repeat replaces copy-paste for anything you do N times. Conditional runFlow with when is my answer to permission dialogs and one-time onboarding screens that appear on some devices and not others.
- runFlow: flows/login.yaml
- repeat:
times: 3
commands:
- tapOn: "Add to cart"
- runFlow:
when:
visible: "Allow notifications"
commands:
- tapOn: "Don't allow"
One warning from experience: don't reach for conditionals to paper over flakiness. If a screen appears unpredictably for no product reason, that's a bug report, not a when block.
Env vars and tags keep the suite maintainable
Environment variables keep credentials and endpoints out of your flows. Reference them as ${USERNAME} and inject at run time with maestro test -e USERNAME=qa.user flows/. Tags decide what runs when:
appId: com.example.app
tags:
- smoke
---
- runFlow: flows/login.yaml
- assertVisible: "Home"
Then maestro test --include-tags smoke flows/ runs just the critical path. In our GitHub Actions setup, the smoke tag runs on every pull request and the full regression suite runs nightly. Splitting the suite this way and letting CI do the boring part is a big reason our regression pass takes roughly half the time it used to.
Quick tip: when you can't figure out a selector, run maestro studio. It shows the live view hierarchy so you can grab a stable id instead of guessing at text — five minutes there saves an hour of trial and error.
None of this is advanced. That's the point of a cheat sheet: launchApp, tapOn, inputText, assertVisible, runFlow, repeat, a conditional, env vars, and tags. Learn these well and you can automate most of a mobile app before you ever need anything exotic.