Browse the Knowledge Hub211 resources

Maestro review: mobile testing pricing, pros and cons
Maestro is an open-source UI testing framework that drives Android apps, iOS simulators and web browsers from flows written in YAML. It works through the platform’s accessibility layer, so it treats the app as a black box regardless of whether it is native, React Native or Flutter. The CLI is free under Apache 2.0; Maestro Cloud is the paid hosted runner.
QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.
- last reviewed
- 13 October 2026
- version covered
- Maestro CLI 2.11
- licence
- Open source
At a glance
Maestro at a glance
- Vendor
- Maestro.dev
- Category
- Mobile automation
- Licence
- Open source (Apache 2.0)
- Platforms
- Android, iOS, Web, React Native, Flutter
- Languages
- YAML, JavaScript
- Integrates with
- Maestro Cloud, GitHub Actions, Bitrise, CircleCI
- First released
- 2022
- Version reviewed
- Maestro CLI 2.11
Verdict
Our verdict on Maestro
Strengths
- A single install and no drivers or SDK hooks; a first flow runs within minutes
- Flows are plain YAML that testers, developers and reviewers can all read
- Assertions poll until the UI settles, so most flows need no waits at all
- One flow syntax for Android, iOS simulators and web, across native, React Native and Flutter apps
- Can tap system dialogs, Settings and other apps because it drives the device, not the app
- Ships an MCP server so coding agents can explore an app and write flows
Weaknesses
- Physical iPhones are not supported; iOS testing runs on simulators
- Real logic needs JavaScript snippets in a sandbox, which is awkward at scale
- Local parallelism means several connected devices; hosted parallel runs are paid
- Text selectors are regular expressions, which surprises people with special characters
Our take
Maestro earns a place where a team wants readable end-to-end smoke and critical-path flows quickly, across Android and iOS, without building an Appium framework: one binary, a flow in minutes, and waiting handled for you. QAble has evaluated it rather than maintained a long-running suite on it, so treat this as a bounded take. The constraints are real. Flows are declarative, so complex logic moves into JavaScript snippets that are harder to structure than test code. Physical iPhones are not supported; iOS runs on simulators. Parallel runs on your own machines need several devices or the paid Cloud. And the 2.x line has removed things that older tutorials still show, such as the Rhino engine, the bundled maestro studio command and maestro chat.
We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.
How it works
How Maestro works
Piloting the device at arm’s length
Maestro describes its model as arm’s length: it pilots the device rather than the app. The CLI installs a small companion driver on the Android device or iOS simulator, reads the screen through the operating system’s accessibility tree and sends low-level taps, swipes and text input. Because nothing is compiled into the app, the same flow works on native, React Native and Flutter builds, and Maestro can act on anything the device shows, including permission dialogs, notifications, Settings and other apps. The trade is that a flow knows only what the accessibility tree exposes, so elements without text, content descriptions or identifiers are hard to target.
Flows are interpreted, not compiled
A flow is a YAML file with a header, holding the appId or a web url, tags and other settings, and a list of commands such as launchApp, tapOn, inputText, scrollUntilVisible and assertVisible. The CLI interprets it directly, so there is no build step, and continuous mode reruns a flow every time you save it. That speed of iteration is the main reason a first flow takes minutes rather than days. Reuse comes from runFlow, which calls another flow file or an inline block, optionally with its own variables.
Tolerance built into every command
Maestro’s documentation calls this built-in tolerance. Commands wait for the screen to settle before acting, and assertions such as assertVisible and assertNotVisible poll the UI until the condition holds or a timeout passes, then continue at once. extendedWaitUntil sets a longer timeout for slow work such as payments, and waitForAnimationToEnd covers movement after a transition. The effect is that flows rarely contain explicit waits. The limit is that tolerance cannot fix genuine instability, and a retry block wrapped around a whole flow can hide it, which the docs themselves call an anti-pattern.
JavaScript for what YAML cannot express
When a flow needs logic, Maestro runs JavaScript in a sandbox: inline expressions inside ${...}, evalScript for logic-only steps and runScript for external files. GraalJS is the engine from Maestro 2.0, with modern JavaScript syntax. Scripts have no file system or Node.js modules, but get a built-in HTTP client, a faker object for test data and a shared output object for passing values back to later commands. It is enough for creating a user through an API or generating unique emails, and deliberately not a general programming environment.
Strengths
What it does well
Selectors built for changing UIs
Elements are matched by visible text or by id, both regular expressions by default, and can be narrowed with relational selectors such as below or childOf and state selectors such as enabled. The documentation recommends text for what users see, ids for icons and localised screens, and enabled: true on buttons that depend on validation, so the tap waits until the button is usable.
System UI and multi-app journeys
Because Maestro drives the device, launchApp can set permissions up front, a flow can follow a link into Safari or the browser and come back, and system prompts can be tapped like any other element. Journeys that cross app boundaries, which in-process frameworks cannot see, are ordinary flows here.
Workspaces, tags and reports
A config.yaml at the workspace root defines which folders hold flows, with glob and negation patterns, and tags in each flow header let CI run subsets with --include-tags and --exclude-tags. The CLI writes JUnit or HTML reports with --format, and each run produces an artifact bundle with screenshots before every step and the view hierarchy on failure.
Studio, Viewer and MCP
Maestro Studio is a free desktop app, not open source, for building flows visually by clicking elements on a mirrored screen. Maestro Viewer shows the live hierarchy and commands as they run. The MCP server, started with maestro mcp, lets coding agents such as Claude Code or Cursor inspect a live device, act on it and write flows.
Sharding across local devices
maestro test can spread a workspace over several connected emulators or simulators: --shard-split divides the flows evenly between devices, and --shard-all runs every flow on each device. Since 2.6.0 parallel iOS simulator execution is supported. It is the free route to parallelism, bounded by how many devices one machine can run.
Hosted parallel runs in Maestro Cloud
maestro cloud or the official GitHub Action uploads the app binary and flows, runs them in parallel on hosted Android, iOS and web devices, and reports back with links in JUnit and HTML. Device model, OS version and locale are chosen per run. Each test execution has a 20-minute soft limit, so long journeys should be split into shorter flows.
Pricing
Maestro pricing and licensing
The Maestro CLI is free and open source under the Apache 2.0 licence, and Maestro Studio, MCP and Viewer are free to use on your own devices. Money enters through Maestro Cloud, which runs flows in parallel on hosted Android, iOS and web devices and is billed per device per month, based on maximum concurrent executions. An Enterprise Cloud plan adds SSO, dedicated support and custom contracts at a custom price.
- Licence: Apache 2.0 for the CLI and core, stated in the repository. Studio is free but closed source.
- Local (free): CLI, Studio, MCP and Viewer on your own emulators, simulators, browsers and Android devices, with no account.
- Cloud: list price on maestro.dev/pricing on 13 October 2026 was $250 per device per month, where devices means maximum concurrent executions; web and Chromium devices are priced on request. A free trial is offered.
- Enterprise Cloud: custom pricing, adding SSO, premium support, custom contract terms and a vendor security review.
- Hidden cost: iOS simulators need macOS machines, and local parallelism needs enough CPU and memory for several emulators at once.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
Maestro tutorial: getting started
Setup, step by step
- Install Java 17 or later and make sure JAVA_HOME points to it.
- Install the CLI with the official script, or Homebrew on macOS, and check it with maestro --version.
- Start an Android emulator or an iOS simulator and install a debug build of your app on it.
- Write a flow in a .maestro folder, using your app’s package name or bundle id as appId.
- Run maestro test on the flow, or add -c for continuous mode while you write it.
Install
# macOS, Linux or WSL (needs Java 17+)
curl -fsSL "https://get.maestro.mobile.dev" | bash
maestro --version
# run a flow against the running emulator or simulator
maestro test .maestro/login.yamlA first example
# .maestro/login.yaml
appId: com.example.app
tags:
- smoke
---
- launchApp:
clearState: true # start from a fresh install state
- tapOn:
id: "login-email" # ids are regular expressions, like text
- inputText: "[email protected]"
- tapOn:
id: "login-password"
- inputText: "correct-horse"
- tapOn: "Sign in" # a plain string matches visible text
# Polls until visible or times out: no sleep needed
- assertVisible: "Welcome back"What trips people up first
Finding what Maestro can see. A tapOn that fails on an element you can see usually means the element exposes no text or identifier in the accessibility tree, or the text matches as a regular expression in a way you did not expect, for example a price with a plus sign or brackets. Open Maestro Studio or run maestro hierarchy to inspect the tree, ask developers to add accessibility identifiers (resource ids or test tags on Android, accessibilityIdentifier on iOS, Semantics identifiers in Flutter), and escape regex characters in text selectors.
In practice
Using Maestro in practice
Workspace layout, subflows and tags
Keep flows in a .maestro folder at the repository root, which is also the default workspace for the Cloud GitHub Action. By default the CLI runs only flows at the top level of the folder, so add a config.yaml with flows patterns as soon as you create subfolders, and use negation patterns to keep helper files out. Put shared steps such as signing in under a subflows folder and call them with runFlow, passing variables with env. Tag every flow, at least smoke or regression, so CI can choose a subset without moving files. Keep each flow to one journey; long flows are slower to debug and run into Cloud’s 20-minute soft limit.
# .maestro/config.yaml
flows:
- "journeys/**"
- "!subflows/**" # helpers are called, never run directly
# .maestro/journeys/checkout.yaml
appId: ${APP_ID}
tags:
- regression
---
- runFlow:
file: ../subflows/sign-in.yaml
env:
EMAIL: "[email protected]"
- tapOn: "Basket"
- scrollUntilVisible:
element: "Checkout"
- tapOn: "Checkout"
- assertVisible: "Order summary"
# Run with: maestro test -e APP_ID=com.example.app --include-tags=regression .maestroConditions, data and JavaScript
Use runFlow with a when block for genuinely optional screens, such as a rating prompt or a platform-specific permission dialog, and keep separate flows for scenarios that differ a lot; the docs warn that heavy conditional logic makes flows hard to read. For test data, a runScript step can call your API through Maestro’s HTTP client and store values on the output object, which later commands read inside ${...}. This keeps setup out of the UI, which is faster and less flaky than creating accounts through screens. Use retry sparingly, around a single unstable step with maxRetries between 0 and 3, never around a whole flow.
# .maestro/journeys/new-user.yaml
appId: com.example.app
---
- runScript:
file: ../scripts/create-user.js # uses http.post and sets output.email
env:
API_URL: ${API_URL}
- launchApp:
clearState: true
- runFlow:
when:
visible: "Rate this app"
commands:
- tapOn: "Not now"
- tapOn:
id: "login-email"
- inputText: ${output.email}
- runFlow:
when:
platform: iOS
file: ../subflows/allow-notifications-ios.yaml
# .maestro/scripts/create-user.js
# const res = http.post(API_URL + '/test-users', { body: '{}' })
# output.email = json(res.body).emailRunning on an emulator in GitHub Actions
Without Maestro Cloud, a Linux runner can host an Android emulator: enable KVM, install Java 17 and the CLI, add ~/.maestro/bin to the path, and let the android-emulator-runner action boot the emulator and run your commands. Pin the CLI with MAESTRO_VERSION so a new release cannot change behaviour between runs of the same commit, and write a JUnit report plus the test output folder so failures can be inspected after the runner is gone. iOS flows need a macOS runner with a booted simulator instead.
# .github/workflows/maestro-android.yml
name: maestro-android
on: [push, pull_request]
jobs:
flows:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 17
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Install a pinned Maestro CLI
run: |
export MAESTRO_VERSION=2.11.0
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
script: |
adb install app/build/outputs/apk/debug/app-debug.apk
maestro test --format junit --output report.xml --test-output-dir maestro-out .maestro
- uses: actions/upload-artifact@v7
if: failure()
with:
name: maestro-results
path: |
report.xml
maestro-outScaling out: local shards or Maestro Cloud
There are two ways to run faster. Locally, start several emulators or simulators and pass --shard-split with the device count so flows are divided between them; this costs nothing but machine capacity, and iOS needs a Mac. With Maestro Cloud, the official action mobile-dev-inc/action-maestro-cloud uploads the app file and the .maestro workspace with an API key and project id, runs flows in parallel on hosted devices, and accepts include-tags, device-model, device-os and device-locale inputs. It requires a Cloud plan. Whichever you choose, keep flows independent, starting from launchApp with clearState where needed, because sharded flows run in no guaranteed order.
Debugging failures and upgrading safely
Each run writes an artifact bundle with a screenshot before every step, device logs and, on failure, the view hierarchy, so start there rather than rerunning. maestro hierarchy prints the current tree as JSON, Maestro Viewer shows commands and the live hierarchy, and maestro record turns a flow into a video for bug reports. Before upgrading, read the changelog: 2.0 moved to Java 17 and GraalJS and dropped URLs in appId in favour of url; 2.6 removed the Rhino engine and the bundled Studio; 2.7 discontinued maestro chat in favour of MCP. The next release will also reject out-of-range scroll speeds and negative point coordinates that were previously ignored.
Pitfalls
Common pitfalls
Planning to run flows on physical iPhones
Why it happens: Maestro supports iOS simulators only. From 2.11 the CLI no longer lists physical iPhones and fails early with a message that they are not yet supported.
The fix: Run iOS flows on simulators locally or in Maestro Cloud, and cover real-device iOS checks with XCUITest, Appium or a device cloud.
Flows in subfolders are silently skipped
Why it happens: Pointed at a folder, the CLI runs only flows at its top level, to avoid running helper flows by accident.
The fix: Add a config.yaml with flows patterns such as journeys/** and negation patterns for helpers, and check the run summary lists every flow you expect.
Text selectors that never match
Why it happens: text and id selectors are regular expressions, so characters such as +, ( or ? change the meaning, and partial strings may match more than intended.
The fix: Escape regex characters, anchor patterns where precision matters, or switch to an id selector agreed with developers.
Wrapping whole flows in retry
Why it happens: It turns a flaky button into a green build, and the docs warn that retrying an entire flow can give unpredictable results.
The fix: Retry only a single known-unstable step, at most three times, and fix or report the underlying flakiness.
Following tutorials written for Maestro 1.x
Why it happens: Older material uses the bundled maestro studio command, the Rhino JavaScript engine, maestro chat and URLs in appId, all removed or changed in 2.x.
The fix: Use the Maestro Studio desktop app, GraalJS syntax, Maestro MCP instead of chat, and the url header field for web flows.
scrollUntilVisible scrolls the wrong area
Why it happens: It swipes from the centre of the screen, which misses a bottom sheet, fragment or small widget that scrolls independently.
The fix: Use a custom scroll loop that swipes inside the scrollable region, as the docs describe for screen fragments, or target the element by id after a fixed swipe.
Expecting disableAnimations to work locally
Why it happens: The config.yaml animation setting is a Cloud-only flag and has no effect on local emulators or simulators.
The fix: Turn off animations on local and CI emulators through developer settings, and rely on Maestro’s settle waits and waitForAnimationToEnd where motion remains.
Compared
Maestro alternatives compared
| Tool | Test format | Platforms | How it finds elements | Waiting | Licence and paid parts |
|---|---|---|---|---|---|
| Maestro | YAML flows, optional JavaScript | Android, iOS simulators, web | Accessibility tree, black box | Built in, polling assertions | Apache 2.0; Cloud paid per device |
| Appium | Code in five client languages | Android, iOS devices, tvOS, desktop | WebDriver locators through platform drivers | Explicit waits in test code | Apache 2.0; device clouds sold separately |
| Detox | JavaScript or TypeScript tests | React Native on iOS, Android | Test ids in an instrumented build | Automatic, monitors app async work | MIT, no paid tier |
| Espresso | Kotlin or Java tests | Android only | View matchers inside the app process | Automatic main-thread idle checks | Apache 2.0, no paid tier |
FAQ
Frequently asked questions
What are the best Maestro alternatives?
The alternatives most often weighed against Maestro are Appium, Detox and Espresso. The comparison on this page sets them side by side on test format, platforms, how it finds elements, waiting and licence and paid parts, and the verdict above says where Maestro is the wrong choice and which of them to pick instead.
Is Maestro free?
The Maestro CLI is open source under Apache 2.0, and Maestro Studio, MCP and Viewer are free to use on your own emulators, simulators, browsers and Android devices. Maestro Cloud, for hosted parallel runs, is paid: the list price on its pricing page on 13 October 2026 was $250 per device per month, based on maximum concurrent executions, with Enterprise on custom pricing.
Should I use Maestro or Appium?
Choose Maestro for fast, readable smoke and critical-path flows across Android and iOS simulators, written by testers or developers without a framework to build. Choose Appium when you need real iPhones, test code in a general-purpose language with full logic and abstractions, or an existing WebDriver ecosystem. Some teams use Maestro for smoke checks on every pull request and Appium for deeper suites.
Maestro or Detox for a React Native app?
Detox is built for React Native and synchronises with the app’s internals, so its waits are precise, but it needs an instrumented build and tests in JavaScript. Maestro needs no app changes and works the same on native and Flutter screens, at the cost of black-box timing based on the UI settling. Teams that value app-aware synchronisation pick Detox; teams that value speed of adoption pick Maestro.
Can Maestro test on real iPhones?
No. Maestro runs iOS flows on Xcode simulators, and since version 2.11 the CLI no longer lists physical iPhones and stops with a message that they are not yet supported. Physical Android devices, Android emulators and desktop browsers are supported. For real-device iOS coverage, use XCUITest, Appium or a device cloud alongside Maestro.
Can Maestro handle logic, loops and test data?
Yes, within limits. Flows support conditions with runFlow and when, loops with repeat, subflows with variables and retries. For anything more, JavaScript runs in a sandbox through inline expressions, evalScript and runScript, with an HTTP client, a faker object and a shared output object. There is no file system or Node.js module access, so complex data handling belongs in your API or a setup script outside Maestro.
Does Maestro test web applications?
Yes. A flow with a url header instead of an appId drives a desktop browser, and the CLI has headless and screen-size options for web runs. The same commands and selectors apply, plus a css selector for web. It is useful for teams that want one flow format across mobile and web, though dedicated browser tools such as Playwright offer far more control.
What is Maestro MCP?
An MCP server built into the CLI and started with maestro mcp. It lets coding agents such as Claude Code, Cursor or Codex inspect a live device, tap, scroll and assert, and write flows that are then committed as ordinary YAML tests. It replaced the discontinued maestro chat command in version 2.7.
Alternatives
Maestro alternatives worth weighing
Appium
Open-source automation server that drives iOS, Android, tvOS, Windows and macOS apps through the W3C WebDriver protocol, using separately installed platform drivers.
Detox
Grey-box end-to-end framework for React Native, synchronising with the app rather than polling.
Resources
Where to go next
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.