World’s largest virtual agentic engineering & quality conference
Maestro testing explained: write mobile UI tests as YAML flows, the command set, Maestro vs Appium, and how to run flows across real devices from one config.

Bhawana
Author
Last Updated on: August 8, 2026
Most mobile suites die of setup cost. A driver session, a build step, a page object layer, and a dependency tree stand between a team and its first passing test, and plenty of apps never get past that to any automated coverage at all.
Maestro removes that entire preamble by making a test a YAML file. This guide covers Maestro testing end to end: the command set, how it compares with Appium, and the parts that get harder rather than easier, using flows and configuration taken from a working Maestro setup.
Overview
Maestro testing means writing mobile UI tests as declarative YAML flows instead of code. A flow names the app under test, then lists ordered commands such as launchApp, tapOn, inputText, and assertVisible. It supports Android, iOS, React Native, and Flutter, and it runs without a compile step or a driver session.
What Makes Maestro Different?
How Do You Run It at Scale?
Locally Maestro drives the devices attached to your machine. Running flows across many real device models means executing them on a cloud grid, which on TestMu AI is done through HyperExecute with a single configuration file that uploads the build, selects devices, and invokes Maestro on the runner.
Maestro testing is built on one idea: a test is a declarative YAML document rather than a program. You state which application the flow applies to, then list the steps in order. The runtime interprets that file against a connected device or emulator.
The design choice underneath is that mobile UI tests are mostly sequences of taps, inputs, and assertions, and that expressing those in a general-purpose language costs more than it returns for the common case. Removing the language removes the project scaffolding with it.
The second design choice matters more in practice. Every command waits for the interface to be ready before acting, rather than executing immediately and leaving the author to guess at sleeps. Hand-written mobile automation accumulates fixed waits precisely because that guessing is hard, and those waits are what make suites slow and flaky at the same time.
Worth settling early, because searching for this tool returns two unrelated products and a great deal of music software.
| Name | What it is | Who it is for |
|---|---|---|
| Maestro (this article) | Open-source mobile UI testing framework driving Android and iOS apps from YAML flows. | Mobile QA engineers and developers automating app journeys. |
| Opkey Maestro | A commercial agentic orchestration layer for enterprise application lifecycles across systems such as Oracle and Workday. | Enterprise programme and ERP teams. |
The two share a word and nothing else. Everything below refers to the mobile testing framework.
A flow has two parts separated by a document break: a header naming the application, and an ordered list of commands. This one walks the Wikipedia app through its onboarding screens.
appId: org.wikipedia
---
- launchApp
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_done_button"That is the whole test. No imports, no driver configuration, no build. A flow using visible text instead of view ids reads even more plainly, which is what makes these files reviewable by people who will never open an IDE.
appId: com.example.contacts
---
- launchApp
- tapOn: "Create new contact"
- tapOn: "First Name"
- inputText: "John"
- tapOn: "Last Name"
- inputText: "Snow"
- tapOn: "Save"
- assertVisible: "Add Phone Number"Run it against an attached device or emulator with a single command. The framework resolves the app, installs it if needed, and executes the steps in order.
maestro test flow_contacts_android.yamlThe command vocabulary is deliberately small. These cover the overwhelming majority of flows.
| Command | What it does | Note |
|---|---|---|
| launchApp | Starts the app named in the header. | Accepts a flag to clear state first, which is what makes a flow independent of the one before it. |
| tapOn | Taps an element by text, id, or accessibility label. | Waits for the element rather than failing immediately if it is not yet rendered. |
| inputText | Types into the focused field. | Usually preceded by a tapOn to set focus, exactly as a user would. |
| assertVisible | Fails the flow unless the element is on screen. | The only command that makes a flow a test rather than a script. |
| swipe | Scrolls or swipes in a direction. | Needed far more often on mobile than its web equivalent, since content sits below the fold by default. |
| runFlow | Executes another flow file inline. | The closest thing to a shared helper, and how login gets reused across flows. |
Selector choice is where flows become durable or brittle. Matching by visible text is the fastest to write and breaks on the first copy change or the first localised build. Matching by view id survives both, and requires the application to expose meaningful identifiers, which is a development decision rather than a testing one. The trade is the same one covered in software testability: how easily a system can be automated is decided before any test is written.
Maestro testing and Appium both drive real mobile applications, and they suit different teams rather than ranking against each other.
| Question | Maestro | Appium |
|---|---|---|
| A test is | A YAML flow, interpreted. | A program in Java, Python, JavaScript, or C#. |
| Setup before first test | Install the binary and write a file. | Server, client library, driver, and a build. |
| Waiting | Implicit in every command. | Explicit waits you write and tune. |
| Loops and branching | Limited and awkward by design. | Whatever the language supports. |
| Reuse | runFlow to call another file. | Functions, classes, and page objects. |
| Who can author | Anyone who can read the flow. | Someone comfortable in the language. |
| Best fit | Fast smoke coverage of core journeys. | Large suites with data setup and custom logic. |
The honest summary is that Maestro is faster to start and hits a ceiling sooner. A team with no mobile automation at all gets more value from ten Maestro flows written this week than from an Appium framework that is still being designed. A team already running a mature Appium suite gains little by porting it. Both frameworks execute on the same infrastructure, so the choice does not have to be made once and forever.
Locally, Maestro drives whatever is plugged in or emulated, which is one device configuration. Mobile defects concentrate in the gaps between device models, OS versions, and screen sizes, so a suite that only ever runs on one handset is testing the least interesting configuration you have.
Running the same flows on TestMu AI covers that spread across 10,000+ real devices without owning any of them. Maestro executes through HyperExecute, and the whole run is described by one configuration file. Upload the build first and keep the returned identifier:
curl -u "USERNAME:AccessKey" -X POST \
"https://manual-api.lambdatest.com/app/upload/realDevice" \
-F "appFile=@/path/to/your/app.apk" \
-F "name=sampleApp"
# Response contains an APP_ID you reference as lt://APP1234...Then describe the run. The configuration below is from a working Maestro setup on HyperExecute, with autosplit distributing the discovered flows across the requested devices:
version: "0.2"
autosplit: true
concurrency: 2
runson: android
runtime:
- language: java
version: "21"
framework:
name: raw
args:
devices: [".*-.*", ".*-.*", ".*-.*"]
video: true
deviceLog: true
appPath: maestro-test/sample.apk
buildName: maestro-t1
queueTimeout: 600
testDiscovery:
command: cat ./maestro-test/discover.txt
mode: static
type: raw
testRunnerCommand: ./maestro-test/runTest_android_emulator.sh $test
report: true
partialReports:
- location: .
type: xml
frameworkName: junitThe runner script is the part that actually invokes Maestro on the machine, and it is deliberately thin:
#!/bin/bash
adb devices
maestro -v
maestro test $1 --debug-output ./MaestroLogs --format junitThen trigger the run. Each discovered flow is dispatched to a device, and the dashboard collects the results:
./hyperexecute --user USERNAME --key AccessKey --config hyperexecute.yamlBoth real devices and Android emulators are supported, and pre-installed system apps can be targeted by setting the app identifier to stock and naming the package in the flow. Full setup, including the iOS path, is in the HyperExecute Maestro documentation.
The format flag in the runner above is doing more work than it looks. It makes Maestro emit JUnit XML, which is the interchange format every CI system and most test management tools already understand, so results land somewhere durable instead of scrolling past in a log.
Debugging a mobile failure needs more than a stack trace, because the interesting question is usually what was on screen. The debug output directory captures logs and screenshots locally. On a grid run, video and device logs attach to each execution, which is what turns a failure that only reproduces on one device model into something a developer without that handset can act on.
Feed the JUnit output into test management if you want history rather than a per-run verdict. A single failing flow is a defect; the same flow failing on one device family for three weeks is a different and more useful finding.
Every account of Maestro testing should state these plainly, because the parts a framework is bad at decide whether it fits your suite more than the parts it is good at.
None of these are defects. They are the cost of the trade that makes a first flow take minutes. The teams that get the most from Maestro use it for the journeys that must never break and keep a programmable framework for the suites that need logic, which is a division of labour rather than a migration.
Note: Write the flow once and run it across real Android and iOS hardware rather than the one handset on your desk, with video and device logs attached to every execution. Start free
Pick the one journey whose breakage would be most embarrassing and write it as a flow this afternoon. Launch, navigate, assert one thing that proves the journey worked. If it takes longer than an hour, the obstacle is your app's selectors rather than the framework, and that is a finding worth having on its own.
Then decide the division of labour deliberately. Maestro for the flows that must never break and that non-engineers should be able to read; a programmable framework for suites that need loops, seeded data, and calls into your own code. Choosing one tool for everything is what forces the compromise.
Whichever mix you land on, run it on hardware your users actually hold. TestMu AI's real device cloud covers that spread, and the broader picture of what to test on mobile is in the mobile app testing guide.
Author
Bhawana is a Community Evangelist at TestMu AI with over 3 years of experience creating technically accurate, strategy-driven content in software testing. She has authored 50+ blogs on test automation, cross-browser testing, mobile testing, and real device testing. She also serves as Product Marketing Manager for Kane CLI, the command-line tool that runs browser automation from the terminal using natural-language flows in a real Chrome browser. Bhawana is certified in KaneAI, Selenium, Appium, Playwright, and Cypress, reflecting her hands-on knowledge of modern automation practices. On LinkedIn, she is followed by 6000+ QA engineers, testers, AI automation testers, and tech leaders.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance