Skip to main content

Maestro on Sauce Labs with maestro-runner

Community Supported Virtual and Real Devices

Maestro is a YAML-based mobile UI testing framework. maestro-runner is an open-source command-line tool, maintained by DeviceLab, that runs unmodified Maestro flows by translating each flow step into Appium commands. Point it at the Sauce Labs Appium endpoint and your existing Maestro flows run on Sauce Labs Android emulators, iOS simulators, and Android and iOS real devices, with the video, device log, Appium log, and screenshots you get from any Appium job.

Community Supported

This framework is built and maintained by its open-source project, not by Sauce Labs. Sauce Labs supports the cloud side: devices, browsers, endpoints, and test artifacts. Report framework issues to the project's issue tracker. Validated with the version noted below; later versions may differ.

Sauce Labs validated this guide with maestro-runner 1.1.25 in September 2026. Use 1.1.25 or later; earlier versions could lose pre-created sessions to the Sauce Labs idle timeout during parallel runs.

How It Works with Sauce Labs​

+--------------------------+ +-----------------------------+ +----------------------------+
| Your machine or CI | | Sauce Labs | | Sauce Labs device |
| | Appium | ondemand.<dc>.saucelabs | Appium | emulator, simulator, or |
| Maestro YAML flows | (HTTPS) | .com/wd/hub | | real device |
| maestro-runner | ---------> | Appium server | ---------> | your app from App Storage |
| --driver appium | | | | |
+--------------------------+ +-----------------------------+ +----------------------------+
| |
| HTML, JUnit, Allure reports | video, logs, screenshots, pass/fail
v v
local report directory Sauce Labs Test Results
  1. You upload your app build to Sauce Labs App Storage.
  2. You write one Appium capabilities file per Sauce Labs target type: Android emulator, iOS simulator, Android real device, or iOS real device.
  3. maestro-runner opens an Appium session on Sauce Labs with those capabilities and translates each Maestro step (tapOn, inputText, assertVisible, and so on) into Appium commands.
  4. Sauce Labs records the job like any Appium test: video, device log, Appium log, and a screenshot per command.
  5. When the flow finishes, maestro-runner names the Sauce Labs job after the flow file, sets its pass or fail status through the Sauce Labs REST API, and writes HTML, JUnit, and Allure reports locally.

Nothing runs on the Sauce Labs side except the Appium session. maestro-runner needs no Sauce Labs specific configuration beyond the endpoint URL and capabilities.

What You'll Need​

  • A Sauce Labs account (Log in or sign up for a free trial license).
  • Your Sauce Labs Username and Access Key.
  • Node.js to install maestro-runner from npm.
  • Your app builds: an .apk for Android, an .ipa for iOS real devices, and a zipped .app simulator build for iOS simulators. To try the steps without your own app, use the Sauce Labs My Demo App builds in the Sauce Labs maestro-runner demo repository.
  • Maestro flows. The demo repository includes flows for the My Demo App on both platforms.

Step 1: Install maestro-runner​

Install maestro-runner as a development dependency of your test project. It is a single binary with no Java requirement.

npm install --save-dev maestro-runner
npx maestro-runner --version

You can also download a release binary from the maestro-runner GitHub releases.

Set your SAUCE_USERNAME and SAUCE_ACCESS_KEY as environment variables so you never write them into flows, capabilities files, or CI configuration.

Check Environment Variables
echo $SAUCE_USERNAME
echo $SAUCE_ACCESS_KEY

If nothing is returned, set them:

export SAUCE_USERNAME="your Sauce username"
export SAUCE_ACCESS_KEY="your Sauce access key"

Step 3: Upload Your App to Sauce Labs​

maestro-runner installs your app from Sauce Labs App Storage. Upload each build to the data center you will run in, and keep the file name that your capabilities file references.

Upload to App Storage (US West)
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" --location \
--request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' \
--form 'payload=@"SauceLabs-Demo-App.apk"' \
--form 'name="SauceLabs-Demo-App.apk"'

Repeat for the .ipa and the simulator .zip. You can also upload through Sauce Labs > App Management or the Upload File to App Storage API.

note

App Storage is per data center. If you switch between us-west-1, eu-central-1, and us-east-4, upload the build again in the new data center.

Step 4: Create a Capabilities File for Each Target​

maestro-runner reads standard Appium capabilities from a JSON file passed with --caps. Create one file per Sauce Labs target type. The examples below are the files Sauce Labs validated with the My Demo App; replace the appium:app file name, and for Android the package and activity, with your own.

provider-caps/android-emulator.json
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Google Pixel 9 Emulator",
"appium:platformVersion": "16.0",
"appium:app": "storage:filename=SauceLabs-Demo-App.apk",
"appium:appPackage": "com.saucelabs.mydemoapp.android",
"appium:appActivity": ".view.activities.SplashActivity",
"appium:appWaitActivity": "*",
"sauce:options": {
"build": "maestro-android-emulator",
"appiumVersion": "2.11.0"
}
}

Points to note:

  • appium:app uses the storage:filename= form, so the file name must match the upload in Step 3 exactly.
  • Emulator and simulator names and OS versions come from the Platform Configurator. ARM emulators are described on the Android Emulators page.
  • Real device capabilities use regular expressions for appium:deviceName and appium:platformVersion so Sauce Labs can allocate any matching device. See Appium on Real Devices.
  • iOS real devices need resigningEnabled: true so Sauce Labs can install your .ipa. iOS simulators need the simulator build, not the .ipa.
  • Pin appiumVersion to a version listed on the Appium Versions page.
  • Leave your username and access key out of the file. maestro-runner takes them from the endpoint URL in Step 5.
  • Set build so you can find all the jobs from one run in Test Results. Add name only if you want a fixed job name instead of the flow name.

Step 5: Run a Flow​

Pass the Sauce Labs Appium endpoint, with your credentials, as --appium-url, and the capabilities file for the target you want. The last argument is a flow file or a directory of flows.

Run one flow on an Android emulator (US West)
export SAUCE_HUB="https://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@ondemand.us-west-1.saucelabs.com/wd/hub"

npx maestro-runner --driver appium --appium-url "$SAUCE_HUB" \
--caps provider-caps/android-emulator.json \
test --output results/android-emulator flows/android/login_standard_user.yaml

For the EU Central or US East data centers, replace us-west-1 with eu-central-1 or us-east-4. See Data Center Endpoints.

The flow itself is ordinary Maestro YAML. Nothing in it refers to Sauce Labs:

flows/android/login_standard_user.yaml
appId: com.saucelabs.mydemoapp.android
name: Login - standard user
tags:
- smoke
- login
---
- assertVisible:
id: "productRV"
- tapOn:
id: "menuIV"
- tapOn: "Log In"
- tapOn:
id: "nameET"
- inputText: "bod@example.com"
- tapOn:
id: "passwordET"
- inputText: "10203040"
- hideKeyboard
- tapOn:
id: "loginBtn"
- assertVisible:
id: "menuIV"

Step 6: View Your Results​

maestro-runner prints each step as it runs and writes HTML, JUnit, and Allure reports to the --output directory. The Sauce Labs job is available as soon as the session ends under Automated > Test Results for emulators and simulators, or Real Devices for real devices. Filter by the build value you set in Step 4.

Running Suites in Parallel​

maestro-runner can run a directory of flows across several Sauce Labs sessions at once, or run them all in one session.

Four concurrent emulator sessions
npx maestro-runner --driver appium --appium-url "$SAUCE_HUB" \
--caps provider-caps/android-emulator.json \
test --parallel 4 --output results/android-parallel flows/android/
Whole iOS suite in one reused real device session
npx maestro-runner --driver appium --appium-url "$SAUCE_HUB" \
--caps provider-caps/ios-real-device.json \
test --parallel 1 --output results/ios-suite flows/ios/
  • --parallel N starts up to N Sauce Labs sessions and feeds flows to them from a queue. Each session is one Sauce Labs job. The runner never starts more sessions than you have flows, and your Sauce Labs concurrency limit still applies.
  • --parallel 1 over a directory runs every flow in a single session, which appears as a single job. Start each flow with launchApp so Maestro restarts the app between flows.
  • --include-tags and --exclude-tags select flows by the tags in their YAML header.
  • Flow discovery is one level deep: test flows/android/ runs only the .yaml files directly inside that directory. Keep one directory per platform and run each with its own --caps file.
  • --appium-session-file sessions.json writes the Appium session IDs of the running sessions. On emulators and simulators the Appium session ID is also the Sauce Labs job ID. On real devices the job ID differs, so use the build name to find jobs.
  • --flatten writes reports directly into --output instead of a timestamped subdirectory, which is easier to archive from CI.

How Jobs Appear in Sauce Labs​

  • Name: the flow file's base name, for example login_standard_user. When one session runs several flows, the job takes the first flow's name. Set name in sauce:options for a fixed name.
  • Status: maestro-runner marks the job passed or failed when the run finishes. It picks the REST API endpoint from the data center in your --appium-url (us-west-1, eu-central-1, or us-east-4).
  • Framework: Sauce Labs shows the job as an Appium test. Maestro steps appear as the Appium commands they were translated into, not as named Maestro steps, and there are no per-flow annotations in the job.
  • Artifacts: video, device log, Appium log, and a screenshot per command, exactly as for any Appium job.
  • Reports: the Maestro-style HTML, JUnit, and Allure reports exist only in your local --output directory. They do not include the Sauce Labs job link, so use --appium-session-file or the build name to cross-reference.

Supported Targets​

Verified by Sauce Labs in September 2026 with maestro-runner 1.1.25 and the My Demo App flows.

Sauce Labs targetValidated onResult
Android emulatorGoogle Pixel 9 Emulator, Android 16✔️ flows pass
Android ARM emulatorGoogle ARM Medium Phone Emulator, Android 16✔️ flows pass
iOS simulatoriPhone Simulator, iOS 18.0✔️ flows pass
Android real deviceSamsung Galaxy S26 Ultra, Android 16✔️ flows pass
iOS real deviceiPhone 16 Pro, iOS 18.7.1✔️ flows pass
Parallel suite (--parallel 4)Four Android emulator sessions✔️ four concurrent jobs
Session reuse (--parallel 1)One iOS real device session✔️ one job, all flows
Mobile web (browserName set)Chrome on Android emulator❌ not supported
Desktop web (--platform web)Sauce Labs desktop browsers❌ not supported

Limitations​

  • Mobile web is not supported. maestro-runner's Appium driver parses the native UI hierarchy only. A Sauce Labs browser session returns HTML page source, so the first assertVisible fails with invalid page source: no hierarchy element found.
  • Desktop web is not supported on Sauce Labs. The runner's --platform web mode launches a local Chrome and has no option to attach to a remote browser.
  • No Maestro step names in the Sauce Labs job. Steps appear as Appium commands.
  • Data center detection is by URL. The pass or fail update goes to eu-central-1 or us-east-4 when the endpoint URL contains that name, otherwise to us-west-1.
  • Parallel emulator reports show one device. All concurrent emulator sessions report the same device ID, so the local report's per-device summary collapses to a single entry. The Sauce Labs jobs are unaffected.

Security Considerations​

Your access key appears in maestro-runner logs

maestro-runner prints the full --appium-url, including your access key, in its Connecting to Appium server log line. It writes the same line into maestro-runner.log inside every report directory, and it writes the URL into the --appium-session-file. Before you adopt it in CI:

  • Mask SAUCE_ACCESS_KEY in your CI system so it is redacted from console output.
  • Do not publish report directories or session files as build artifacts without removing the key.
  • Do not commit report or session files to source control.

Check the maestro-runner release notes for a fix to the credential echo before relying on unmasked logs.

Troubleshooting​

SymptomCause and fix
invalid page source: no hierarchy element found on the first stepThe capabilities start a browser session. Mobile web is not supported; test the native app instead.
Invalid version format used when the session startsappiumVersion: "latest" was used with a browser session. Pin a version from the Appium Versions page.
iOS real device install or launch failsAdd resigningEnabled: true to sauce:options, and use the .ipa, not the simulator build.
iOS simulator install failsUse the zipped .app simulator build in appium:app, not the .ipa.
Parallel sessions end before their flows startUpgrade to maestro-runner 1.1.25 or later, which keeps pre-created sessions active.
Flows in subdirectories are skippedDiscovery is one level deep. Point test at the directory that contains the flow files.
The runner reports Update availableThe npm package can lag GitHub releases by a few days. Either channel works with Sauce Labs.

More Information​