Skip to content

Releases: devicelab-dev/maestro-runner

v1.1.1

06 Apr 10:49

Choose a tag to compare

Release v1.1.1

Added

  • Cloud provider abstraction — automatic detection and result reporting for cloud device providers (Sauce Labs, BrowserStack, LambdaTest, etc.) when using the Appium driver. Test pass/fail status, flow results, and metadata are reported to the provider after the run completes. Based on @eyaly's Sauce Labs integration (#43, #45)
    # Sauce Labs — automatically detected from the Appium URL
    maestro-runner --driver appium --appium-url "https://ondemand.us-west-1.saucelabs.com/wd/hub" \
      --caps caps.json test flows/
  • Source file path in FlowResult — each flow result now includes the path to the source YAML file, used by cloud providers and report consumers

Changed

  • Updated DeviceLab Android driver APK with latest on-device agent
  • Airplane mode commands now use cmd connectivity airplane-mode enable/disable (Android 11+) instead of the legacy settings put global airplane_mode_on approach

Fixed

  • CDP waitForPageReady crash — replaced panicking MustWaitLoad() with error-handling WaitLoad() in the browser CDP driver, preventing test run crashes on pages with deeply nested object references
  • Removed unused freePort() function from DeviceLab WebView driver
  • Removed unused regex variables (reLabel, reHint, reValue) from Flutter semantics parser
  • Tightened variable scope in Flutter widget tree parser

Contributors

@eyaly

  1. Implemented original Sauce Labs pass/fail reporting integration (#43), which formed the basis for the cloud provider abstraction in #45

Installation

Quick Install

curl -fsSL https://raw.githubusercontent.com/devicelab-dev/maestro-runner/main/install-download.sh | bash

Manual Download

Download the binary for your platform below, make it executable, and move to your PATH.

macOS:

chmod +x maestro-runner-darwin-arm64  # or darwin-amd64
mv maestro-runner-darwin-arm64 /usr/local/bin/maestro-runner

Linux:

chmod +x maestro-runner-linux-amd64  # or linux-arm64
sudo mv maestro-runner-linux-amd64 /usr/local/bin/maestro-runner

Verify Installation

maestro-runner --version

Platform Support

  • macOS Intel (amd64) - Signed & Notarized
  • macOS Apple Silicon (arm64) - Signed & Notarized
  • Linux amd64
  • Linux arm64

Built by DeviceLab.dev

v1.1.0

25 Mar 19:20

Choose a tag to compare

What's New

WebView CDP Support for Android

The DeviceLab driver now connects to Android WebViews via Chrome DevTools Protocol. When a WebView is detected, maestro-runner automatically uses CDP for element finding and JavaScript execution — no configuration needed.

# Automatic — CDP kicks in when a WebView is visible
maestro-runner --driver devicelab test webview-flow.yaml
# Your flow doesn't change — WebView elements are found via CDP transparently
- launchApp:
    appId: com.example.app
    clearState: true
- tapOn: "Open WebView"
- assertVisible: "Welcome"        # Found via CDP inside the WebView
- tapOn:
    id: "submit-button"           # CDP element finding

Chrome Browser CDP on Android

The DeviceLab driver can now automate Chrome browser on real Android devices via CDP, enabling web testing directly on Android hardware.

New Commands: evalWebViewScript & runWebViewScript

Execute JavaScript inside a mobile WebView via CDP — the WebView equivalents of evalBrowserScript and runBrowserScript.

evalWebViewScript — inline JavaScript execution:

# Simple — returns document title
- evalWebViewScript: "return document.title"

# With output variable
- evalWebViewScript:
    script: "return document.querySelector('#price').textContent"
    output: price

# Use the result in assertions
- assertTrue: ${price == '$7.50'}

runWebViewScript — execute a JavaScript file:

# Simple file execution
- runWebViewScript: scripts/extract-data.js

# With environment variables and output
- runWebViewScript:
    file: scripts/validate-cart.js
    env:
      EXPECTED_TOTAL: "29.99"
    output: validationResult

Network Idle Detection & DOM Stability Waits

After navigations (in both browser and WebView contexts), maestro-runner now waits for network idle and DOM stability before proceeding. This reduces flakiness on pages with async loading — no more waitForAnimationToEnd hacks after navigation.

CDP Browser Improvements

  • RAF-based visibility polling — element visibility checks now use requestAnimationFrame-based polling, improving reliability for dynamically rendered content
  • <select> option supporttapOn with <option> elements correctly selects the option via JavaScript instead of attempting a click
  • JS click fallback — when a native click fails on a browser element, falls back to JavaScript .click() for better reliability with overlapping elements

Changes

  • Default WDA swipe duration changed from 300ms to 100ms for faster, more responsive swipe gestures on iOS
  • JavaScript helper code extracted from Go string literals into dedicated embedded .js files for easier maintenance (#37)

Bug Fixes

  • Swipe coordinates now match Maestro behavior across all drivers (UIAutomator2, DeviceLab, WDA, Appium) — previously, swipe start/end positions differed from Maestro's implementation
  • assertNotVisible now correctly polls for disappearance instead of polling for appearance — previously, the command would pass immediately if the element wasn't visible, without waiting for it to disappear after an action
  • Filter out-of-bounds elements from page source searches — elements with coordinates outside the visible screen bounds are now excluded, preventing false matches on off-screen elements (#39)
  • Text node attribute error — fixed TypeError: this.getAttribute is not a function when browser CDP encounters text nodes (#35, #36)
  • iOS WDA session lifecycle — improved driver reliability with better session creation, cleanup, and error recovery
  • --team-id no longer required for auto-detected simulators — when a booted simulator is auto-detected, --team-id is automatically skipped
    # Before: required --team-id even when simulator is already booted
    # Now: just works
    maestro-runner --platform ios test flow.yaml
  • Flutter reconnection — skip retries for non-Flutter apps instead of wasting time on connection attempts. Non-Flutter apps now pay zero retry cost
  • WebView CDP forwarder — wired SetWebViewForwarder in the DeviceLab driver, which was never connected — elements were previously found only via native UiAutomator accessibility tree
  • hideKeyboard reliability — on-device agent now uses KEYCODE_ESCAPE first (keyboard-only, no navigation side-effects), falls back to KEYCODE_BACK if needed. Retries up to 3 times with keyboard visibility polling
  • In-WebView navigation — when visibility check fails during in-WebView page navigation (JS context destroyed), refreshes page reference and retries instead of skipping CDP entirely
  • CDP text match filtering — text-based visibility checks (text, textContains, textRegex) now filter to the deepest matching element, preventing false positives from ancestor elements whose textContent includes hidden children's text

Thanks

Thanks to everyone who reported issues and contributed code!

  • @tmahesh — fixed text node attribute error in browser CDP (#36), refactored JS helpers into embedded files (#37)
  • @mahesh-e27 — reported text node attribute bug in browser CDP (#35)
  • @sircharleswatson — reported assertVisible passing for off-screen text in browser (#39)
  • @satishs22 — reported tapOn timeout issue on Android emulator (#25)
  • @chrisjin-swipe — reported inputText character skipping on Android (#32)

Installation

curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash

# Install this specific version
curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash -s -- --version 1.1.0

Documentation

Platform Support

  • macOS Intel (amd64) — Signed & Notarized
  • macOS Apple Silicon (arm64) — Signed & Notarized
  • Linux amd64
  • Linux arm64

Built by DeviceLab.dev

v1.0.9

11 Mar 07:16

Choose a tag to compare

What's New

  • Desktop browser testing — new --platform web with built-in CDP driver for Chrome/Chromium. Headless by default, --headed for visible browser. Supports parallel browser execution

    maestro-runner --platform web test flow.yaml
    maestro-runner --platform web --headed --browser chrome test flow.yaml
    maestro-runner --platform web test --parallel 3 flows/
  • Browser-specific commandsevalBrowserScript, setCookies, getCookies, saveAuthState, loadAuthState, openTab, switchTab, closeTab, mockNetwork, blockNetwork, setNetworkConditions, waitForRequest, clearNetworkMocks, uploadFile, waitForDownload, grantPermissions, resetPermissions, getConsoleLogs, clearConsoleLogs, assertNoJSErrors, runBrowserScript

  • Browser selectorscss and xpath selectors for web elements, in addition to text and id

    - tapOn:
        css: "button.submit"
    - inputText:
        id: "username"
        text: "hello"
  • --no-app-install flag — skip app installation even if --app-file is provided

    maestro-runner --no-app-install --app-file app.apk test flow.yaml
  • --no-driver-install flag — skip driver installation (UIAutomator2, WDA, DeviceLab)

    maestro-runner --no-driver-install test flow.yaml

Bug Fixes

  • iOS simulator no longer requires --team-id — simulators don't need code signing, so the validation now only enforces --team-id for real devices
  • Fixed banner rendering in CI — removed Unicode box-drawing characters and stripped OSC 8 hyperlink escape codes when output is piped

Install

curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash

# Install this specific version
curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash -s -- --version 1.0.9

Documentation

v1.0.8

04 Mar 20:41

Choose a tag to compare

What's New

  • Flutter VM Service fallback — when the native driver can't find a Flutter element, automatically discovers the Dart VM Service and searches the semantics/widget trees in parallel. Works on Android and iOS simulators. Non-Flutter apps pay only one log read on first miss, then fully bypassed. Disable with --no-flutter-fallback

  • Flutter widget tree cross-reference — when semantics tree search fails, falls back to widget tree analysis (hint text, identifiers, suffix icons) and cross-references with semantics nodes for coordinates

  • DeviceLab Android driver — WebSocket-based on-device automation, ~2x faster than UIAutomator2

    maestro-runner --driver devicelab --platform android test flow.yaml
  • setAirplaneMode and toggleAirplaneMode for iOS — automates the Settings app to toggle airplane mode on real devices

    - setAirplaneMode:
        enabled: true
    - setAirplaneMode: enabled
    - toggleAirplaneMode
  • maxTypingFrequency for iOS — configurable typing speed via --typing-frequency flag (default: 30 keys/sec)

    maestro-runner --typing-frequency 15 test flow.yaml
  • scrollUntilVisible maxScrolls and timeout — now fully wired for all 4 drivers (previously parsed but ignored)

    - scrollUntilVisible:
        element:
          text: "Sign Out"
        direction: "down"
        maxScrolls: 5
        timeout: 10000
  • On-failure WebView detection with CDP-aware error enrichment

  • Regex pattern support for ID selectors across all drivers

    - tapOn:
        id: "username-.*"
    - assertVisible:
        id: "(username|email)-input"
  • repeat with while condition now loops correctly instead of executing only once

    - repeat:
        while:
          visible: "Delete"
          timeout: 2000
        commands:
          - tapOn: "Delete"

Bug Fixes

  • runFlow: when conditions with variable expressions (e.g., ${output.element.id}) were never expanded, causing conditions to always evaluate as false
  • iOS real device: acceptAlertButtonSelector matched "Don't Allow" instead of "Allow" — changed to BEGINSWITH[c] 'Allow' with OK fallback
  • AllocatePort was ignoring existing port allocations
  • repeat-while condition check timeout reduced from 17s to 7s default
  • Android setAirplaneMode/toggleAirplaneMode failed with SecurityException on Android 7+ — now uses cmd connectivity airplane-mode on Android 11+, with settings put fallback for older versions
  • Implicit wait warning resolved by using Appium settings endpoint
  • assertVisible optional timeout and optimized tap element finding
  • WDA launchApp optimized: parallel permissions and removed sleeps

Test Results (v1.0.8)

Device Driver Result Duration
Pixel 4a (real) UIAutomator2 9/9 pass 2m 21s
Pixel 4a (real) DeviceLab 9/9 pass 1m 18s
iPhone 16 (sim) WDA 8/9 pass 2m 56s

Contributors

v1.0.7

20 Feb 05:39

Choose a tag to compare

What's Changed

Added

  • Appium driver: newSession option for launchApp — creates a fresh Appium session, useful when clearState fails on real iOS devices (mobile: clearApp unsupported). On iOS real devices with newSession: true, clearState is skipped since a fresh session already provides clean state (#14)
  • Bundled UIAutomator2 server upgraded from v9.9.0 to v9.11.1 with new LaunchApp endpoint (getLaunchIntentForPackage + startActivity)
  • Android: classify error types in report (element_not_found, timeout, assertion, keyboard_covering, etc.) for better debugging
  • Android: detect keyboard covering elements after inputText/inputRandom — when the soft keyboard covers a target element, taps land on the keyboard instead. Now detects this with a clear error message suggesting - hideKeyboard
  • Auto-create iOS simulators when not enough shutdown simulators exist for --parallel — created simulators are automatically deleted on shutdown
  • Parallel device selection: in-use detection via WDA port check (iOS) and socket check (Android) to skip devices already claimed by another maestro-runner instance

Fixed

  • iOS real device: clearState no longer kills WDA connection — replaced go-ios (installationproxy/zipconduit over usbmuxd) with xcrun devicectl (over Apple's remoted daemon), which doesn't interfere with USB port forwarding
  • Android: scroll and scrollUntilVisible direction was invertedscroll down was scrolling up because /appium/gestures/scroll already uses scroll semantics, no inversion needed (#9)
  • Android: launchApp failed with "No apps can perform this action" on certain devices — resolve-activity was called without -a android.intent.action.MAIN -c android.intent.category.LAUNCHER flags. New three-tier launch strategy: (1) UIAutomator2 server getLaunchIntentForPackage() on-device, (2) shell fallback with proper flags + dumpsys parsing + API-level-aware am start, (3) monkey fallback (#15)
  • Android: server APK install now checks version and handles signing conflicts (uninstall + reinstall when version mismatches)
  • index selector was ignored in simple (non-relative) selectorstapOn: text: X, index: 1 always tapped the first match. Now selectors with a non-zero index route through page source parsing, which returns all matches and picks the Nth one
  • -e env variables were not expanding in flow config appIdappId: ${APP_ID} with -e APP_ID=com.myapp sent the literal ${APP_ID} to adb. Now expands using ExpandVariables() before setting as a variable (#12)
  • Parallel device selection: devices are now filtered by platform (excludes tvOS/watchOS/xrOS) and in-use devices are skipped (#11)
  • Android: emulator port allocation skipped ports occupied by running emulators
  • CLI: flags must come before flow paths in command examples

Thanks

Thanks to everyone who reported issues and helped make this release better! 🙏

  • @ditzdragos — reported launchApp "No apps can perform this action" on Android (#15)
  • @popatre — reported clearState failing on real iOS devices via Appium (#14)
  • @hyry2024 — reported -e env variables not expanding in flow config appId (#12)
  • @DouweBos — reported parallel device selection issues — non-iOS simulators selected and in-use devices not skipped (#11)
  • @janfreund — reported scroll direction inversion with video evidence (#9)
  • @SuperRoach — reported keyboard covering elements after input steps on Android, and index selector being ignored in simple selectors

Installation

curl -fsSL https://open.devicelab.dev/maestro-runner/install | bash

Platform Support

  • ✅ macOS Intel (amd64) — Signed & Notarized
  • ✅ macOS Apple Silicon (arm64) — Signed & Notarized
  • ✅ Linux amd64
  • ✅ Linux arm64

Built with ❤️ by DeviceLab.dev

v1.0.6

17 Feb 09:26

Choose a tag to compare

What's New

Fixed

  • iOS WDA: off-screen elements no longer returned by findElementassertVisible, tapOn, scrollUntilVisible, and all element commands now correctly reject elements not visible in the viewport
  • iOS WDA: scrollUntilVisible no longer skips scrolling when the target element exists in the accessibility tree but is off-screen (#9)
  • iOS WDA: scrollUntilVisible direction matching is now case-insensitive (e.g., direction: "DOWN" works)
  • iOS WDA: waitForIdleTimeout now works on iOS via WDA quiescence
  • when: platform condition was ignored in runFlow blocks (#8)

Contributors

Thanks to everyone who reported issues and suggested improvements!

@janfreund

  1. Reported scrollUntilVisible and element visibility issues on iOS (#9)

@kavithamahesh

  1. Reported when: platform condition being ignored (#8)

Installation

Quick Install

curl -fsSL https://open.devicelab.dev/maestro-runner/install | bash

Verify

maestro-runner --version

Platform Support

  • macOS Apple Silicon (arm64) — Signed & Notarized
  • macOS Intel (amd64) — Signed & Notarized
  • Linux amd64
  • Linux arm64

Built with love by DeviceLab.dev

v1.0.5

16 Feb 09:19

Choose a tag to compare

What's New

Added

  • tapOn: point now supports absolute pixel coordinates (e.g., point: "286, 819") in addition to percentages (#6) — see docs
  • Coordinate validation: negative values, out-of-bounds pixels, and percentage range (0-100%) are rejected with clear error messages
  • Screen size cached at session startup instead of fetching on every tap/swipe/scroll
  • launchApp: environment for passing environment variables to the app (#7) — see docs

Changed

  • Extracted shared helpers from drivers into pkg/core to reduce duplication
  • Removed hardcoded 1080x1920 screen size fallback in UIAutomator2 scroll/swipe

Fixed

  • launchApp: arguments silently failed on real iOS devices — early return after session creation, unpopulated env map, activate vs launch, missing variable expansion (#7)
  • Removed unused AI flags (--analyze, --api-url, --api-key)

Contributors

Thanks to everyone who reported issues and suggested improvements!

@mahesh-e27

  1. Reported tapOn: point not supporting absolute pixel coordinates (#6)
  2. Spotted unused AI flags (--analyze, --api-url, --api-key)

@majdukovic

  1. Reported launchApp: arguments not working on real iOS devices (#7)

Installation

Quick Install

curl -fsSL https://open.devicelab.dev/maestro-runner/install | bash

Verify

maestro-runner --version

Platform Support

  • macOS Apple Silicon (arm64) — Signed & Notarized
  • macOS Intel (amd64) — Signed & Notarized
  • Linux amd64
  • Linux arm64

Built with love by DeviceLab.dev

v1.0.4

13 Feb 15:56

Choose a tag to compare

What's New

Added

  • keyPress option for character-by-character text input on Android
  • Stale socket cleanup on force-stop (Ctrl+C / kill -9) with PID-based locking

Fixed

  • iOS Appium driver: element finding and tap reliability (use label instead of content-desc for accessibility)
  • iOS Appium driver: pressKey command support
  • iOS Appium driver: tapOn and inputText reliability improvements
  • iOS Appium driver: skip --app-file and --team-id pre-checks (not needed for Appium)
  • iOS Appium driver: skip clearState on real devices (mobile: clearApp only works on simulators)
  • iOS WDA driver: auto-alert handling on simulators (accept/dismiss permission dialogs)
  • takeScreenshot command now correctly saves PNG files
  • GitHub star link in HTML report
  • All errcheck violations fixed with proper error logging

Contributors

Thanks to everyone who reported issues and suggested improvements!

@SuperRoach

  1. Suggested the keyPress feature for character-by-character input
  2. Suggested the --team-id pre-check for WDA driver
  3. Reported the takeScreenshot bug

@kavithamahesh

  1. Reported iOS element finding issue — label instead of content-desc (#3)
  2. Reported pressKey not working for iOS on Saucelabs (#4)

@janfreund

  1. Reported clearState and iOS permission dialog handling issues (#2)

u/Healthy_Carpet_26

  1. Reported the stale socket issue on force-stop (Ctrl+C)

Installation

Quick Install

curl -fsSL https://open.devicelab.dev/maestro-runner/install | bash

Verify

maestro-runner --version

Platform Support

  • ✅ macOS Apple Silicon (arm64) — Signed & Notarized
  • ✅ macOS Intel (amd64) — Signed & Notarized
  • ✅ Linux amd64
  • ✅ Linux arm64

Built with ❤️ by DeviceLab.dev

v1.0.3

11 Feb 09:52

Choose a tag to compare

What's New

iOS Permission Handling (#2)

  • Simulator: simctl privacy with three modes — allow (grant), deny (revoke), unset (hands off)
  • Real device: WDA defaultAlertAction at session creation with custom acceptAlertButtonSelector for non-standard dialogs (e.g. location)

Permission Modes

YAML Behavior
No permissions field all: allow (default) — everything granted silently
permissions: all: allow Same — everything granted silently
permissions: all: deny Everything revoked silently — app sees .denied
permissions: all: unset Hands off — don't touch permissions at all

acceptAlert / dismissAlert Commands

New flow commands for handling system dialogs manually. Polls with 500ms interval, 5s default timeout (configurable). Succeeds silently if no alert appears.

- launchApp:
    permissions:
      all: unset
- acceptAlert              # polls up to 5s, taps Allow
- acceptAlert:
    timeout: 3000          # custom timeout
- dismissAlert             # polls up to 5s, taps Don't Allow

iOS clearState via Uninstall+Reinstall

clearState: true now works on iOS by uninstalling and reinstalling the app. Requires --app-file flag.

Suite Detection

Expands runFlow-only files into separate test cases automatically.

Other

  • Fix LICENSE to match canonical Apache 2.0 text
  • Change default waitForIdleTimeout from 5000ms to 200ms

v1.0.2

04 Feb 18:25

Choose a tag to compare

Changes

  • Export driver functions for library usage
  • Change default waitForIdleTimeout from 5000ms to 200ms

Library Usage

driver, cleanup, _ := cli.CreateDriver(cfg)
defer cleanup()

for flowPath := range getFlows() {
    f, _ := flow.ParseFile(flowPath)
    result, _ := cli.ExecuteFlowWithDriver(driver, cfg, *f)
}