When I joined my first QA team, every release meant a full manual regression pass. It worked, but it was slow, repetitive, and easy to rush under deadline pressure. I got the chance to fix that by building the company's first mobile automation workspace, and I chose Maestro for it. This post is the practical version of what I set up: the structure, the conventions, and the CI wiring.
Why Maestro
Maestro's flows are plain YAML, which means anyone on the team can read a test and understand what it does — no page-object classes to learn before your first contribution. It also handles the flakiest parts of mobile UI testing (waiting, retries, animations) by default. For a small team starting from zero, time-to-first-useful-test matters more than framework power, and Maestro's is measured in minutes.
Workspace structure
The single most important early decision was separating flows (what a test covers) from subflows (reusable steps). My layout:
.maestro/
flows/
auth/login.yaml
auth/logout.yaml
orders/create-order.yaml
subflows/
login-as.yaml
dismiss-onboarding.yaml
config.yaml
Anything used by two or more flows becomes a subflow. Login is the obvious first one:
# subflows/login-as.yaml
appId: ${APP_ID}
---
- launchApp:
clearState: true
- tapOn:
id: "email-input"
- inputText: ${USERNAME}
- tapOn:
id: "password-input"
- inputText: ${PASSWORD}
- tapOn:
id: "login-button"
- assertVisible:
id: "home-screen"
Credentials and app IDs come from environment variables, so the same flows run against staging and production builds without edits.
Selector and assertion standards
Flaky selectors kill automation projects faster than anything else. Our rules were simple:
- Test IDs first. Ask developers to add
testID/resource-idto anything a test touches. Text selectors break the moment copy or localization changes. - Assert something meaningful after every action. A tap without a following
assertVisibletells you nothing about where the failure actually happened. - Tag every flow.
tags: [smoke]ortags: [regression]— the tag is the contract for what runs when.
Wiring it into GitHub Actions
Automation that only runs on someone's laptop isn't automation — it's a demo. The GitHub Actions job boots an emulator, installs the build, and runs the tagged suite:
- name: Run smoke suite
run: maestro test .maestro/flows --include-tags=smoke
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-recordings
path: ~/.maestro/tests
Two details paid for themselves many times over: parallel execution (splitting flows across shards so the suite finishes in a fraction of the serial time) and failure artifacts — screenshots and recordings uploaded automatically, so a red build comes with evidence instead of a mystery.
What it changed
Regression testing time dropped by roughly half. But the bigger change was psychological: developers stopped asking "did QA check this?" and started checking the CI status. When the suite is green on every build, release conversations get shorter.