Automate Android and iOS App Tests in the Cloud
Run Your Appium, Espresso, XCUITest, and Maestro Suites as They Are, in Parallel on 50,000+ Real Devices.
- TestMu AI (Formerly LambdaTest)
- /
- Learning Hub
- /
- Maestro Testing: Mobile UI Automation With YAML Flows
Maestro Testing: Mobile UI Automation With YAML Flows
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.
Last Updated on:
Most mobile suites stall on 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 any automated coverage as a result.
Maestro replaces that setup with a YAML file. This guide covers Maestro testing hands-on: installing the CLI, writing flows, adding conditions and JavaScript, comparing it with Appium, and running the same flows on real Android devices on TestMu AI with a configuration you can copy. For the wider strategy around it, start with the mobile app testing guide.
TL;DR
Maestro testing is UI testing with Maestro, an open-source framework where each test is a declarative YAML flow instead of code. A flow names the app, then lists ordered commands such as launchApp, tapOn, inputText, and assertVisible. Maestro covers Android, iOS, and web apps, including React Native and Flutter builds, with no compile step.
What Makes Maestro Different?
- YAML instead of code: Does a Maestro test need a programming project? No. A Maestro flow is a readable list of steps that runs without a build, so a product manager can review one and a tester can write one in minutes.
- Built-in waiting: Do Maestro flows need explicit sleeps? No. Maestro commands wait for the interface to settle and retry before failing, which removes the fixed waits that make hand-written mobile suites slow and brittle.
- Logic when needed: Can a Maestro flow branch and loop? Yes. Maestro adds when conditions on runFlow, a repeat command, and sandboxed JavaScript, although suites with heavy data setup and shared helpers still fit Appium better.
- Free local runs: Is Maestro free? Yes on your own devices. The Maestro CLI is open source under the Apache 2.0 license, and Maestro's paid plans add hosted devices on Maestro Cloud.
- Physical iPhones: Can the open-source Maestro CLI test physical iOS devices? No. Locally, Maestro runs on Android phones and emulators, iOS simulators, and web browsers.
Can You Run Maestro on Real Devices in the Cloud?
Yes, Maestro flows can run on cloud devices. On TestMu AI, HyperExecute runs them on Android real devices, Android emulators, and iOS simulators. One YAML file uploads the app, selects devices, installs Maestro on the runner, and runs each flow as its own task with video and device logs.
What Is Maestro?
Maestro is an open-source UI automation framework for mobile and web apps. The Maestro documentation says it tests apps built with React Native, Flutter, or native code, runs tests without compilation, and waits for animations and network content on its own, so flows need no manual sleep() calls.
A flow is a declarative document. It names the application, lists the steps in order, and the runtime interprets that file against a connected device, emulator, simulator, or browser. Removing the programming language removes the project scaffolding with it.
Platform coverage is uneven, and the Maestro repository states the gap plainly: Android, iOS, and web apps are supported, but physical iOS devices are not yet supported. On your own machine that means Android phones and emulators, iOS simulators, and browsers.
Maestro sits at the end-to-end layer of mobile UI testing. It checks what a user sees and taps, so unit and API tests still cover internal state and backend logic.
Other Products Called Maestro
Search results for Maestro also return unrelated enterprise products that share the name:
| Name | What it is | Who it is for |
|---|---|---|
| Maestro (this article) | Open-source UI testing framework that drives Android, iOS, and web apps from YAML flows. | Mobile QA engineers and developers automating app journeys. |
| Opkey Maestro | Opkey's agentic AI command center that assembles AI agents into workflows for ERP application lifecycle tasks. | Enterprise application and ERP teams. |
| UiPath Maestro | UiPath's cloud-native orchestration platform that coordinates automation, AI agents, and people in business processes. | Enterprise automation and operations teams. |
Everything below refers to the testing framework.
How to Install Maestro
The Maestro CLI runs flows from a terminal or a CI job. Maestro Studio is a desktop app, with installers for Windows, macOS, and Linux, for building and running flows locally.
On macOS and Linux, the Maestro CLI install guide installs the CLI with one curl command and requires Java 17 or higher:
# Install the Maestro CLI
curl -fsSL "https://get.maestro.mobile.dev" | bash
# Confirm the CLI works and Java 17+ is on the path
maestro --help
java -versionOn Windows, the same guide prefers the native route over WSL: download maestro.zip from the GitHub releases page, extract it, and add its bin folder to your PATH.
Local runs cost nothing. According to Maestro's pricing page, the free Local plan covers the CLI and Maestro Studio on your own devices, while paid plans add hosted devices and parallel runs on Maestro Cloud. If you are comparing hosted options, the Maestro alternative page sets them side by side.
Your First Flow
A flow has two parts separated by a document break (---): a header naming the application, and an ordered list of commands. This flow, taken from TestMu AI's Maestro sample repository, walks the Wikipedia Android 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"There are no imports, no driver configuration, and no build in that file. A flow that matches visible text instead of view ids reads even more plainly, which is what makes these files reviewable by people who 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"Install the app on the device or emulator first, because Maestro launches it by appId but does not install it. Then run the file:
maestro test flow_contacts_android.yamlSelectors and Commands
A small set of commands covers most flows:
| Command | What it does | Note |
|---|---|---|
| launchApp | Starts the app named in the header. | With clearState: true it resets the app's data first, which keeps a flow independent of the one before it. |
| tapOn | Taps an element matched by text, id, or screen coordinates. | Retries until the element appears instead of failing on the first frame. |
| inputText | Types into the focused field. | Usually follows a tapOn that sets focus, as a user would. |
| assertVisible | Fails the flow unless the element appears. | Retries automatically. assertNotVisible and assertTrue cover the opposite case and computed checks. |
| scrollUntilVisible | Scrolls until a target element is on screen. | Replaces guessed swipe counts on long lists and settings screens. |
| runFlow | Runs another flow file inline. | The standard way to reuse a login sequence 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 localized 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 same trade runs through software testability: how easily a system can be automated is decided before any test is written.
Conditions, Loops, and JavaScript in Maestro
Conditions attach to runFlow through a when block, and the Maestro conditions documentation lists visible, notVisible, platform, and true (a JavaScript expression) as the supported keys. This block dismisses a popup only when it appears:
- runFlow:
when:
visible: "Dismiss"
commands:
- tapOn: "Dismiss"The platform key lets one file carry Android and iOS selectors side by side instead of maintaining two copies. Loops use repeat, which runs a block a fixed number of times or while a condition holds:
- repeat:
times: 3
commands:
- tapOn: Button
- scrollJavaScript handles whatever YAML cannot express. runScript runs a .js file and evalScript runs an inline expression, both inside a sandbox with no file system access and no npm packages. The sandbox rules and the flow-ordering behavior that trips up larger suites are covered in depth in the Maestro mobile testing guide.
Maestro scripts can still reach your backend through a built-in HTTP client. The Maestro HTTP requests guide recommends seeding test data through your API before the UI steps, because setting it up through the UI is slow and flaky:
// seed_contact.js: create the contact through your API before the UI steps run
const response = http.post('https://staging-api.your-app.test/contacts', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ firstName: 'John', lastName: 'Snow' })
});
const contact = json(response.body);
output.contactName = contact.firstName;appId: com.example.contacts
---
- runScript: seed_contact.js
- launchApp:
clearState: true
- tapOn: "Search contacts"
- inputText: ${output.contactName}
- assertVisible: ${output.contactName}The json() helper parses the response body, and any value stored on output is available to later commands as an expression. clearState wipes only the app's local data, so the contact created on the server is still there when the search runs.
Maestro vs Appium
Maestro testing and Appium both drive real mobile apps. The difference is how much of each test is code.
| Question | Maestro | Appium |
|---|---|---|
| A test is | A YAML flow, interpreted at run time. | A program in Java, Python, JavaScript, C#, or Ruby. |
| Setup before first test | Install the CLI and Java, then write a file. | An Appium server, a driver per platform, a client library, and a test project. |
| Waiting | Built into every command. | Explicit waits you write and tune. |
| Logic | when conditions, repeat, and sandboxed JavaScript. | Anything the language and its packages support. |
| Reuse | runFlow and shared JavaScript functions. | Functions, classes, and page objects. |
| Who can author | Anyone who can read the flow. | Someone comfortable in the language. |
| Best fit | Smoke coverage of critical journeys. | Large suites with data setup and custom logic. |
Maestro is faster to start and reaches its ceiling sooner. A team with no mobile automation gets more from ten Maestro flows written this week than from an Appium framework still being designed, while a team with a mature Appium suite gains little by porting it.
HyperExecute runs both frameworks, so the choice can be made per suite instead of once for the whole organization. For the wider field, see the roundup of Appium alternatives.
How to Run Maestro Tests on Real Devices
Locally, Maestro drives one device configuration at a time. Mobile defects cluster in the gaps between device models, OS versions, and screen sizes, so a suite that only runs on the handset on your desk tests the least interesting configuration you have.
TestMu AI runs Maestro on HyperExecute through its raw framework mode. One YAML config uploads the app, picks the devices, installs Maestro on the runner, and calls maestro test once per discovered flow. The HyperExecute Maestro documentation covers Android real devices, Android emulators, and iOS simulators, plus a dedicated runner script for iOS real devices.
This is the Android real-device config from TestMu AI's Maestro sample repository, with only the build name changed:
version: "0.2"
autosplit: true
concurrency: 2
runson: android
dynamicAllocation: true
runtime:
- language: java
version: "21"
framework:
name: raw
args:
devices: ["Pixel 6-14"]
video: true
deviceLog: true
appPath: maestro-test/sample.apk
buildName: maestro-real-device
queueTimeout: 600
region: ap
disableReleaseDevice: true
isRealMobile: true
reservation: false
network: true
platformName: android
env:
MAESTRO: true
MAESTRO_LOGS_DIR: MaestroLogs
pre:
- chmod +x ./maestro-test/setup-script.sh
- chmod +x ./maestro-test/runTest.sh
- ./maestro-test/setup-script.sh
testDiscovery:
command: cat ./maestro-test/discover.txt
mode: static
type: raw
testRunnerCommand: ./maestro-test/runTest.sh $test
frameworkStatusOnly: true
report: true
partialReports:
- location: .
type: xml
frameworkName: junitThe pre step runs setup-script.sh, which updates adb and installs Maestro on the runner (the script pins Maestro 2.6.1). testRunnerCommand then hands each flow listed in discover.txt to runTest.sh, which stays deliberately thin:
#!/bin/bash
adb devices
/home/ltuser/.maestro/bin/maestro -v
/home/ltuser/.maestro/bin/maestro test $1 --debug-output ./MaestroLogs --format junitTrigger the job from the repository root with the HyperExecute CLI:
./hyperexecute --user USERNAME --key ACCESS_KEY --config hyperexecute.yamlRun it against the sample and the CLI prints the execution plan, uploads the APK, and reports each stage as it finishes. Trimmed output from a passing run on the requested Pixel 6:
Execution Plan
Mode: autosplit
Runson: android
Concurrency: 2
Unique Id for App - lt://APP10160161391793000247554808
Test Discovery Result: 2
yaml/android/android-swipe1.yaml
yaml/android/android-launch.yaml
Job e6e456c9-7e48-468f-b3a8-72876b84526f has INITIATED
✔ [2] pre (14s)
✔ [2] discovery (2s)
✔ [2] yaml/android/android-swipe1.yaml (59s)
✔ [2] yaml/android/android-launch.yaml (32s)
✔ [1] pre (2m25s)
✔ [1] discovery (1s)
COMPLETED
Test Execution Time: 1m6s
Job Duration Time: 3m4s
Total Tasks: 2
Failed Tasks: 0Each flow's own log shows the device adb sees, the Maestro version the setup script installed, and the result:
List of devices attached
19041FDF60006W device
2.6.1
Waiting for flows to complete...
[Passed] android-swipe1 (15s)
1/1 Flow Passed in 15sAutosplit sends each flow to whichever task is ready first. Task 2 finished its pre stage in 14 seconds and ran both flows while task 1 was still installing Maestro, so on a small suite one device can end up doing all the work. Extra concurrency only pays off once the suite takes longer to run than the pre stage.
For emulators, the sample's emulator config sets isRealMobile: false and switches the runner to runTest_android_emulator.sh. To launch an app that is already on the device instead of an uploaded build, set appId: stock under framework.args and name the package in the flow.
Reports and Debugging
The --format junit flag in runTest.sh makes Maestro write a JUnit XML report, the format every CI system and most test management tools already read. On HyperExecute, partialReports collects those files into the job report. When one task runs several flows, each run overwrites report.xml, so rename the file after every flow in testRunnerCommand. Without it, the sample run's job report lists only android-launch, the last flow that task executed:
testRunnerCommand: ./maestro-test/runTest.sh $test && mv report.xml $test.xmlDebugging a mobile failure needs more than a stack trace, because the useful question is what was on screen. --debug-output keeps Maestro's logs and screenshots, and with video and deviceLog switched on, every HyperExecute task also carries a screen recording and the device log. That is what turns a failure that only reproduces on one device model into something a developer without that handset can act on.
When a flow fails, HyperExecute's AI root cause analysis reads Maestro flow execution logs and device interaction logs alongside the command logs, and separates the primary cause from the failures that cascade from it. Feed the JUnit output into a test management platform to keep history across runs: a single failing flow is a defect, while the same flow failing on one device family for three weeks is a pattern worth its own ticket.
Maestro Limitations
- Physical iPhones - The open-source CLI does not run flows on physical iOS devices, so local iOS coverage stops at simulators.
- Sandboxed JavaScript - Scripts cannot read fixture files or import npm packages, so data-driven suites pass data in through HTTP calls or environment variables.
- Flow order - Maestro runs discovered flows in a non-deterministic order by default, so flows that depend on each other need an explicit execution order in the workspace config.
- Cross-platform selectors - Commands are shared between Android and iOS, but view ids and accessibility identifiers usually are not, so one flow rarely runs on both without platform conditions.
- Deep assertions - A flow sees only the screen. Checking that a value persisted correctly in the backend stays the job of API-level tests.
Teams that get the most from Maestro use it for the journeys that must never break and keep a programmable framework for suites that need heavy data setup and custom logic.
Note: Run the same Maestro flows on real Android devices, emulators, and iOS simulators from one HyperExecute config, with video and device logs attached to every task. Start free
Conclusion
Pick the one journey whose breakage would hurt most and write it as a flow today: launch, navigate, and assert one thing that proves the journey worked. If that takes longer than an hour, the obstacle is your app's selectors, and that is worth knowing on its own.
Then run it on hardware your users hold. TestMu AI's real device cloud supplies the Android devices, and the HyperExecute getting started guide sets up the CLI and credentials the sample config needs.
Testing from the terminal? Kane CLI by TestMu AI lets SDETs and developers plan, write, run and debug tests from the command line.
Author
Sai Krishna is Director of Engineering at TestMu AI, where he leads agentic AI for quality engineering, building AI agents that autonomously drive mobile and conversational test automation. His current focus is Agent Testing and Model Context Protocol (MCP) support for mobile. He is a core contributor and member of the Appium open-source project and the creator of AppiumTestDistribution and appium-device-farm. With over 14 years of experience including more than 9 years at Thoughtworks as a Principal Consultant, he holds a BSc in Electronics and speaks regularly at TestMu and Appium Conf on Appium, mobile automation, and agentic AI in testing.
Reviewer
Srinivasan Sekar is Director of Engineering at TestMu AI, where he leads engineering and open-source initiatives behind the Selenium and Appium automation grid and owns TestMu AI's MCP Server. A committer to Appium and a contributor to Selenium, WebdriverIO, Taiko, and AppiumTestDistribution, he brings over 15 years of experience in quality engineering and open-source technologies. He is the author of the Apress book 'The MCP Standard: A Developer's Guide to Building Universal AI Tools with the Model Context Protocol,' a Certified Kubernetes and Cloud Native Associate, and an international conference speaker. Before TestMu AI he spent over eight years at Thoughtworks as a Principal Consultant and Quality Architect. Srinivasan holds a B.Tech in Information Technology from Anna University.
Maestro Testing FAQs
Did you find this page helpful?
More Related Learning Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance
- Advanced access controls
- Advanced data retention rules
- Advanced Local Testing
- Premium Support options
- Early access to beta features
- Private Slack Channel
- Unlimited Manual Accessibility DevTools Tests





