Skip to content

v1.1.28

Latest

Choose a tag to compare

@omnarayan omnarayan released this 01 Oct 09:34

Release 1.1.28

This release is about running a Maestro suite and getting Maestro's answer. Most of it came from running real Maestro suites (DuckDuckGo Android and iOS, React Native's RNTester, React Navigation) side by side with Maestro and fixing each place the two disagreed. The largest of those is selector matching: text and id selectors now match the whole value, as Maestro does, which is a behaviour change — see below. DeviceLab becomes the default driver on both platforms: on iOS it runs a new agent, on simulators and on real iPhones. Every flow gets DEVICE_UDID, -e stops splitting values at commas, and the Appium driver closes a long list of gaps with the native drivers.

⚠️ Behaviour changes

DeviceLab is the default driver on Android and on iOS. Running without --driver now uses the on-device DeviceLab agent on both platforms: on Android instead of UIAutomator2, on iOS instead of WebDriverAgent, on simulators and real iPhones (a real iPhone still needs --team-id, as WDA did). Nothing changes for a run that names its driver. To keep the previous defaults, pass --driver uiautomator2 on Android or --driver wda on iOS; both drivers are unchanged. See Changed below for the numbers behind the switch.

Text and id selectors match the whole value, ignoring case, as in Maestro. A plain text selector used to match any element whose text, content description or hint contained it, so tapOn: Open could land on a "Talk · Open" row and assertVisible: English passed on "Language: English". Ids matched as substrings too: id: login found com.app:id/login_button. Maestro compiles every text and id selector as a case-insensitive regex that must match the whole value (an id may match the whole resource-id or the part after the package prefix), falls back to the literal text when the pattern does not compile, and also accepts a value equal to the pattern. That rule now applies on DeviceLab, UIAutomator2 and Appium on Android, on --driver devicelab and --driver devicelab-legacy on iOS, and on Appium for iOS — both in the queries sent to the device and in the page-source matchers. A flow that relied on substring matching must say so with a regex. The WDA driver keeps its existing predicate matching: it prefers an element whose whole text matches and falls back to a substring match when there is none (#188, #161).

- tapOn: "Open"               # only an element whose whole text is "Open" (any case)
- tapOn: ".*Open.*"           # any element whose text contains "Open"
- tapOn: { id: "login" }      # "login" or "com.app:id/login", not "login_button"
- tapOn: { id: ".*login.*" }  # the old substring behaviour, spelled out

Regex selectors ignore case again. 1.1.26 made regex text selectors case-sensitive on the premise that Maestro does; it does not — every text and id selector is compiled with IGNORE_CASE, and suites depend on it ((let's get started!|Let's do it!) against "Let's get started!"). Regexes now ignore case. The problem 1.1.26 fixed is kept solved as a preference: when several elements match, those matching in the pattern's own case come first, so ^SIGN OUT$ still taps the "SIGN OUT" button ahead of a "Sign out" row (#151).

An index: past the number of matches finds no element. It used to fall back to the first match, so assertNotVisible: {id: row, index: 1} ("there is no second row") failed with one row on screen, and a tap with an out-of-range index hit the first row. Now it names no element, on every mobile driver (#188).

-e keeps commas in values. -e REPLY="Thanks, going with Vue" reached the flow as REPLY=Thanks. Each -e is now one variable, as in Maestro, so -e A=1,B=2 sets A to 1,B=2; pass -e A=1 -e B=2 instead (#189).

Element swipes go from the element's centre to 10% / 90% of the screen, as Maestro does on Android and iOS, instead of across 90% to -10% of the element's own bounds. On a chat list whose bottom edge sat under a docked composer, the old swipe began on the composer and the list never scrolled. Element swipes now travel further than before; point: and distance: keep their behaviour (#189).

Added

  • New iOS driver: --driver devicelab runs the DeviceLab iOS agent, a new XCUITest agent shipped prebuilt in drivers/ios/devicelab-ios-agent/, on simulators and real iPhones. It is the iOS default (see Changed); --driver wda selects WebDriverAgent, and the previous DeviceLab runner is kept as --driver devicelab-legacy so the two can be compared on the same flows.
    • Simulators: the agent is installed and launched with simctl (falling back to xcodebuild test-without-building), re-attached when a live agent of the same version is found, and left running between runs (DEVICELAB_IOS_AGENT_KEEP=0 stops it). The agent's port is derived from the simulator's UDID, so parallel simulators do not collide.
    • Real iPhones: pass --team-id. On the first run the prebuilt agent is re-signed for your team and device (through a small public signing-stub project that Xcode provisions), cached under ~/.maestro-runner/cache/devicelab-ios-agent/device-signed/ until the agent changes or its profile nears expiry, then started over usbmux. A start that fails is retried; an untrusted certificate, Developer Mode off, a locked phone or a rejected signature stops at once with what to do. DEVICELAB_IOS_AGENT_BUNDLE_ID signs it under another bundle id for personal teams. clearKeychain, setPermissions, setLocation, addMedia, the clipboard and screen recording are not available on a device and say so.
    • Maestro semantics on the host: the agent returns candidates and Maestro's selector rules pick among them, with relative selectors, index, state and size filters. Only elements at least 10% on screen are looked up, as Maestro's out-of-bounds filter does, and the node budget counts on-screen nodes only, so the footer of a long web page is reachable. Taps, swipes and scrolls are synthesized touches in Maestro's shape and durations; after a step that can move the screen, the next action waits until the screen is still and the app is idle. scrollUntilVisible honours centerElement and speed, and stops when the screen stops changing. Launch arguments follow Maestro's iOS rule (a boolean keeps its key as written). Element bounds are computed from truncated edges, as Maestro does, so cropOn screenshots match baselines Maestro took.
    • App lifecycle: launchApp resets permissions then applies the flow's (all: allow when none are named) on a simulator; clearKeychain, killApp and launchApp: clearKeychain work; clearState reinstalls from a copy of the app staged once per build (DEVICELAB_IOS_FAST_CLEARSTATE=1 empties the data container instead). On a simulator the driver turns Reduce Motion on and password AutoFill off before the app starts (DEVICELAB_IOS_SIM_PREFS=0 leaves the simulator untouched).
    • Web content: a WebKit inspector client watches the app's web pages, so the settle after a tap also waits for the visible page to finish loading and for its network requests to stop (DL_IOS_WEB_READY=0 turns this off).
    • Recovery: an agent that dies is restarted once; read-only commands are re-sent, actions never are.
    • The agent binaries are proprietary, free to use with maestro-runner; see drivers/LICENSE-BINARIES.md.
    # Simulator
    maestro-runner --platform ios --driver devicelab test flows/
    # Real iPhone
    maestro-runner --platform ios --driver devicelab --team-id <TEAM_ID> \
      --device <UDID> --app-file MyApp.app test flows/
  • DEVICE_UDID in every flow — the serial (Android) or UDID (iOS) of the device the flow runs on, the same for every flow on that device. Under --parallel an app that keys its mocks by a per-device launch argument can now tell the devices apart. A DEVICE_UDID you set yourself (shell environment or -e) is kept. runShell's MAESTRO_DEVICE_ID also names the worker's own device under --parallel instead of the run's first one. Sharding (MAESTRO_SHARD_ID) is not supported (#187).
    - launchApp:
        arguments:
          deviceId: ${DEVICE_UDID}
  • -e NAME takes the value from the shell environment, as docker -e does, and warns when NAME is not set instead of dropping it silently (#189).
    API_TOKEN=abc123 maestro-runner test -e API_TOKEN -e USER=qa flows/
  • disableAnimations on Android — Maestro's workspace setting platform: android: disableAnimations: true is honoured, --disable-animations (MAESTRO_DISABLE_ANIMATIONS) turns it on for a run, and a flow's own config can turn it on or off for that flow. The three global animation scales are set to 0 and restored to their saved values when the run or flow ends. DeviceLab and UIAutomator2 drivers; other drivers warn and carry on.
  • Maestro's legacy action: spelling — - action: back (and hideKeyboard, scroll, clearKeychain, pasteText) parses to the same step as the modern form. It was rejected as an unknown step, so suites written with it failed to parse (30 of DuckDuckGo Android's 166 flows).
  • --app-file accepts an http(s) URL — point it at an artifact store (S3, GCS, a CI artifact) instead of a local path, and the binary is downloaded once and cached under ~/.maestro-runner/app-cache. A .zip is unpacked to the .app or .ipa inside, so a zipped simulator build works directly. --app-file-sha256 <hex> verifies the download and makes the cache content-addressed, so a changed artifact at the same URL is refetched. Presigned URLs are handled safely: the URL is only ever printed with its query string redacted, so the signature never reaches a log, report, or error message. Honours --insecure for a self-signed host.
    maestro-runner --app-file https://ci.example.com/builds/app.apk \
      --app-file-sha256 3b1f… test flows/
  • CommonJS require in runScript — a script can now pull in a helper module that lives beside the flow: const helpers = require('./helpers.js'), where the module sets module.exports. Relative paths resolve against the flow's directory, nested requires resolve relative to the requiring module (Node's algorithm), and an absolute path is read as given. Only CommonJS is supported — goja has no ES-module loader, so import is not available. This is a maestro-runner extension; Maestro's runScript has no module system.
    - runScript: |
        const { formatDate } = require('./lib/dates.js')
        output.today = formatDate(new Date())
  • wait step — a plain time delay, for the times a flow needs to pause for a fixed duration rather than for a condition. The duration is in milliseconds, like every other timing field. This is a maestro-runner extension; Maestro has no plain wait (only extendedWaitUntil and waitForAnimationToEnd), so a flow that uses it will not run on Maestro. It waits on the run's context, so Ctrl-C during a long wait stops the run promptly. Prefer a condition wait where one fits; reach for this only for a genuine fixed pause.
    - wait: 2000                 # pause 2 seconds
    - wait: { duration: 500, optional: true }
  • --insecure for runScript HTTP calls — http.get/post/put/delete in a runScript block can now reach a self-signed or otherwise untrusted TLS endpoint. Pass --insecure (or MAESTRO_INSECURE=1) to skip certificate verification for every request in the run, or opt in per request with http.get(url, { insecure: true }), which overrides the run-wide default either way. Verification stays on unless asked for.

Changed

  • DeviceLab is now the default Android driver. Running Android without --driver uses the on-device DeviceLab agent instead of UIAutomator2. On the TestHive suites, measured on a Pixel 4a, DeviceLab matched UIAutomator2's pass rate and ran about 1.5-2x faster per flow (auth, products, cart, and the system media picker). UIAutomator2 is unchanged and still one flag away: --driver uiautomator2.
  • DeviceLab is now the default iOS driver. Running iOS without --driver uses the DeviceLab iOS agent instead of WebDriverAgent, on simulators and real iPhones (a real iPhone still needs --team-id, as WDA did). On the simulator suites it passed everything WDA passed and more — TestHive 4/4 every run where WDA loses one to its alert race, RNTester 48/48, React Navigation 39/39 — and it runs about 30% faster per step. On a real iPhone SE (iOS 26.6.2) it passed 8 of the 9 TestHive flows; the ninth (search) failed the same way on the simulator, and is fixed below — the 9 now pass on the simulator. WDA is unchanged and one flag away: --driver wda. The web default (cdp) is unaffected.
  • DeviceLab Android settles like Maestro. After a tap, the next action waits for a 200ms quiet window on screen (it was 500ms); after steps that start a page load (openLink, pressing Enter) the window stays 500ms, and openLink first waits up to 3s for the screen to change. A check between a tap and the next action no longer cancels that settle, and with a WebView connected the action also waits for the page to finish loading. A required element is looked for 17s, as Maestro's lookup timeout, instead of 12s. launchApp's default all: allow grants only the permissions the app declares, read once per run, instead of all 23 known ones (about 0.7s per launch) (#189).
  • when: / while: checks get Maestro's time budget — 7s less the time since the last step that could change the app, never below 1s, instead of a fixed 1s. A check right after a quick step now still catches a dialog that is on its way; a present element still resolves at once. --condition-timeout sets a fixed budget only when given, and a per-condition timeout: still wins.
  • waitToSettleTimeoutMs on a driver with a native settle (DeviceLab) waits for the screen to stay unchanged for 500ms, up to the step's timeout, instead of comparing two hierarchy reads 200ms apart.
  • The WDA driver swipes, settles and taps like Maestro. Swipes are W3C touch actions with no hold before the move (the old hold made iOS take a scroll as a tap), scrolls run from the centre to 10% from the edge, the next action after a screen-moving step waits for two identical screenshots (up to 3s), and tapOn taps the element's on-screen centre, keeping WDA's element click as the fallback. Of nested matches with the same id, the innermost is tapped (a SwiftUI Toggle's switch, not its row). scrollUntilVisible takes the match that is on screen and uses the timeout as its budget unless maxScrolls is set. The client talks to 127.0.0.1 rather than localhost.
  • The previous DeviceLab iOS runner (now --driver devicelab-legacy) restarts its on-device runner if it dies mid-session, instead of failing every remaining step with "connection refused". A read-only command is retried on the relaunched runner; an action is not replayed (so a crash-inducing tap cannot loop), but the runner is back for the next step. Proven by killing the runner mid-suite: the run still completed green.
  • Bundled WebDriverAgent updated from 11.1.3 to 16.12.8. Brings upstream fixes for taps landing off-target in iPad compatibility mode, stale element snapshots on long-lived elements, a bounded accessibility request path so a frozen app cannot hang the server, and screenshots that no longer queue behind a stuck command. The runner now needs iOS 15 or newer (WDA raised its deployment target). The WDA build cache is now keyed by the bundled WDA version as well as the simulator or device configuration, so the first run after this upgrade (or after wda update) rebuilds the runner once instead of silently launching the previous version's build. The session no longer sends the shouldUseTestManagerForVisibilityDetection capability, which WDA removed in 12.0.
  • scrollUntilVisible stops as soon as the screen stops moving — two scrolls that change nothing, or a rubber-band bounce off the edge — instead of spending every scroll maxScrolls allows on a target that is not in the list. The failure message says so ("made no progress after N scrolls"). Android (uiautomator2, devicelab), iOS (wda, devicelab) and Appium; the browser driver keeps its fixed 10-scroll cap.
  • --typing-frequency now also throttles keyPress-mode typing on the uiautomator2 Android driver. The flag (default 30 keys/sec, override per flow with typingFrequency:) is applied by the runner to every driver that types character by character: the WDA and DeviceLab iOS drivers for all typing, and uiautomator2 for keyPress-mode inputText, which now adds a per-character pause (1000/frequency ms) so apps that drop fast key events — notably React Native fields — receive every keystroke. Lower it if a field still loses characters. Two drivers are unaffected by design: the default DeviceLab Android driver types through its on-device agent (a change there needs the agent, not the runner), and normal Android inputText sets the whole string at once so it never needed throttling.
  • The prebuilt DeviceLab agents (Android APKs and the iOS agent) are covered by a binaries license, drivers/LICENSE-BINARIES.md: free to use with maestro-runner for any purpose, redistributable unmodified as part of maestro-runner, not open source. The rest of the repository stays Apache 2.0.

Fixed

  • DeviceLab iOS: a tap on an id two siblings share goes to the control. TestHive's search icon and its text field both carry the id search-bar, the icon first; the tap hit the icon, the field never took focus, and inputText failed with NO_FOCUS on every device. Of matches that rank alike, a text field, button or other control now comes before an image or a label — the element WDA's "first displayed" rule lands on, and Maestro passes the flow too.
  • DeviceLab iOS: hideKeyboard no longer fails when the keyboard stays up. It tries Maestro's two dismiss swipes and moves on, as Maestro does; some screens do not dismiss the keyboard on a drag, and the flow's next steps decide.
  • DeviceLab iOS: eraseText between a tap and inputText keeps the tap point, so inputText can still re-tap the field when nothing has keyboard focus.
  • inputText after a tapOn types into the field the tap focused on DeviceLab Android. A tap that landed while a bottom sheet was still sliding in did nothing, the previous field kept focus, and the text went there. inputText now waits up to 1.5s for a focused field on the tapped element and taps a text field once more if it never takes focus; a tap that worked costs nothing. The DeviceLab iOS agent does the same. inputText keeps its set-the-text semantics; keyPress: true appends (#189).
  • takeScreenshot and assertScreenshot on DeviceLab Android capture full resolution, lossless. They used the agent's half-size JPEG, so a cropOn crop came back at half the size and every assertScreenshot against a baseline taken by Maestro or another driver failed on size. They now capture through adb exec-out screencap -p, falling back to the agent's image if adb fails.
  • A text selector on iOS never matches the accessibility identifier. WDA and Appium matched text against name, which XCUITest fills with the accessibility identifier (React Native's testID), so tapOn: Open tapped a button whose testID was open-scanner and whose label was "Scan code". Text now matches the label and value, as in Maestro; id: still matches the identifier (#178).
  • WDA tapOn reported success with nothing tapped. Text equal to a key name (Delete, Return, Enter, Tab, Space, Backspace) was sent as that key before any lookup; now the element is looked for first and the key is sent only when none is on screen and a keyboard is up. During a push both screens hold an element with the same id, and the lookup took the outgoing one; it now prefers the displayed match, re-looks up after a failed click and never taps off screen. An action right after a tap waits for the tapped element to come to rest, and an action after scrollUntilVisible waits for the list to stop, since iOS takes a tap on a moving list as "stop scrolling" (#179).
  • Android selector parity: a regex text selector also matches hint text; an anchored id (^auth\.login\.emailInput$) matches an id with a package prefix; ids match regardless of case; text containing a " matches on the agent path; an exact match that takes up no space (a collapsed 0px banner) does not count as found; text: "" matches elements with no text instead of failing with "no selector specified".
  • killApp on Android is a system process death, as in Maestro (am kill): the app keeps its saved state and restores it on the next launch. It ran am force-stop, which is stopApp. As in Maestro, it only kills an app in the background.
  • Executor fixes: ${...} is expanded afresh on every pass of a repeat, retry or runFlow, so a repeat typing ${output.d[output.i]} no longer types the first item every time; a runFlow when: condition is re-evaluated on every retry attempt (#176); runShell and pasteText work inside runFlow — runShell failed as unsupported, and pasteText pasted the device clipboard instead of what copyTextFrom saved (#174); only steps that can change the app restart the when: budget clock; a find's short probes count against its timeout, so a when: check no longer takes 10-12s against a 7s budget.
  • A swipe's non-numeric speed: (such as speed: fast) is ignored, as in Maestro, instead of failing validation for the whole flow.
  • A retype after dropped characters keeps what the field held. The retry cleared the field and typed only the step's text, so "abc" followed by a dropped "defgh" came back as "defgh". DeviceLab Android now also reads a field back from the device after typing, so dropped characters are detected and retyped there.
  • Appium driver parity with the native drivers: clipboard steps work on Appium 3; LANDSCAPE_LEFT, LANDSCAPE_RIGHT and UPSIDE_DOWN on Android; pressKey on Android takes the same keys as uiautomator2; a literal id on Android tries the exact id first, then the whole-id rule; swipe honours distance: for screen swipes; tapOn and dragAndDrop apply point: inside the matched element (#175); elements outside the viewport are not visible; launchApp applies permissions on every launch, with allow and deny; takeScreenshot and assertScreenshot honour cropOn; inputText on iOS no longer types into a field the flow left; every window is read on Android and deep trees on iOS; eraseText's delete fallback works on iOS; scroll and scrollUntilVisible go left and right; tap selectors with text plus id or state match every field; the focused element is looked up with GET.
  • Appium on iOS no longer dumps the page source after every miss. On a large tree over a cloud endpoint that call took ~20s and could exceed Appium's 60s limit, ending the session. After a native miss, one more predicate query covering what the page-source matcher accepts now proves absence; any other error falls back as before. On the iOS auth suite, page-source requests went from 804 to 8 (#173).
  • DeviceLab Android reliability: the bundled agent finds other apps' launch intents on Android 11+ and no longer launches with CLEAR_TASK, which wiped a task restored after process death; its settle compares fresh tree signatures (it had reported settled at once); an installed agent that matches the bundled APK is kept instead of being reinstalled on every start; a stale "Process crashed." line from a previous run no longer kills a healthy start; a WebView CDP handshake cannot hang past its connect timeout, and a socket that keeps failing is backed off up to a minute; the report's hierarchy covers every window.
  • The legacy DeviceLab iOS runner: eraseText honours its count and passes with nothing focused, as in Maestro; typing "" in replace mode clears the field; an app that is off screen has no visible elements, so assertNotVisible after stopApp passes; a snapshot follows the screen when the target app leaves it.
  • Flutter VM discovery is skipped for iOS simulator apps without Flutter.framework, saving about 2.4s on the first element step of every non-Flutter run.
  • Allure reports now include screenshot files in allure-results, with distinct attachment names for screenshots from different source paths. This also applies to nested steps (#186).
  • JUnit <failure> now carries the error in the element body, not just the message attribute. Maestro writes the failure reason in the body, and CI parsers written against Maestro read it there; ours put the error only in the attribute and left a step description in the body, so a Maestro-tuned parser saw no real error — which defeats being drop-in (caught reviewing the Expo EAS integration). The error now appears in both places; the body falls back to the step description only when no command-level error was captured.
  • WDA now reads a field back after typing and retypes dropped characters. XCUITest typing can silently lose a character when the app janks — notably digits in Expo / React Native number fields, so 4049876574 lands as 409876574. When inputText targets an element, the WDA driver now reads the value after typing and, if characters are missing, clears the field and types once more, the same recovery the Android drivers already do. Combined with the default 30 keys/sec throttle, this closes the iOS side of the long-standing fast-typing character loss.
  • WDA rejects missing or invalid element bounds and retries selector lookup, preventing stale elements from producing successful taps at (0, 0) (#168).
  • Deep React Native (New Architecture) screens were clipped out of the hierarchy. WDA snapshots the accessibility tree to a default depth of 50, so a Fabric view tree deeper than that dropped its leaf elements and a tapOn near the bottom reported "not found". The session now raises the snapshot depth to 100 on the WDA and legacy DeviceLab iOS drivers; override with MAESTRO_WDA_SNAPSHOT_MAX_DEPTH / DEVICELAB_SNAPSHOT_MAX_DEPTH (#171).
  • WDA on a physical iPhone no longer pins the build destination architecture. 1.1.27 pinned arch=arm64 to avoid an ambiguous arm64/arm64e resolution, but on some devices that produced a destination xcodebuild could not match and the runner never started. The physical-device destination is now platform=iOS,id=<udid> with no arch, letting Xcode pick; MAESTRO_WDA_DEST_ARCH still pins one if needed, and any is the explicit no-pin (#172).
  • A run's appId is taken from the first flow that declares one, not from the first flow alone. A suite could lead with a setup or shared-steps flow that carries no appId; the app-version lookup and default launchApp target then went empty even though later flows declared one. The runner now scans the flows for the first declared appId (or web url).
  • Report artifact capture: the view hierarchy is written with the extension that matches its content (JSON for the native iOS snapshot, XML elsewhere) instead of always .xml; an assertScreenshot diff image is written into the report's assets keyed by command index rather than beside the reference file outside the report; a failed step's after-screenshot no longer overwrites one an explicit takeScreenshot captured; and a failure inside runFlow/repeat/retry now saves its own screenshot and hierarchy.

Known issues

  • DuckDuckGo Android privacy_tests/8 fails its first attempt more often than on 1.1.27 with the DeviceLab driver, and passes on the flow's retry:. It fails where a tap on a WebView element (id: pay-button) does not take the app to the next page, so the privacy dashboard that follows shows no trackers. Being looked at for 1.1.29.

Contributors

@aralshukaili

  1. Reported the selector differences from Maestro on DeviceLab Android — substring text and id, out-of-range index (#188)
  2. Reported the missing settle after actions, inputText landing in the wrong field, and -e splitting values at commas (#189)

@nt-ben-leblond

  1. Reported and first patched plain text selectors matching by substring ahead of exact matches (#161)

@Cancercookie

  1. Asked for a per-device identifier in parallel runs, which became DEVICE_UDID (#187)

@ablause

  1. Reported WDA text selectors matching the accessibility identifier (#178)
  2. Reported WDA tapOn succeeding with nothing tapped (#179)

@yesiamdaniel

  1. Reported Appium tapOn ignoring an element-relative point: (#175)
  2. Reported inline retry not re-evaluating a runFlow condition, with a minimal repro (#176)

@rhys-thomas

  1. Reported runShell failing inside runFlow (#174)

@team-realtor-mobile

  1. Reported the Appium iOS page-source fallback exceeding Appium's 60s limit on large trees, with a proposed fix (#173)

@VictorGTdaCruz

  1. Reported the WDA physical-device build destination failure (#172)

@vlasov-pepperstone

  1. Reported deep React Native trees missing from the WDA hierarchy (#171)

@s4lmon

  1. Fixed WDA accepting missing or invalid element bounds (#168)

@sjchmiela

  1. Fixed missing Allure screenshot attachments (#186)

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.28

Manual install (from the assets below)

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

# 3. Run it from the extracted folder (it finds its drivers next to it),
#    or move the folder to ~/.maestro-runner and put its bin/ on your PATH:
./maestro-runner/bin/maestro-runner --version

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