Releases: devicelab-dev/maestro-runner
v1.1.1
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 legacysettings put global airplane_mode_onapproach
Fixed
- CDP
waitForPageReadycrash — replaced panickingMustWaitLoad()with error-handlingWaitLoad()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
- 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 | bashManual 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-runnerLinux:
chmod +x maestro-runner-linux-amd64 # or linux-arm64
sudo mv maestro-runner-linux-amd64 /usr/local/bin/maestro-runnerVerify Installation
maestro-runner --versionPlatform Support
- macOS Intel (amd64) - Signed & Notarized
- macOS Apple Silicon (arm64) - Signed & Notarized
- Linux amd64
- Linux arm64
Built by DeviceLab.dev
v1.1.0
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 findingChrome 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: validationResultNetwork 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 support —tapOnwith<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
.jsfiles 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
assertNotVisiblenow 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 functionwhen browser CDP encounters text nodes (#35, #36) - iOS WDA session lifecycle — improved driver reliability with better session creation, cleanup, and error recovery
--team-idno longer required for auto-detected simulators — when a booted simulator is auto-detected,--team-idis 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
SetWebViewForwarderin 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_ESCAPEfirst (keyboard-only, no navigation side-effects), falls back toKEYCODE_BACKif 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 whosetextContentincludes 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
assertVisiblepassing for off-screen text in browser (#39) - @satishs22 — reported
tapOntimeout issue on Android emulator (#25) - @chrisjin-swipe — reported
inputTextcharacter 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.0Documentation
Platform Support
- macOS Intel (amd64) — Signed & Notarized
- macOS Apple Silicon (arm64) — Signed & Notarized
- Linux amd64
- Linux arm64
Built by DeviceLab.dev
v1.0.9
What's New
-
Desktop browser testing — new
--platform webwith built-in CDP driver for Chrome/Chromium. Headless by default,--headedfor visible browser. Supports parallel browser executionmaestro-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 commands —
evalBrowserScript,setCookies,getCookies,saveAuthState,loadAuthState,openTab,switchTab,closeTab,mockNetwork,blockNetwork,setNetworkConditions,waitForRequest,clearNetworkMocks,uploadFile,waitForDownload,grantPermissions,resetPermissions,getConsoleLogs,clearConsoleLogs,assertNoJSErrors,runBrowserScript -
Browser selectors —
cssandxpathselectors for web elements, in addition totextandid- tapOn: css: "button.submit" - inputText: id: "username" text: "hello"
-
--no-app-installflag — skip app installation even if--app-fileis providedmaestro-runner --no-app-install --app-file app.apk test flow.yaml -
--no-driver-installflag — 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-idfor 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.9Documentation
v1.0.8
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 -
setAirplaneModeandtoggleAirplaneModefor iOS — automates the Settings app to toggle airplane mode on real devices- setAirplaneMode: enabled: true - setAirplaneMode: enabled - toggleAirplaneMode
-
maxTypingFrequencyfor iOS — configurable typing speed via--typing-frequencyflag (default: 30 keys/sec)maestro-runner --typing-frequency 15 test flow.yaml -
scrollUntilVisiblemaxScrolls 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"
-
repeatwithwhilecondition now loops correctly instead of executing only once- repeat: while: visible: "Delete" timeout: 2000 commands: - tapOn: "Delete"
Bug Fixes
runFlow: whenconditions with variable expressions (e.g.,${output.element.id}) were never expanded, causing conditions to always evaluate as false- iOS real device:
acceptAlertButtonSelectormatched "Don't Allow" instead of "Allow" — changed toBEGINSWITH[c] 'Allow'withOKfallback AllocatePortwas ignoring existing port allocationsrepeat-whilecondition check timeout reduced from 17s to 7s default- Android
setAirplaneMode/toggleAirplaneModefailed withSecurityExceptionon Android 7+ — now usescmd connectivity airplane-modeon Android 11+, withsettings putfallback for older versions - Implicit wait warning resolved by using Appium settings endpoint
assertVisibleoptional timeout and optimized tap element finding- WDA
launchAppoptimized: 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
- @gdealmeida1885 — Fixed variable expansion in
runFlowwhenconditions (#10) - @maggialejandro — Fixed
acceptAlertButtonSelectormatching (#24) - @7ammer — Reported
repeatwithwhilecondition bug (#23) - @wrench7 — Reported
setAirplaneModeparsing and permission issues (#27) - @AkashRajvanshi — Reported regex pattern support for ID selectors (#22)
- @jochen-testingbot — Added TestingBot cloud provider docs (#20)
v1.0.7
What's Changed
Added
- Appium driver:
newSessionoption forlaunchApp— creates a fresh Appium session, useful whenclearStatefails on real iOS devices (mobile: clearAppunsupported). On iOS real devices withnewSession: true,clearStateis 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:
clearStateno longer kills WDA connection — replacedgo-ios(installationproxy/zipconduitover usbmuxd) withxcrun devicectl(over Apple'sremoteddaemon), which doesn't interfere with USB port forwarding - Android:
scrollandscrollUntilVisibledirection was inverted —scroll downwas scrolling up because/appium/gestures/scrollalready uses scroll semantics, no inversion needed (#9) - Android:
launchAppfailed with "No apps can perform this action" on certain devices —resolve-activitywas called without-a android.intent.action.MAIN -c android.intent.category.LAUNCHERflags. New three-tier launch strategy: (1) UIAutomator2 servergetLaunchIntentForPackage()on-device, (2) shell fallback with proper flags +dumpsysparsing + API-level-awaream start, (3) monkey fallback (#15) - Android: server APK install now checks version and handles signing conflicts (uninstall + reinstall when version mismatches)
indexselector was ignored in simple (non-relative) selectors —tapOn: text: X, index: 1always tapped the first match. Now selectors with a non-zeroindexroute through page source parsing, which returns all matches and picks the Nth one-eenv variables were not expanding in flow configappId—appId: ${APP_ID}with-e APP_ID=com.myappsent the literal${APP_ID}to adb. Now expands usingExpandVariables()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
clearStatefailing on real iOS devices via Appium (#14) - @hyry2024 — reported
-eenv variables not expanding in flow configappId(#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
indexselector being ignored in simple selectors
Installation
curl -fsSL https://open.devicelab.dev/maestro-runner/install | bashPlatform Support
- ✅ macOS Intel (amd64) — Signed & Notarized
- ✅ macOS Apple Silicon (arm64) — Signed & Notarized
- ✅ Linux amd64
- ✅ Linux arm64
Built with ❤️ by DeviceLab.dev
v1.0.6
What's New
Fixed
- iOS WDA: off-screen elements no longer returned by
findElement—assertVisible,tapOn,scrollUntilVisible, and all element commands now correctly reject elements not visible in the viewport - iOS WDA:
scrollUntilVisibleno longer skips scrolling when the target element exists in the accessibility tree but is off-screen (#9) - iOS WDA:
scrollUntilVisibledirection matching is now case-insensitive (e.g.,direction: "DOWN"works) - iOS WDA:
waitForIdleTimeoutnow works on iOS via WDA quiescence when: platformcondition was ignored inrunFlowblocks (#8)
Contributors
Thanks to everyone who reported issues and suggested improvements!
- Reported
scrollUntilVisibleand element visibility issues on iOS (#9)
- Reported
when: platformcondition being ignored (#8)
Installation
Quick Install
curl -fsSL https://open.devicelab.dev/maestro-runner/install | bashVerify
maestro-runner --versionPlatform 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
What's New
Added
tapOn: pointnow 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: environmentfor passing environment variables to the app (#7) — see docs
Changed
- Extracted shared helpers from drivers into
pkg/coreto reduce duplication - Removed hardcoded 1080x1920 screen size fallback in UIAutomator2 scroll/swipe
Fixed
launchApp: argumentssilently 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!
- Reported
tapOn: pointnot supporting absolute pixel coordinates (#6) - Spotted unused AI flags (
--analyze,--api-url,--api-key)
- Reported
launchApp: argumentsnot working on real iOS devices (#7)
Installation
Quick Install
curl -fsSL https://open.devicelab.dev/maestro-runner/install | bashVerify
maestro-runner --versionPlatform 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
What's New
Added
keyPressoption 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
labelinstead ofcontent-descfor accessibility) - iOS Appium driver:
pressKeycommand support - iOS Appium driver:
tapOnandinputTextreliability improvements - iOS Appium driver: skip
--app-fileand--team-idpre-checks (not needed for Appium) - iOS Appium driver: skip
clearStateon real devices (mobile: clearApponly works on simulators) - iOS WDA driver: auto-alert handling on simulators (accept/dismiss permission dialogs)
takeScreenshotcommand now correctly saves PNG files- GitHub star link in HTML report
- All
errcheckviolations fixed with proper error logging
Contributors
Thanks to everyone who reported issues and suggested improvements!
- Suggested the
keyPressfeature for character-by-character input - Suggested the
--team-idpre-check for WDA driver - Reported the
takeScreenshotbug
- Reported iOS element finding issue —
labelinstead ofcontent-desc(#3) - Reported
pressKeynot working for iOS on Saucelabs (#4)
- Reported clearState and iOS permission dialog handling issues (#2)
- Reported the stale socket issue on force-stop (Ctrl+C)
Installation
Quick Install
curl -fsSL https://open.devicelab.dev/maestro-runner/install | bashVerify
maestro-runner --versionPlatform Support
- ✅ macOS Apple Silicon (arm64) — Signed & Notarized
- ✅ macOS Intel (amd64) — Signed & Notarized
- ✅ Linux amd64
- ✅ Linux arm64
Built with ❤️ by DeviceLab.dev
v1.0.3
What's New
iOS Permission Handling (#2)
- Simulator:
simctl privacywith three modes —allow(grant),deny(revoke),unset(hands off) - Real device: WDA
defaultAlertActionat session creation with customacceptAlertButtonSelectorfor 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 AllowiOS 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
waitForIdleTimeoutfrom 5000ms to 200ms
v1.0.2
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)
}