Once my first flows passed locally, the real work was getting Maestro tests in GitHub Actions — because a suite that only runs on my laptop is a demo, not automation. This is the setup my team runs today: an Android emulator job, tags that split smoke from regression, parallel shards, and artifacts that turn a red build into something you can actually debug.
Getting Maestro tests running in GitHub Actions
GitHub-hosted runners don't come with a running emulator, so the job has to create one. The reactivecircus/android-emulator-runner action handles booting and snapshot caching; on Linux runners, enable KVM first (two udev lines, documented in the action's README) or the emulator will crawl. The skeleton:
name: maestro-smoke
on: [pull_request]
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Maestro
run: curl -fsSL "https://get.maestro.mobile.dev" | bash
- name: Run smoke suite
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
script: |
adb install app-debug.apk
$HOME/.maestro/bin/maestro test .maestro/flows --include-tags=smoke
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-failures
path: ~/.maestro/tests
Build the APK in an earlier step or download it from your build pipeline — the emulator job should install a ready binary, not compile one. And pin the action version and API level explicitly: an emulator image that changes silently underneath you is a flakiness source no test fix will ever cure.
Tags decide what runs when
Every flow carries tags: [smoke] or tags: [regression] in its header. The workflow above runs the smoke set on every pull request — it finishes in minutes and answers "is this build worth testing?". A second workflow, triggered by schedule (nightly) and by release branches, runs with --include-tags=regression. Same flows folder, same tooling, two very different feedback loops. The tag is also documentation: when someone asks what's covered on every PR, the answer is whatever carries the smoke tag.
Sharding for parallel speed
Once the regression set outgrows a single emulator, split it. Maestro shards natively:
maestro test .maestro/flows \
--include-tags=regression \
--shard-split 3 \
--format junit --output report.xml
On one runner, --shard-split distributes flows across several emulator instances; if a single runner is still too slow, use a job matrix where each job boots its own emulator and runs a subset of flows. Two rules make sharding safe: every flow must be independent (start with launchApp and clearState: true), and no flow may depend on data another flow created.
Failure artifacts are the whole point
A red build with no evidence turns into "rerun it and hope." Maestro writes screenshots and screen recordings of failed flows under ~/.maestro/tests, and the if: failure() upload step attaches them to the workflow run. The JUnit report from --format junit feeds any test-report action, so reviewers see which flow failed without opening a single log.
Optional: Slack and status wiring
The commit status check comes free — a red X on the pull request is usually all the smoke suite needs. For the nightly regression run I add one webhook step, guarded by if: failure(), that posts the workflow URL to the team channel. Resist notifying on every green run: alerts people learn to ignore are worse than no alerts at all.