← back to blog

how to run Maestro flow tests on cloud phones

May 06, 2026

how to run Maestro flow tests on cloud phones

if you want fast e2e tests for any mobile app without writing a single line of compiled test code, Maestro is the simplest path. Maestro flows are YAML files that read like English: tap, swipe, assert visible. no Gradle, no test runner, no synchronization model. and because Maestro speaks ADB on Android, any cloud phone reachable over ADB-over-network is a valid test target. this post walks the full setup: install Maestro, connect to a cloudf.one phone, write a flow, run it, parallelize, and integrate with CI.

cloud phones suit Maestro especially well for two reasons. first, Maestro is platform-agnostic, so the same flow runs on any Android device including real cloud phones. second, Maestro’s flake-resistance is best when device timing is consistent, which real arm64 hardware delivers more reliably than emulators.

why Maestro over Detox, Espresso, or Appium

three reasons teams pick Maestro. it has the lowest activation energy: install one binary, write a YAML file, run it. it is framework-agnostic: native Android, React Native, Flutter, hybrid all work the same way. and it is built around flow testing rather than unit-style assertions, which matches how product teams think about user journeys.

the tradeoff is depth. Detox knows about React Native bridges. Espresso runs in-process with your Android app. Maestro is more shallow but covers more cases with less code. for product-team smoke tests, Maestro is the right tool. for deep app-internal logic, Espresso is still the right tool. our Detox post and Espresso post cover those depths.

prerequisites

on your laptop or CI runner: Java 11 or newer, Android platform-tools (for adb), and Maestro itself.

# install Maestro on macOS or Linux
curl -Ls "https://get.maestro.mobile.dev" | bash

# verify
maestro --version

step 1: connect to your cloud phone

adb connect adb-sg.cloudf.one:5555
adb devices

confirm the cloud phone shows up. our ADB setup guide covers troubleshooting.

step 2: install your APK

Maestro does not build APKs. install the APK manually:

adb -s adb-sg.cloudf.one:5555 install -r app-debug.apk

or download a Play Store app for testing:

# the Maestro samples app is on Play
adb -s adb-sg.cloudf.one:5555 shell am start \
  -a android.intent.action.VIEW \
  -d 'https://play.google.com/store/apps/details?id=com.example.maestrosample'

step 3: write your first flow

create flows/smoke.yaml:

appId: com.example.maestrosample
---
- launchApp
- assertVisible: "Welcome"
- tapOn: "Get started"
- assertVisible:
    id: "username_input"
- tapOn:
    id: "username_input"
- inputText: "test"
- tapOn:
    id: "password_input"
- inputText: "test123"
- tapOn: "Log in"
- assertVisible: "Home"

that’s a complete login smoke test. no compile step, no test runner, just YAML.

step 4: run the flow

maestro --device adb-sg.cloudf.one:5555 test flows/smoke.yaml

Maestro launches the app, runs each step in order, and prints PASS or FAIL with the failing step. if a step fails, Maestro auto-captures a screenshot and saves the view hierarchy.

step 5: write a more realistic flow

a real product flow has loops, conditions, and reusable steps.

appId: com.example.app
---
- launchApp:
    clearState: true
- runFlow: subflows/login.yaml
- tapOn: "Search"
- inputText: "shoes"
- pressKey: Enter
- assertVisible: "Results"
- repeat:
    times: 3
    commands:
      - swipe:
          direction: UP
          duration: 500
- tapOn:
    text: "Add to cart"
    index: 0
- assertVisible: "Item added"

the runFlow directive reuses subflows, keeping your test files DRY. the repeat block scrolls the result list three times.

step 6: assert with deeper conditions

Maestro supports JavaScript expressions for complex assertions:

- evalScript: |
    const total = parseInt(maestro.copyTextFrom("cart_total").replace("$", ""));
    output.expectedTotal = total;
- assertTrue: ${output.expectedTotal > 0}

this opens the door to flows that read data from the UI, perform computation, and assert against the result.

step 7: parallelize across cloud phones

Maestro has built-in cloud and CI integrations, but for self-managed cloud phones, parallelism is just running multiple Maestro processes against multiple devices.

adb connect adb-sg.cloudf.one:5555
adb connect adb-sg.cloudf.one:5556
adb connect adb-sg.cloudf.one:5557

maestro --device adb-sg.cloudf.one:5555 test flows/login.yaml &
maestro --device adb-sg.cloudf.one:5556 test flows/checkout.yaml &
maestro --device adb-sg.cloudf.one:5557 test flows/profile.yaml &
wait

each Maestro instance drives one cloud phone. shard your flows across devices for faster CI runs.

step 8: capture artifacts on failure

Maestro auto-captures a screenshot on failure. for video, run screenrecord in the background:

adb -s adb-sg.cloudf.one:5555 shell screenrecord --time-limit 180 /sdcard/run.mp4 &
SCREEN_REC_PID=$!

maestro --device adb-sg.cloudf.one:5555 test flows/smoke.yaml
RESULT=$?

kill $SCREEN_REC_PID 2>/dev/null
adb -s adb-sg.cloudf.one:5555 pull /sdcard/run.mp4 artifacts/

exit $RESULT

upload the artifacts via your CI’s artifact system; see CI/CD integration.

debugging flaky flows

three issues catch teams new to Maestro.

first, animation timing. disable system animations on the cloud phone:

adb -s adb-sg.cloudf.one:5555 shell settings put global window_animation_scale 0
adb -s adb-sg.cloudf.one:5555 shell settings put global transition_animation_scale 0
adb -s adb-sg.cloudf.one:5555 shell settings put global animator_duration_scale 0

second, locator strategy. Maestro defaults to text matching, which breaks if your UI is localized or if text changes. prefer id: (Android resource-id) for stable locators.

third, view hierarchy timing. add waitForAnimationToEnd before assertions if the UI animates frequently.

- waitForAnimationToEnd
- assertVisible: "Home"

Maestro Studio: visual flow recording

Maestro ships with a visual recorder that connects to a device and lets you click your way through a flow, generating YAML.

maestro --device adb-sg.cloudf.one:5555 studio

this opens a browser that mirrors the device. click through your flow, and Maestro generates the YAML in real time. great for product managers and designers who want to write tests without learning syntax.

try Maestro on a real Singapore cloud phone

register for a free trial for a one-hour cloud phone token. install Maestro, connect to the cloud phone, run the smoke YAML against any APK. once confirmed, scale to a paid plan and add cloud phones to your e2e fleet.

frequently asked questions

does Maestro work with Flutter and React Native?

yes, both. Maestro is framework-agnostic; it drives the device UI regardless of what stack the app uses. our Flutter testing post covers Flutter specifics.

what’s the latency for Maestro from outside Singapore?

Maestro itself does most computation locally. ADB-over-network latency adds 150 to 250 ms per command from outside Singapore. flows that have many quick taps in a row run noticeably slower from a US runner. for tight CI windows, run from an Asia-Pacific runner.

can I write Maestro flows in JavaScript instead of YAML?

partially. you can embed JavaScript expressions inside YAML via evalScript and output.foo. but the flow control (steps, conditions) is YAML. for full JavaScript control, look at Detox or WebdriverIO.

does Maestro need a special build of my APK?

no. Maestro tests the production APK as-is. no instrumentation, no test APK, no debug build required. unlike Espresso or Detox, Maestro does not link into your app.

how does Maestro Cloud differ from running Maestro locally?

Maestro Cloud is a managed service from the Maestro team that runs your flows on their devices. running Maestro locally against cloudf.one cloud phones gives you the same flow language with full control over the device pool. the Maestro docs cover both modes.