Skip to content

Releases: devicelab-dev/maestro-runner

v1.1.14

12 May 07:46

Choose a tag to compare

Release 1.1.14

This release closes out the Flutter Web testing story. v1.1.13 fixed the finding layer (selectors traverse same-origin iframes, index is a first-class web selector). v1.1.14 completes it: selectors also pierce open shadow roots, tapOn dispatches at correct top-frame viewport coordinates when the target lives inside an iframe (with hit-target verification), the same path extends to doubleTapOn / longPressOn / scrollUntilVisible, visibility checks intersect iframe content viewports, and tapOn handles Flutter Web's <flutter-view> pointer-router glass pane that consumes trusted events before any third-party listener can observe them. A real Flutter Web user — @richjun — drove most of this with two substantial PRs (#73, #74) and two issue reports (#71, #72).

Added

  • Selectors pierce open shadow roots on webtext / CSS / id / attribute /
    role finders, plus the visibility and wait helpers, now recurse through
    every same-origin <iframe> and every open shadowRoot reachable from
    them. Flutter Web mounts its accessibility tree inside an open shadow root
    attached to <flt-glass-pane>, so tapOn: "Close" against a Flutter Web
    semantics node now resolves to the actual element. Closed shadow roots
    remain unreachable (same constraint every WebDriver-class tool has — no
    fix possible without privileged access). Reported by
    @richjun (#71).
  • tapOn text + index enumerates across iframes / shadow roots
    completes the #67 fix from 1.1.13.
    Previously the resolver enumerated matches only within the top frame, so
    asking for index 1 when matches 0..N-1 lived in the top frame and the
    real target lived in an iframe silently re-tapped the in-range top-frame
    match — green test, wrong button. Now walks every same-origin root via
    _collectRoots(), sorts by document order, and indexes deterministically.
    Out-of-range returns a precise error with the actual match count instead
    of falling back. Reported by @richjun
    (#72).
  • tapOn dispatches at top-frame coordinates for iframe-nested targets
    Rod's Element.Click() used iframe-LOCAL viewport coordinates from
    getBoundingClientRect(); CDP Input.dispatchMouseEvent operates in
    TOP-FRAME viewport coordinates. The click landed at the wrong place and
    tapOn reported success silently. Now ports Playwright's
    _checkFrameIsHitTarget walk: from the target outward, adds each
    ancestor <iframe> element's box plus its content-area inset (border +
    padding) to convert iframe-local → top-frame viewport coordinates.
    Hit-target verification runs as both static pre-flight (rejects
    occluded / wrong-element clicks before dispatch) and post-click trusted-
    event capture (verifies the click landed on the target's frame tree).
    Contributed by @richjun in
    #73.
  • doubleTapOn / longPressOn / scrollUntilVisible inherit the
    iframe-coord-translated path
    — same root cause as tapOn had. Now
    routed through a shared dispatchCrossRoot helper. scrollUntilVisible
    for iframe-nested targets calls native Element.scrollIntoView() inside
    the element's own document (the previous page-level Mouse.Scroll only
    scrolled the outer document and never reached iframe content).
  • Visibility check intersects iframe content viewport
    _isElementVisible used to do intrinsic-only checks (computed style +
    getBoundingClientRect() dimensions) and reported elements scrolled or
    clipped outside their iframe's content viewport as "visible." This made
    assertVisible / waitForVisible / extendedWaitUntil silently pass
    on iframe-clipped elements, and made scrollUntilVisible's loop exit
    on iteration 0 (the new scrollIntoView branch was unreachable in
    practice). Now walks the iframe ancestor chain at each level,
    intersecting with the iframe's content viewport. Empty intersection
    returns false; surviving rect is translated to parent coordinates and
    rechecked. Top-frame "below the fold" elements stay visible — only
    iframe clipping is added.
  • tapOn into Flutter Web semantics — three orthogonal fixes for
    Flutter Web targets. findBySearch now rejects non-tappable text
    containers (<script> / <style> / <template> / etc.) because CDP
    DOM.performSearch matches against serialized HTML and Flutter Web
    pages whose JS source contains the button label as a string literal
    silently returned the <script> element. The hit-target pre-flight
    and post-click verifier both accept the Flutter <flutter-view> glass-
    pane occlusion case (target + topmost hit both inside <flutter-view>);
    Flutter intercepts trusted pointer events at the document/glass-pane
    capture layer and routes them through its own internal pointer router
    for semantics dispatch, so the verifier's one-shot listener never fires
    and a strict same-element walk-up always reports false occlusion. Non-
    Flutter occlusion (overlay div, modal, genuine z-stack) continues to
    fail-fast — the Occluded and Transformed regression tests still reject.
    Contributed by @richjun in
    #74.

Fixed

  • runScript per-call scope + persistent output mutations — two
    related bugs. (a) top-level const / let / function declarations
    collided across runScript calls because the JS engine reused a single
    Goja runtime's global scope, surfacing as
    SyntaxError: Identifier 'word' has already been declared on the second
    invocation. Each runScript now executes inside an IIFE so top-level
    declarations are function-scoped to that invocation. (b) Mutations like
    output.list.push(x) did not persist across runScript calls because
    the output proxy returned a snapshot Go map per call — only whole-
    value reassignment (output.list = [...]) survived. The output bag
    is now a Goja-native Object shared across invocations so mutations
    persist. Reported by @Sina-KH
    (#70).
  • iOS openLink on simulatorPOST /session/<sid>/url on
    WebDriverAgent v12+ returns Unhandled endpoint: /url. Users who ran
    maestro-runner wda update and got the newer WDA hit a hard failure
    on every openLink step, blocking Expo dev client flows where deep
    linking loads the JS bundle from Metro. Bypassed entirely on
    simulators by shelling out to xcrun simctl openurl <udid> <url>
    same primitive Maestro CLI uses, faster, no WDA version coupling.
    Real iOS devices keep the existing WDA /url path (simctl can't
    reach them). Reported by @jongbelegen
    (#68).
  • iOS clearState on simulator no longer requires --app-file
    the runner needs to uninstall + reinstall the app to wipe its data
    container (Apple doesn't expose a "clear data only" API). Previously
    failed with either clearState on iOS requires --app-file (no
    --app-file) or lstat ... No such file or directory (if
    --app-file pointed inside the live sim container, which the
    uninstall deleted before install could read it). Now auto-discovers
    the installed .app via xcrun simctl get_app_container and copies
    it to a temp directory before the uninstall — same approach Maestro
    CLI uses (LocalSimulatorUtils.kt#reinstallApp). Reported by
    @jongbelegen
    (#69).

Contributors

@richjun

  1. Reported selectors not piercing shadow DOM (#71)
  2. Reported tapOn text+index not spanning iframes (#72)
  3. Contributed iframe + shadow-root coord-translated tapOn with hit-target verification (#73)
  4. Contributed Flutter Web semantics support — finder rejection, pre-flight and post-click glass-pane concession (#74)

@Sina-KH

  1. Reported runScript top-level declaration collisions and non-persistent output mutations (#70)

@jongbelegen

  1. Reported iOS openLink failing on simulator after WDA upgrade (#68)
  2. Reported iOS clearState on simulator failing without / with --app-file (#69)

Installation

Quick Install

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

Install a specific version

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

Manual install (from the assets below)

# 1. Download the tarball for your platform.
# 2. Extract:
tar -xzf maestro-runner-1.1.14-darwin-arm64.tar.gz

# 3. Run setup (installs into ~/.maestro-runner/bin/):
cd maestro-runner && ./setup.sh

Verify Installation

maestro-runner --version

Platform Support

  • macOS Intel (amd64) - Signed & Notarized
  • macOS Apple Silicon (...
Read more

v1.1.13

05 May 14:33

Choose a tag to compare

Added

  • Same-origin iframe traversal on web — text/CSS/ID/attribute selectors now
    walk into same-origin <iframe> content (e.g. Flutter Web embedded under a
    host page). Cross-origin / OOPIF iframes are still skipped, but the
    not-found error now surfaces a clear (skipped N cross-origin iframes — full OOPIF support not implemented yet) hint so users debugging a missing
    selector can tell the cause is frame isolation, not a typo. Reported by
    @richjun (#65).
  • Mobile-style index selector on webtapOn: { text: "Help", index: 1 }
    now picks the second match instead of being silently dropped as
    unsupported. The web finder accepts both index (string, mobile-style) and
    nth (int) via a single EffectiveNth() helper, so the same flow YAML
    works across Android, iOS, and web. Reported by
    @richjun (#67).
  • Sauce Labs job context per flow — the runner now posts
    sauce:context to Sauce on every flow start so jobs surface the YAML
    basename in the Sauce UI, and renames empty / "Default Appium Test" jobs
    on completion using the first flow's filename. Real-device caps without
    appium:jobUuid fall back to VMS + session id so REST status updates
    still target the right job. Contributed by
    @eyaly (#66).

Fixed

  • onFlowStart hook with default appIdlaunchApp (and other app
    lifecycle steps) inside onFlowStart / onFlowComplete now resolve the
    flow's default appId the same way as top-level steps. Previously the
    hook ran with an empty AppID, causing a silent no-op on Android. Fixes
    #62, reported
    by @zcsteele.
  • copyTextFrom on Appium 3.x — stop pushing the captured text to the
    device clipboard via POST /appium/device/set_clipboard, which Appium 3
    returns 404 for. The runner already keeps the value in memory (matching
    Maestro's design) so pasteText continues to work. Fixes
    #61, reported
    by @kavithamahesh.
  • iOS permission dialogs blocking real-device flows — WDA's alerts
    monitor only registers when defaultAlertAction is in the session-creation
    capabilities; the runner now defaults to accept so notification (and
    other) permission dialogs auto-dismiss out of the box. Fixes
    #64, reported
    by @j-ezeh.
  • assertVisible silently wrong for state filters / nth / role — the JS
    fast path bypassed several capabilities the Go finder already implemented,
    so selectors with enabled / checked / focused / nth / role /
    ID-cascade hit the fast path and produced wrong answers. Centralised
    routing now sends those selectors to the Go finder; the JS path's id
    case also runs the same data-testid / name / aria-label cascade.

Contributors

@richjun

  1. Reported same-origin iframe selector failures with Flutter Web (#65)
  2. Reported index selector being silently dropped on web (#67)

@zcsteele

  1. Reported onFlowStart hook unable to reference default appId (#62)

@kavithamahesh

  1. Reported copyTextFrom failing on Appium 3.x with 404 (#61)

@j-ezeh

  1. Reported iOS permission dialogs not auto-accepted on real devices (#64)

@eyaly

  1. Improved Sauce Labs job naming + per-flow context (#66)

Installation

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

Platform Support

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

Built by DeviceLab.dev

v1.1.12

22 Apr 14:48

Choose a tag to compare

Added

  • Tap optionsrepeat, delay, retryTapIfNoChange, and waitToSettleTimeoutMs now
    honored during execution on all drivers (uiautomator2, wda, devicelab, appium, cdp).
    Implemented at the executor layer, zero driver-side changes.
    (#52, #53)
    - tapOn:
        id: "login-button"
        repeat: 3
        delay: 500
        retryTapIfNoChange: true
        waitToSettleTimeoutMs: 2000
  • runFlow timeouttimeout: parameter on runFlow steps with context propagation
    into driver polling loops. Element-finding cancels immediately on expiry, and failures
    are classified as TIMEOUT in reports. Ref
    #29, thanks to
    @maraujop for the suggestion.
    - runFlow:
        file: common/login.yaml
        timeout: 5000
        env:
          username: devicelab
  • Cloud Provider lifecycle hooksProvider interface now exposes OnRunStart,
    OnFlowStart, and OnFlowEnd alongside the existing ExtractMeta and ReportResult.
    Cloud integrations can update dashboards live per-flow instead of only at run end.
    Sauce Labs ships with no-op placeholders for the new hooks.
  • UI.waitForSettle RPC — on-device tree-comparison settle detection on the DeviceLab
    Android driver, used as an auto-settle before inputText / eraseText to avoid key
    events firing mid-transition.
  • Clickable-ancestor promotion — when a DeviceLab tap matches text on a non-clickable
    descendant (e.g. "Sign In" TextView inside a clickable login-button ViewGroup), the
    agent now walks up to the nearest clickable ancestor.
  • hintText matchinghintContains / hintMatches UiSelector extensions on the
    DeviceLab driver match an EditText's android:hint placeholder. Lets
    tapOn: "Email" find an empty email field by its hint.
  • Case-insensitive text matching on AndroidtextContains / descriptionContains
    now fall back to case-insensitive match when case-sensitive fails, fixing Android dialog
    buttons where textAllCaps displays "CANCEL" but the view hierarchy text is
    "Cancel". Reported by @satya164.
  • Appium parallel execution — run flows across N Appium sessions concurrently. Each
    session connects to the same Appium URL; the server allocates devices.
    (#47)
  • --wda-bundle-id flag — custom WebDriverAgent bundle identifier for signing
    scenarios where the default bundle id isn't usable.
    (#48)
  • Device info in Appium reports — device info and session ID now surface in console
    output and JUnit/Allure reports for Appium runs.

Changed

  • Simpler inputText without selector — DeviceLab and UIAutomator2 drivers now send
    key events directly via SendKeyActions instead of attempting
    findFocused / ActiveElement fallbacks. Matches Maestro's "type into whatever the OS
    has focused" behavior.
  • Updated DeviceLab Android driver APK to ship UI.waitForSettle, clickable-ancestor
    promotion, and hintText predicate support.
  • Appium parallel session count is capped at the number of flows (prints a warning
    when parallel count exceeds flow count).

Fixed

  • iOS install hang on iOS 17+ / iOS 26 — prefer xcrun devicectl device install app
    over the legacy go-ios zipconduit path on real devices. Both paths now run under a
    3-minute context timeout so a stuck install surfaces as an error instead of an infinite
    spinner. Escape hatch via MAESTRO_RUNNER_IOS_INSTALLER=zipconduit|devicectl. Fixes
    #54, thanks to
    @ptmkenny for the clear repro.
  • clearKeychain on iOS — standalone clearKeychain step and
    launchApp { clearKeychain: true } both now work. Previously the step erred with
    Step type '*flow.ClearKeychainStep' is not supported on iOS, and the launchApp
    flag was a silent no-op (users stayed logged in). On simulators runs
    xcrun simctl keychain <udid> reset; on real devices returns a clear unsupported
    message pointing to clearState as the alternative. Fixes
    #57, thanks to
    @ross-aker for reporting.
  • Swipe LEFT / RIGHT on Android — use screen coordinates directly instead of the
    previous element-relative computation that misbehaved.
  • when: { true: <expr> } silently always-true — the true: field wasn't parsed
    (YAML tag bound to the internal scriptCondition name instead), so conditions were
    ignored and commands always ran. Fixes
    #60, reported by
    @satya164 and
    @kavithamahesh.
  • Env var default syntax${VAR || "default"} and ${VAR ?? "fallback"} now
    resolve correctly. Undefined JS variables auto-define as undefined on
    ReferenceError, matching Maestro's GraalJS Proxy behavior. Fixes
    #49,
    #50.

Contributors

@ptmkenny

  1. Reported the iOS install hang on iOS 17+/26 with a clear repro (#54)

@ross-aker

  1. Reported clearKeychain not working on iOS Simulator (#57)

@satya164

  1. Reported Android dialog textAllCaps case mismatch (CANCEL vs Cancel)
  2. Reported when: { true: <expr> } parsing bug (duplicated by #60)

@kavithamahesh

  1. Reported when.true condition ignored (#60)

@maraujop

  1. Suggested runFlow timeout (#29)

Installation

Quick Install

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

Version-specific install

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

Manual Download

Download the archive for your platform below, extract, and place bin/maestro-runner on your PATH.

Platform Support

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

Built by DeviceLab.dev

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