Repository navigation
Release 1.1.31
A fix release for 1.1.30, whose GitHub release was withdrawn. Everything listed under 1.1.30 further down ships in 1.1.31. On top of it: WDA answers the permission prompts a flow does not tap (a 1.1.30 regression), taps after a check wait for the screen to settle, WDA page-source lookups on real iPhones are much faster, and a few smaller fixes.
Changed
WDA: page-source lookups are much faster on real iPhones (#266). A lookup that falls back to the page source (a selector not on screen yet, an optional step, when: visible, relative selectors, index:, copyTextFrom) now asks for it without the visible and accessible attributes, which WebDriverAgent computes for every element with an accessibility call each and the matcher never reads: on a heavy screen a page source went from about 25 s to 2.4 s. The report and the hierarchy command still get every attribute.
WDA: --wait-for-idle-timeout above 0 no longer sends settings WebDriverAgent ignores (#267). The value never changed anything on WDA: WebDriverAgent reads shouldWaitForQuiescence only when a session starts, takes waitForIdleTimeout in seconds, and every launchApp set it back to 0. On WDA only 0 has an effect, turning off the settle before each action, and the flag's help now says so.
Fixed
WDA: a flow that taps one permission prompt's button still gets the other prompts answered (#264, #265). Since 1.1.30, a flow that taps a prompt's button itself (tapOn: "Ask App Not to Track") turned off WDA's automatic answer for the whole session, so a later prompt the flow never meant to answer (notifications) stayed up over the app and every later step failed behind it. A lookup that finds nothing now answers such a prompt as WDA would have (Allow…, or Don't Allow… with permissions: {all: deny}) and looks again. The prompts the flow taps are still left to it, and a flow with acceptAlert or dismissAlert is unchanged.
WDA: kAXErrorServerNotFound fails the step at once and says what it is (#268). On real iPhones, after a clearState reinstall, the app sometimes runs without its accessibility server, and every page source fails with this error. A lookup polled to its timeout, each poll a page source of 8 to 10 s, and failed with "not visible within 30s", which read like a slow screen. The condition does not clear within a flow, so the step now fails at the first such read with "the app's accessibility server is not running (kAXErrorServerNotFound): WDA can reach the device but not the app; a relaunch does not help, restarting the device does".
WDA: a tap after a check waits for the screen to settle (#269). The driver settles before an action when the step before it may have moved the screen, but any other step cleared that, so a check or a script between two taps made the second tap skip it. When a tap opened a sheet, the check passed while the sheet was still sliding up (WebDriverAgent reports its final frame), and the next tap on the sheet went out mid-slide and was lost. A step that neither acts nor moves the screen (an assert, a script, a condition) now leaves it as it was.
WDA: eraseText on a simulator no longer hides the keyboard for the rest of the run (#270). WebDriverAgent cleared a field with a hardware keyboard's Clear key. On a simulator iOS then took a hardware keyboard as attached and kept the software keyboard off screen whenever a field had focus, so anything an app shows with the keyboard never appeared. On a simulator the field is now cleared with typed backspaces, as Maestro does; a real iPhone keeps the Clear key.
DeviceLab iOS: launchApp with stopApp: false keeps the running app (#261). The launch applied the default permissions first, and iOS kills an app whose permissions change, so the app restarted and lost its state. With stopApp: false, permissions and clearKeychain are no longer applied (a log line says so when the flow gives them).
DeviceLab iOS: hideKeyboard no longer swipes on the keyboard while it is still sliding in. The iOS agent took the screen size from the keyboard's frame. Right after a field took focus, that frame still lay below the screen, so the dismiss swipes started at the centre of a screen twice as tall: on the keys, where they dismiss nothing. Since 1.1.30 fails the step when the keyboard stays up (#259), flows that passed in 1.1.29 failed there (React Native's RNTester text.yml on iOS 27, 4 of 6 runs). The agent now reads the screen size from the app, waits for the keyboard to stop moving, and never starts the swipes below the keyboard.
Contributors
- WDA answers the permission prompts a flow does not tap (#264, #265)
- WDA lookups read the page source without
visibleandaccessible(#266) - WDA settles before a tap that follows a check (#269)
- WDA
eraseTexton a simulator keeps the keyboard on screen (#270) - Reported
--wait-for-idle-timeoutdoing nothing on WDA (#267) andkAXErrorServerNotFound(#268)
- DeviceLab iOS
launchAppwithstopApp: falsekeeps the running app (#261)
Also in this release: 1.1.30 changes
1.1.30's GitHub release was withdrawn, so this is the first release with these changes since 1.1.29.
This release closes more gaps with Maestro: index: counts matches by their position on screen, a long press holds 3 s on every driver, a ${…} that throws fails its step, and DeviceLab iOS hideKeyboard fails when the keyboard stays up. WDA can see and tap system permission prompts, can drive a WebDriverAgent that is already running (--wda-url) or keep one between runs (--reuse-wda), and reads typed text back to retype dropped characters. New options: --pause-on-failure, --find-timeout and inputText retype: false. The DeviceLab agents on Android and iOS now listen on loopback only and answer only the run that started them. Contributors sent 23 pull requests and many precise bug reports, credited at the end.
⚠️ Behaviour changes
DeviceLab iOS: hideKeyboard fails when the keyboard is still up after its dismiss swipes, as in Maestro (#259). Since 1.1.28 the step passed with the keyboard still up, so the flow failed later, on a tap the keyboard covered or a wait for it to go, with nothing pointing at hideKeyboard. Maestro has failed the step since 2.2. The swipes close the keyboard over a scroll view that dismisses it on drag; on other screens (a React Native ScrollView without keyboardDismissMode) use pressKey: Enter on a field whose return key closes the keyboard, or tap a label. WDA's hideKeyboard presses return and is unchanged. Reported by @georgetarazi-swipejobs.
A ${…} whose JavaScript throws fails its step, even with optional: true, as in Maestro. The expression was left in the step as literal text, so inputText typed ${output.user.email}, a selector searched for it, a when: read it as false, and env: passed it on; the flow then failed somewhere else, or passed with the wrong input. The step now fails with the JavaScript error and does not run. This covers step fields, selectors, runFlow's when: and env:, and repeat's while: and times:. A name that was never declared still reads as undefined. (#250)
DeviceLab Android: inputText types short text into an empty field key by key, as Maestro does. Text of up to 8 ASCII characters goes in as key events, so apps that react to each keystroke see every one: a one-time-code box that moves to the next box, a PIN, an input mask. The field was set in one call, which such apps saw as a single change. Longer or non-ASCII text is still set in one call, and a field that already holds text is still typed into at the cursor. When the app moves focus while the text goes in, the first field is no longer read back and retyped.
A long press with no duration holds 3 s on every driver, as Maestro's does on Android, iOS and web. DeviceLab Android, UIAutomator2, Appium and DeviceLab iOS held 1 s, the old DeviceLab iOS driver 0.8 s and the browser driver 1 s (longPressOn, and tapOn or tapOnPoint with longPress). WDA already held 3 s. A flow that gives duration: is unchanged, except on DeviceLab iOS, which raised any duration under 1 s to 1 s: duration: 500 now holds 500 ms.
A run cancelled before its flows finish reports failed, not passed. Its flows were marked skipped, and a run of skipped flows counted as passed: a run cancelled before any flow started reported passed, with report.json left at running and those flows at pending, and one cancelled inside a flow had report.json say passed. Those flows stay skipped, a flow the run never reached now reads skipped with run cancelled in report.json too, and the run's status is failed unless every flow passed. report.json keeps its fields; only that value changes. This applies to runs driven through the executor with a cancellable context. Ctrl-C on maestro-runner test already exits with code 1, and still does.
index: counts matches by their position on screen, top to bottom then left to right, as Maestro does (DeviceLab Android, UIAutomator2, Appium, WDA, DeviceLab iOS). Clickable matches were put first before the index was applied, so below: "Connections", text: ".+", index: 0 on Android's Settings tapped a navigation-bar button (clickable, matched by its content-desc) instead of the summary line under "Connections", and left the app; copyTextFrom with that selector copied an empty string. Maestro puts clickable matches first only when there is no index, and with one sorts by position. Now so do these drivers, for plain and relative selectors, and a negative index counts from the bottom-right. A flow whose index relied on clickable matches coming first may now pick a different element. A selector without index: is unchanged. DeviceLab iOS counted its matches in the order of the accessibility tree, where a tab bar comes before the content above it; it now counts by position too. (#254)
tapOn with text: or id: and an index: taps that match on DeviceLab Android, and with an index, a match that contains another match is not counted, as in Maestro (DeviceLab Android, UIAutomator2, Appium, WDA). DeviceLab Android sent such a tap to its agent, which tapped the first match whatever the index, so index: 1 tapped the same element as index: 0 and an index past the last match passed. It now picks by position and fails an index with no match ("no element at index 5: 2 match(es)"). A container whose description matches the text of a label inside it, such as YouTube's "Home" tab around the "Home" label, used to count as a match of its own, so index: 1 picked that label instead of the next "Home" on screen. Maestro keeps only the innermost match there, and now so do these drivers when an index is given; without one, nothing changes. On a Pixel with 3-button navigation, tapOn: {text: "Home", index: 1} in YouTube taps the navigation bar's Home button, as in Maestro 2.10. Reported by @losey-kim. (#260)
DeviceLab Android: setOrientation: PORTRAIT and LANDSCAPE turn the screen. They passed without doing anything: the on-device agent answered the request and left the display as it was. Every orientation is now set over adb as Maestro does, with LANDSCAPE turning the same way as LANDSCAPE_LEFT, and the step waits for the display to turn. A flow that ran in portrait after one of these steps now runs in landscape. Reported in #256.
WDA: hideKeyboard drags the keyboard away instead of pressing return, as Maestro does. Return submitted the form or closed the screen the keyboard belonged to, so a hideKeyboard between two fields could send a half-filled form. The driver now makes Maestro's two short drags (up 3% of the screen in 50 ms, then left), which a scroll view turns into a dismissal, and stops as soon as the keyboard is gone. They start at the point nearest the centre of the screen, above the keyboard, that no control covers (8 pt clearance), inside a scroll view when the screen has one: Maestro starts them at the exact centre, where a checkbox or a React Native Pressable takes the drag as a tap. With no such point, or a keyboard that will not go (a screen with no scroll view), the keyboard stays up and the step passes, as in Maestro and DeviceLab iOS. A flow that relied on hideKeyboard to submit needs pressKey: Enter instead.
WDA: a flow that answers permission prompts itself keeps WDA from answering them first (#248). With acceptAlert, dismissAlert, or a tapOn naming "Allow…", "Don't Allow…" or "Ask App Not…" among a flow's own or inline steps, launchApp no longer turns on WDA's automatic answer for that flow, so any other prompt in it waits for a step too. Steps in a runFlow: file: are not looked at.
Added
maestro-runner hierarchyshows what Android toggles, sliders and panes announce (DeviceLab Android). The tree and--compactnow carry a node'sstate("ON", "96%", from Android 11) andpanetitle ("Status bar", from Android 9), and a field'serror. They are for reading only: no selector matchesstateorpane, as none does in Maestro.--pause-on-failurestops a run at the step that fails its flow, with the device and the app left as they are. Finding out why a step failed meant changing the flow and running it again from the start. Now the run waits at the failure and the terminal takes commands:h [text]prints the screen's elements (or those matching the text),ssaves a screenshot under<output>/paused/,x <step>runs a step on the device in the flow's context (its variables,output.*andcopiedText),rruns the failed step again, and Enter lets the failure stand and the run go on. A retry that passes replaces the failure and the flow carries on; one that fails pauses again. Steps run withxare probes: they are not added to the report or counted. It pauses only where a failure would fail the flow, not on an optional step; a failure inside arunFlow,repeatorretrypauses on that step, andrruns it again whole. On a local Android driver,maestro-runner --device <id> hierarchyalso works from another terminal while paused. The flag (orMAESTRO_PAUSE_ON_FAILURE) is ignored with a warning when stdin is not a terminal, so CI never waits, and with--parallelor several devices. Without it nothing changes, and with it the report has the same shape. (#246)inputTextwithretype: falseleaves a field that reads back short as it is (DeviceLab Android, UIAutomator2, Appium, WDA, DeviceLab iOS). After typing, these drivers read the field back and, when it holds less than was typed (a dropped keystroke), clear it and type again. A field that cuts what it is given (a stake field that drops decimals, a length limit) reads back short too, and typing into it again changes what the app holds. Withretype: falsethe step notes how many characters the field holds and moves on. The default is unchanged. On DeviceLab iOS, where the agent does the read-back, the field is then not read back at all, so there is no note. The browser driver never reads the field back, so the key changes nothing there. Maestro has noretypekey, so a flow that uses it won't parse in Maestro. (#247)--find-timeout <ms>(MAESTRO_FIND_TIMEOUT) sets how long a step waits for its element when it gives notimeout:of its own, on every driver. Unset, each driver keeps its default (17 s on most, as Maestro). Lower it, to 3000 say, and a wrong selector fails in 3 s instead of 17 s while you write a flow. A flow'scommandTimeoutstill wins for that flow, and a flow without one gets the flag's value back instead of the previous flow'scommandTimeout.when:conditions keep--condition-timeout, andoptional: truesteps keep their own 7 s budget on the drivers that have one.- Telemetry: an Appium run reports where its Appium server runs (
appium_vendor): a known cloud by name (saucelabs,browserstack,lambdatest,perfecto,kobiton,headspin,testingbot,digitalaiorawsfor Device Farm),localforlocalhostor any IP address, andotherfor any other server. Only the host is read, and it is never sent, nor are the rest of the URL, the port, credentials or capabilities. Other drivers send nothing new. The telemetry page lists every field, andMAESTRO_RUNNER_NO_TELEMETRY=1orDO_NOT_TRACK=1turn it off. --wda-url http://host:port(MAESTRO_WDA_URL) drives a WebDriverAgent that is already running for the--deviceinstead of building and starting one (--driver wda). The run waits up to 30 s for its/status, needs no--team-idsince it signs nothing, and at the end ends only its own session, within 5 s, leaving the WebDriverAgent running. It takes exactly one--device, which must still be connected to this machine. A URL on that device's own WDA port on this machine is not read as "device in use". Without Xcode on the host, this is how a run reaches a physical iPhone whose WebDriverAgent another tool started (go-ios'srunwdaon the machine the phone is plugged into, for one).--reuse-wda(MAESTRO_REUSE_WDA) leaves WebDriverAgent running between runs and reuses it (--driver wda, #213). A run with the flag leaves its WebDriverAgent running when it ends, the first one included, and ends only its own session. The next run with the flag on the same device drives that WebDriverAgent with a new session instead of starting one, when it is still the xcodebuild a run started for that device, on the device's port, from the same build (a hash of the cached test bundle), and/statussays ready; otherwise it is stopped and a new one started. Skipping the start-up cut a harness that runs one process per test case by 16–25%. Two runs never drive one WebDriverAgent: a second run with the flag on a device in use is refused. A run without the flag stops a WebDriverAgent left this way and starts its own, as before. On a simulator, a kept WebDriverAgent is stopped when the simulator shuts down.--wda-urltakes precedence.- WDA:
MAESTRO_WDA_HOSTED_FRAMES=1corrects the frames of a view another process draws over the app, off by default. On iOS 27 XCUITest reports the elements of such a view (Apple Health's authorization sheet, for one) in the view's own coordinates, so they read as much as 48 points too high and every tap on them missed, by page source, by WDA rect and by XCUITest's own click. With the variable set, page-source frames are moved onto the screen where such a space starts, and every lookup reads the page source, so text andid:selectors,scrollUntilVisibleandcopyTextFromall get the corrected frame (a page source per lookup, with no fallback to WDA's own queries); nothing inside a web view, whose content already reports screen coordinates, is moved. A scroll view scrolled by exactly its own offset can look the same, which is why it is off by default;0andfalseleave it off.--driver devicelabhandles these sheets without the variable. This is the WDA counterpart of the DeviceLab iOS fix for #191 in 1.1.29. - DeviceLab iOS answers system permission prompts the way WDA does. With
permissionsresolving to allow (nopermissions:at all, orall: allow), a SpringBoard permission prompt is answered by tapping a button whose label starts with "Allow" ("Allow", "Allow While Using App", "Allow Once", "Allow Full Access", and App Tracking Transparency's "Allow"); withall: deny, "Don't Allow" (either apostrophe) or "Ask App Not to Track". A mixed list answers nothing. The driver looks for a prompt before the steps of the first 3 s afterlaunchApp(and for 3 s after each prompt it answers), when an element has been missing for a second (then at most once a second), and whenscrollUntilVisiblestops moving; a found element costs no extra call. Only SpringBoard's alerts are read, so the app's own alerts, and a screen the app shows before the system prompt, are left to the flow, as Maestro does. A prompt answered "OK" (Face ID on a device) or "Continue" (contacts, iOS 18 and later) is not accepted for you; useacceptAlertortapOnthere. A step aimed at the prompt itself is left to answer it:acceptAlert,dismissAlert, or a step whosetext/idmatches one of the prompt's buttons or its title (tapOn: "Allow",tapOn: "Ask App Not to Track", anassertVisibleon the question), so flows written for Maestro that tap "Allow" themselves keep passing.acceptAlertanddismissAlertwork as before, andDEVICELAB_IOS_AUTO_ALERTS=0(orfalse) turns the automatic answers off. - DeviceLab Android:
scrollUntilVisiblewithfrom:scrolls inside that element, as on UIAutomator2. The step failed with "not supported on this driver yet". The swipe now starts and ends inside thefrom:element's bounds, kept an eighth of its height off its top and bottom edges, so a carousel or nested list scrolls instead of the screen behind it. Afrom:element that isn't found fails the step with "Scroll container not found". - DeviceLab Android warns when a tap's point lies under another window. A
tapOn,doubleTapOnorlongPressOnon an element found through the page source (a selector withpoint:,index:, a relative selector, a regexid:, and the like) goes to whatever window is on top at that point, so a keyboard, dialog or overlay there takes it instead of the element. The log now names that window (tap on … the point is under keyboard window …); the tap still goes out and the step is unchanged. Taps by text orid:already refuse a covered point or tap the part left showing.MAESTRO_DISABLE_HIT_TEST=1turns it off. - A
runScriptfile's relativerequire()resolves against the script's own folder first, then against the flow's folder as before, so a shared helper script can loadrequire("../lib/env.js")the same way from flows at any depth. A path that resolves today from the flow's folder still does, unless a file with the same relative path sits next to the script. Inline scripts resolve from the flow's folder only, a module's ownrequire()stays relative to that module, and an absolute path is read as given. The same relative path required from two folders now loads two modules instead of the first one from the cache (#253). - DeviceLab iOS:
setAirplaneModeandtoggleAirplaneModework on a real iPhone. They failed on every target with "airplane mode is not available on the iOS simulator", a real iPhone included. As on WDA, the driver opens Settings, reads the Airplane Mode switch and taps it when it is not in the state asked for (toggleAirplaneModeflips it); "already enabled/disabled" taps nothing. Unlike WDA, it reads the switch again after the tap and fails the step if the state did not change, and it brings the app that was in front back to the front without relaunching it, so a flow needs nostopApp/launchAppafter the toggle. Settings must open on its main page, as with WDA. On a simulator, which has no airplane mode, both steps are skipped, as on WDA. Not yet tested on a physical iPhone. (#252) - DeviceLab iOS on a simulator warns when the simulator cancelled a tap that XCTest reported as delivered. XCTest plays every gesture through a new virtual touchscreen, and when the touch arrives before the simulator has registered that touchscreen, the app gets the touch cancelled instead of a tap while the step passes; the next step then fails on an unchanged screen, most often after the first tap of a session. The driver reads the simulator's
backboarddlog for the two lines that show it (unknown senderIDanddigitizer did disappearwith an open contact, for the same sender) and logs a warning naming the sender.DL_IOS_LOST_TOUCH=retryalso sends such a tap or long press once more, since the log shows the app never got it (a double tap or a swipe is not resent);DL_IOS_LOST_TOUCH=offturns the log reading off. The default only warns and adds no time to a step. MAESTRO_RUNNER_NO_UPDATE_CHECK=1turns the update check off, and on CI the update notice is no longer printed. An npm install's update notice now saysnpm i -g maestro-runner@latestinstead of the install script'scurlcommand.maestro-runner skills installinstalls a skill for AI coding agents. It writesSKILL.mdto~/.claude/skills/maestro-runner/, telling Claude Code and other agents that read skills how to write Maestro flows, run them, inspect the screen withhierarchyand read the reports.--dirwrites it elsewhere (.claude/skills/maestro-runnerin a repository) and--printprints it. The file carries the binary's version: installing again replaces it only when the version differs, or with--force, and aSKILL.mdthat maestro-runner did not write is left alone unless--forceis given.- Contributors:
make speedgatecatches a command type that got slower. It reads the reports of repeated runs of one suite on one device and driver, takes each command type's median over its passed steps, and fails when one is more than 20% and 50 ms slower than the committed baseline (benchmarks/speed-baseline.json);WRITE=1writes the baseline.report.jsonis unchanged. See DEVELOPER.md. - Contributors:
make hierparity DEVICE=<serial>checks that the DeviceLab Android agent reads every labelled elementuiautomator dumpreads, in the same place. Recall and precision over the screen in front, with what is missing listed; see DEVELOPER.md. - Library users: the DeviceLab Android agent reports windows, the app in front and the keyboard, and can wait for a launch, behind new opt-in calls (
pkg/maestro). For a host that drives the agent directly, such as an AI agent working step by step.SnapshotWithadds each window's type, layer, focus, title and package, the display size, the app in front and the keyboard's top edge;GetForegroundreads the app in front, the lock screen and the screen state without a shell call;LaunchAppWaitfails withscreen_locked, or withnot_foregroundnaming what is in front, instead of returning while a dialog or the lock screen covers the app;WaitForSettleWithandTreeHashWithcover all of the app's windows (popups, dialogs) and can ignore text changes such as a clock;Observesettles, reads the tree, the app in front and the keyboard, and optionally a screenshot, in one call.Session.createlists them incapabilities. Flows use none of them and run as before: on a Pixel 4a the TestHive suite passed with the same times on DeviceLab, UIAutomator2 and Appium. These packages make no API promise.
Changed
- DeviceLab iOS: only the run that started the agent can drive it. The agent listened on every network interface with no check, so anyone on the same network could tap, type and take screenshots through it. It now listens on the loopback address only and answers only requests that carry the random token the run started it with; a later run re-attaches using the token saved with the agent's state (readable by the user only). A run checks which agent build is installed on the simulator instead of trusting its own record, and does not re-attach to an agent that answers without the token: an older build another maestro-runner install put on the simulator is replaced by this one.
DEVICELAB_IOS_AGENT_BIND=anymakes the agent on a real iPhone listen on every interface, should the USB forward fail to reach the loopback address. - DeviceLab Android: only the run that started the agent can drive it. The agent listened on port 6791 on every network interface with no check, so another device on the same Wi-Fi, or another app on the phone, could tap, type and read the screen through it. It now listens on the phone's loopback address, where
adb forwardconnects, and refuses a client that does not send the random token the run started it with (am instrument -e token, which only adb can start, so no app can read it). The health check and a restart mid-run use the new token. An older agent ignores the token and works as before; an agent started without one (by an older runner) accepts any local client.DEVICELAB_ANDROID_AGENT_BIND=anymakes it listen on every interface again, for a setup that reaches the phone over its network instead of adb. - Bundled WebDriverAgent updated from 16.12.8 to 16.17.5, now taken from
packages/webdriveragentofappium/appium-ios, where WebDriverAgent moved at 16.16.0. Brings upstream fixes for a coordinate drag that could hang WDA on an infinite point, a deleted session that left the app it launched running, an app brought back from the background before SpringBoard settled, HTTP bytes dropped when the client closed the connection, and a faster accessibility-id lookup and page source. Nothing maestro-runner relies on changed behaviour; the first run after this update rebuilds the WDA runner once, as the build cache is keyed by the bundled version.
Fixed
- DeviceLab iOS: a run on a simulator ends about 2.5 s sooner and leaves no
log streambehind. The watch for lost touches reads the simulator's log throughsimctl spawn … log stream. Stopping it ended onlysimctl; thelogprocess inside the simulator kept running and held the pipe, so every run waited 2 s for it and left one morelogprocess on the simulator. It is now ended by name. The Reduce Motion and AutoFill settings are also read, written and put back side by side, about 0.7 s less per run. Suites that start the runner once per flow gain the most: about 3 s a flow. - WDA: of two elements with the same id side by side, a tap goes to the control (#259). A search field and its magnifier icon can share an id; the icon came first in tree order and took the tap, so no field got focus and the next
inputTextfailed. A field, button or other control is now taken over an image or label, as the DeviceLab iOS driver already did. A match that holds another is still skipped for the one inside it. - WDA: flows can see and tap system permission prompts (#248). A prompt belongs to SpringBoard and WDA's page source holds only the app, so
visible:,assertVisibleandextendedWaitUntilnever saw it andtapOn: "Allow"never found its button. When a text selector finds nothing in the app, these steps now also read an open system alert: its text and buttons for a visibility check, its buttons for a plaintapOn. - WDA:
acceptAlert,dismissAlertand a tap on a prompt's button pass only once the prompt has closed. A tap sent while a prompt was still animating in was answered 200 and did nothing, soacceptAlertpassed with the prompt still up. The tap is now sent again, up to 4 times, and the step fails if the prompt stays open. - DeviceLab iOS:
inputTextwith a newline or a tab is no longer typed a second time. The agent reads the focused field back after typing and, when it looks short, deletes what arrived and types the text again. A field never shows a newline or a tab, so text such as"query\n"looked short whenever a field still had focus after return, and the retype pressed return again: a search ran twice or a form was submitted twice, and when return had moved focus on, the delete could reach the next field. That text is no longer read back, as on WDA. Other text is still read back and retyped once when keys went missing. - DeviceLab iOS: the content of a web view the app keeps hidden is no longer on screen to flows (#211). WebKit keeps reporting a hidden web view's elements with on-screen frames, so a consent form an ad SDK preloads out of sight (Google's UMP form in a Flutter app) made
when: visibletrue andtapOn: "Continue"hit the app's own button under it. Each web view in a capture is now asked whether it is visible, as WDA'svisibledoes, and a hidden one is sent without its content: at most 6 requests of 0.3 s per capture, and an unanswered one keeps the content as before. On an iOS 27 simulator with a hidden web view holding "Continue", the app's own "Continue" is tapped; TestHive's login flows take as long as before. Reported by @JohnAtTimeless. - DeviceLab iOS reads UI that another process draws into the app, such as the photo picker and the device-passcode sheet. Such a sheet can reach a capture as one element with nothing inside, so nothing in it could be found or tapped. Where an element is drawn by another process, the capture now reads it from that process and adds its content: at most 4 such elements and 2 s per capture.
DL_AGENT_REMOTE_CONTENT=0turns it off. On an iOS 27 simulator the photo picker, the share sheet and the Face ID sheet were already readable, so this was not seen to change a result there. - DeviceLab Android: an app behind a launch screen Android never removed is read again. A launch can leave its starting window over the app (a React Native debug app on a Pixel 4a), and Android then lists only the status bar as a window, so the read that relative,
index:and page-source lookups andhierarchyuse had none of the app: 0 of 19 labelled elements on that screen, whereuiautomator dumphad all 19. The agent now also reads the window in front whenever the window list leaves it out (19 of 19). - DeviceLab Android: a text selector finds a field by its validation error, as Maestro does and UIAutomator2 already did. A field's error (Android's
setError, Composeerror()semantics: "Invalid email") was not read by the agent, soassertVisible: "Invalid email"failed when the message lived only there. The agent now writes it aserrorin the hierarchy, and text selectors match it after text, description and hint; a field with no error never matches, even.*. - DeviceLab Android: percentage points, swipes and scrolls are worked out from the whole display (
wm size), as Maestro's are (getRealMetrics). They used the size the agent read from the window in front when the session started, which can leave out the system bars or be a smaller window, so50%could land higher than in Maestro. On a Pixel 4a both sizes are 1080×2340 today, so taps there land where they did; the size no longer depends on what was on screen when the run began. - WDA on a real iPhone:
launchAppright afterclearState's reinstall waits for iOS to register the app. The launch was sent before iOS knew the reinstalled app and failed withFBSOpenApplicationErrorDomain Code=4("unknown to FrontBoard"). The launch, and a new session that launches the app, are now sent again every 500 ms for up to 10 s while they fail that way, with one log line when a retry was needed. A flow whose app is not installed when it starts now waits those 10 s beforelaunchAppinstalls it. Contributed by @luka-vladika-happening-xyz. (#251, #257) - After
setOrientation, percentage points, coordinate taps and the on-screen check use the turned screen size (DeviceLab Android, UIAutomator2, Appium, #256). The size read when the session started was kept, so in landscapetapOn: point: "50%, 40%"was worked out from the portrait size and landed elsewhere, DeviceLab refused a point right of the portrait width as off screen, and on AppiumassertVisiblefound an element there "off screen". The size now follows the rotation the step leaves, andsetOrientationon UIAutomator2 and Appium waits for the screen to turn before the next step. Reported by @wanglinyu12345-lab. - iOS simulators in another language: system permission prompts are answered again (WDA, DeviceLab iOS). The automatic answer matched English labels only ("Allow…", "Don't Allow", "Ask App Not to Track"), so on a simulator set to German the notification prompt's "Erlauben" / "Nicht erlauben" was left open and the flow failed behind it. The driver now reads iOS's own wording of those buttons from the simulator runtime's string tables (notifications, privacy access including App Tracking Transparency, and location), for each of the simulator's preferred languages, once per run and only when needed, and matches a localized button as a whole label with the same meaning as the English one it translates. A translation that would mean both allow and deny counts as neither. The rule is otherwise unchanged: only an allow-type (or, with
all: deny, deny-type) button, never an "OK" or "Continue", and on DeviceLab iOS only SpringBoard's alerts. Real iPhones still match English labels only. engine: agentscrolls go the right way again (DeviceLab Android). The agent scrolled the opposite way to the direction asked, soscrollUntilVisibleandscrollwithengine: agentgave up after two scrolls with no progress (Settings on a Pixel 4a: 0/1, now 1/1 both ways). Fixed in May for #81, the fix was lost from the agent source. The default (adb) engine was not affected.- DeviceLab Android: a swipe inside a nested pager pages the inner pager, not the outer one. The swipe moved 1 ms after touching down, and react-native-pager-view decides which pager owns a gesture one frame after the touch, so the outer pager took it. The swipe now rests 50 ms before it moves, about 50 ms more per swipe; how far a swipe scrolls is unchanged.
- DeviceLab Android: an action on a screen that never stops animating waits 2 s for it to settle once, not before every step. After a tap or swipe the next action waits up to 2 s for the screen to stop changing, and a screen where something keeps moving (an auto-scrolling carousel, a marquee) used all 2 s every time. Once a wait has run out, the next one on the same screen waits at most 500 ms; new text or a new screen gets the full 2 s again, as does a screen that has settled. The screen is told apart by its text and content descriptions, which such movement leaves alone; a ticking timer changes text, so it still gets the full 2 s. Waits after
openLinkand Enter are unchanged.MAESTRO_DEVICELAB_STUCK_SETTLE_MSsets the shorter wait;2000or more turns it off. - DeviceLab Android: a view removed while the screen is read no longer fails the read. The agent read the hierarchy node by node, and a node whose view went away mid-read threw, failing the whole read; the step polled again or failed. That node is now left out, with what was under it, and the rest of the screen is returned.
- UIAutomator2 and Appium on Android no longer tap an element with no area. A 0x0 element (a collapsed view, or a Compose node not yet laid out) was tapped at the centre of its bounds, which is its corner, so the tap landed on whatever was drawn there and the step passed. Such an element is now passed over and the lookup keeps polling: one laid out within the timeout is tapped at its centre, and one that never gets a size fails with "element has no area". Maestro never finds such an element, and the page-source lookups of both drivers and DeviceLab Android already passed it over. Covers
tapOn,doubleTapOnandlongPressOn, and on UIAutomator2 also adragAndDropendpoint given by selector. - WDA:
hideKeyboarddoes nothing when no keyboard is on screen. It pressed return regardless, and a focused field with no keyboard showing (a simulator with the hardware keyboard connected) took it as submit, so the form was sent. Maestro'shideKeyboarddoes nothing then, and DeviceLab iOS already did not. - WDA:
eraseTextwith a count on an empty field no longer types part of its placeholder into it. WDA reads an empty text field's placeholder as its text, and erasing fewer characters than that clears the field and types back what should remain:eraseText: 5on an empty "Email address" field left "Email a" in it. A text that is the field's placeholder now counts as empty, and the field is cleared. - DeviceLab iOS:
dismissAlertnever taps an "Allow" button, andacceptAlertanddismissAlertpick by iOS's layout when no label is known. With no known decline label,dismissAlerttapped the first button andacceptAlertthe last, which on a stacked alert of three or more buttons is the wrong end: a location prompt in another language was answered "Allow Once" bydismissAlert. The driver now reads the alert and names the button: a known label first ("Allow…", "OK", "Continue", "Open", "Yes" to accept; "Don't Allow", "Ask App Not to Track", "Cancel", "Not Now", "Close", "No", "Dismiss" to dismiss); else the left button of two for a dismiss and the right for an accept, and the bottom of a stack for a dismiss and the top for an accept; else the one button that does not read as the other answer. An alert where no button can be named fails the step and taps nothing, instead of the step passing as "no alert" after its timeout; a one-button alert is answered with its button, as before. - WDA:
acceptAlertanddismissAlerttap the alert's accept or decline button whateverpermissionsresolve to. The button selectors were set only for the matching auto-answer (all: alloworall: deny), and the dismiss one never matched because of the apostrophe in "Don't Allow". Without a match WDA picks by position, the first button for a dismiss, so on a stacked alert (Delete,Archive,Cancel, or a prompt in another language)dismissAlerttapped the top action. Accept now names "Allow…", "OK", "Continue", "Open" and "Yes"; dismiss names "Don't Allow" (either apostrophe), "Ask App Not to Track", "Cancel", "Not Now", "Close", "No" and "Dismiss". An alert with none of them still falls back to WDA's positional pick. - DeviceLab iOS:
launchAppwithstopApp: falsebrings a running app to the front instead of restarting it, as Maestro does. The simulator launch always passed--terminate-running-process, and on a real iPhone XCTest's launch restarts the app, so the app started fresh and a flow checking what survives a relaunch tested nothing. A running app now comes to the front as it was (its launch arguments do not apply, as in Maestro); an app that is not running is launched as before. The old DeviceLab iOS driver (--driver devicelab-legacy) had the same gap on simulators and is fixed too. - iOS 27:
hideKeyboardis no longer left with nowhere to start its drag by the floating bar container (WDA, DeviceLab iOS). iOS 27 reports the container as a screen-sized toolbar, and every point under it counted as a control. - iOS: a tap no longer presses a key when the software keyboard covers its target (WDA, DeviceLab, DeviceLab legacy). XCUITest reports an element under the keyboard as on screen, so a
tapOnon a button below a focused field touched the key above it and the step passed; Maestro does the same. After a step that types, or a tap on a text field, a tap first checks whether the keyboard covers the point it aims at. If it does, the driver hides the keyboard ashideKeyboarddoes and finds the element again; when the keyboard will not go it taps the part of the element above the keyboard, and when none of it shows the step fails with "… is under the keyboard" instead of pressing a key. A key, or the keyboard's own return button, is tapped where it is. This coverstapOn,doubleTapOnandlongPressOnaimed at an element's centre; apoint:inside the element is left as given. A tap before anything has been typed asks nothing extra, so a keyboard the app raises by itself (a field focused on screen entry) is not caught until the flow types. maestro-runner wda updatefinds WebDriverAgent releases again. WebDriverAgent moved to theappium/appium-iosrepository at 16.16.0, and the oldappium/WebDriverAgentrepository stopped at 16.15.3, sowda updateandwda update --checkreported 16.15.3 as the latest version andwda update 16.16.0or later failed with HTTP 404. The latest version is now the highestappium-webdriveragent@X.Y.Ztag ofappium/appium-ios(the old repository's latest release if that lookup fails), and a version from 16.16.0 is downloaded fromappium/appium-ios, installing only itspackages/webdriveragentfolder; earlier versions still come fromappium/WebDriverAgent. The installed WebDriverAgent is now replaced only after the download has been extracted and holdsWebDriverAgent.xcodeproj, so a failed update leaves it in place.assertScreenshotagainst a baseline of another size now says why it failed. A whole-screen baseline recorded on another device, simulator or display scale failed with onlyscreenshot size mismatch: expected WxH, actual WxH; the error now names that cause and the fix (re-record on this device with--update-screenshots, or keep one baseline per device). The comparison is unchanged: a size mismatch still fails, as in Maestro.- WDA: when
launchAppwithstopApp: falsebrings a backgrounded app back, the next tap reaches the app. Right after the switch back the app can already be idle while SpringBoard is still moving it to the front, and a tap sent then was reported as done but never arrived; anassertVisiblein between passed on the app's tree and skipped the settle before the tap. When the app was in the background,launchAppnow waits for the screen to stop changing, up to 2 s. An app that was not running, or already in front, waits for nothing, and alaunchAppthat stops the app first (the default) is unchanged. - Old DeviceLab iOS driver (
--driver devicelab-legacy): bringing a backgrounded app back waits, up to 2 s, for SpringBoard to go idle before the next step, for the same lost tap. Activating an app that was not in the background waits for nothing. runScripthonourswhen:, as Maestro's does. The condition was parsed and ignored, so a script guarded bywhen: true: ${MAESTRO_CI == 'true'}ran on every run. An unmet condition now skips the script (Skipped (when condition not met)), at the top level and insiderunFlow,repeatandretry, and a skipped script leaves the interaction clock alone, as a skippedrunFlowdoes. (#238)- Appium on iOS:
selected: truematches a selected tab or segment. XCUITest's page source has noselectedattribute for them; it says so intraits="Selected, Button", which is now read, so a check on a tab bar no longer waits out its timeout. (#245) - Relative selectors honour
focused,enabled,selectedandcheckedwhen they have no text or id (DeviceLab Android, UIAutomator2, Appium, WDA).childOf: {id: field}, focused: truedropped the state and took the deepest element inside the anchor, so on a Compose text field (the test tag on a wrapper, the focusedEditTextinside)copyTextFromcopied an empty child view instead of the typed text. The state now narrows the candidates as text and id do, as in Maestro. (#240) copyTextFromno longer writes the device clipboard on Android (DeviceLab Android, UIAutomator2). Writing it could put the system's clipboard UI over the app, and the nexttapOnlanded on that instead. The copied text is kept by the runner for${maestro.copiedText}andpasteText, as Maestro keeps it and as the Appium and iOS drivers already did.setClipboardstill writes the device clipboard. (#239)- WDA:
selected:matches a selected tab or segment when the element is found through the page source. WDA's page source marks selection only intraits="Selected, …", which was not read, so there every element counted as unselected:selected: truenever matched andselected: falsealso matched the selected tab. This hit lookups that read the page source: relative selectors (below:,childOf:and the like),index:above 0,width:/height:,checked:,id:withtext:, regex text in a tap,MAESTRO_WDA_HOSTED_FRAMES=1, and the fallback when WDA's own query found nothing. The same fix for Appium on iOS shipped in #245. - WDA and the old DeviceLab iOS driver start on simulators again with Xcode before 26 (Xcode 16 and older). Since 1.1.16 the simulator
-destinationcarriedarch=, which xcodebuild before Xcode 26 rejects, so every simulator run on such a host failed to start; build images that pin 1.1.15 for Xcode 16, as Expo EAS does, can use current releases. Thearch=pin is now added only on Xcode 26 and later, whose resolver lists a simulator under both arm64 and x86_64, and when the Xcode version cannot be read. - A
launchAppinsideretry,repeator arunFlowelsebranch now sets up the iOS session's permission alerts. Before the session starts, the runner looks for the flow's firstlaunchApp, and it looked only insiderunFlow, so a flow whose onlylaunchAppsat in aretrygot no alert handling. A flow file named inside a subflow file is also found next to that file, as it is when the flow runs. On WDA this turns on the session's alert monitor for those flows, as a top-levellaunchAppdoes. - WDA no longer taps the app's own alerts. With
permissionsresolving to allow (the defaultall: allowincluded), WDA's alert monitor answered the first alert of any app, the app under test too: it tapped the "OK" of an in-app alert such as "Invalid Credentials" about 2 s after it appeared, so a flow asserting that alert and then tapping "OK" failed about half the time. It also tapped an alert's last button when no button matched. The monitor now taps only a button whose label starts with "Allow" (or, forall: deny, "Don't Allow…" and "Ask App Not to Track"), and leaves every other alert to the flow, as Maestro does. A permission prompt answered "OK" (Face ID on a device) or "Continue" (contacts, iOS 18 and later) is no longer accepted for you; useacceptAlertortapOnthere. - WDA: a long press with no duration holds 3 s, as Maestro's does on iOS (
longPressOn, andtapOnortapOnPointwithlongPress). It held 1 s. - WDA:
copyTextFromno longer copies an empty string when reading the text fails. It reads the text again, then takes it from the page source when an element there has the bounds WDA reported, and fails the step otherwise. A page-source match copies the value, else the placeholder, else the label, as Maestro does; it copied only the label. That text is also whatreport.jsonand the HTML report show for an element found through the page source, so a text field there now shows its value instead of its label. --wait-for-idle-timeout 0turns off the wait for the screen to settle before an action, on WDA and DeviceLab iOS (#212). Since 1.1.28 an action after a tap, swipe or typing waits until the screen stops changing, up to 3 s, as Maestro's iOS driver does. On a screen that never stops animating (a looping Rive or Lottie view) every action paid the full 3 s, and0, documented as "disabled", only turned off XCTest's own idle wait. Any other value keeps the settle, as before.- DeviceLab iOS puts the simulator's Reduce Motion and password AutoFill settings back when the run ends (#214). The driver turns Reduce Motion on and AutoFill off for the run, and left them that way. A setting that was already at the run's value is not touched.
DEVICELAB_IOS_SIM_PREFS=0still skips both. - The warning for retyped text no longer prints the text. When characters went missing while typing, the warning showed the field's value and the typed text, which could be a password. It now gives character counts.
- A secret typed with
inputTextshows as***in step names, step messages and errors. When the text typed holds the value of a variable named like a secret (a word of the name ispassword,passwd,pass,secret,token,apikey,key,pin,otporcredential, any case, soAPI_KEY,userPasswordandPINcount andMONKEYdoes not), that value is replaced with***. A step name filled in a password set in the flow'senv:, and a failedinputTextcould carry the typed text in its error, intoreport.json, the HTML, JUnit and Allure reports, the console andmaestro-runner.log. The same applies topasteTextandsetClipboard, and to steps insiderunFlow,repeatandretry. The text typed is unchanged, values shorter than 3 characters are not masked, and other text in reports is unchanged. Variables from-e,--env-fileand the shell already stayed${NAME}in step names. scrollUntilVisiblescrolls in a row that runs past its scroll container's edge (DeviceLab Android, UIAutomator2, Appium on Android). The hierarchy doesn't always clip a child to its scroll container: a bottom sheet's last row was reported running past the sheet's list, under the gesture bar. Measured against the screen it was fully visible, so the scroll stopped and the tap hit the gesture bar. A match past its container's leading edge now keeps the scroll going; one the list can't move is accepted after a single scroll, as with #164.launchApp: clearStateno longer loses its launch to Android's own cleanup (DeviceLab Android, UIAutomator2). Clearing an app removes its task, and removing a task kills every process the app has. When the app's screens were still showing, that kill came about a second later (Destroy timeout of remove-taskin logcat); when another app's activity, such as the permission dialog, sat in its task, it came after the clear had returned. Either way a launch right after the clear could be killed, and the flow waited on a blank screen. The app is now stopped, and its screens waited out, before it is cleared, and its task waited out after, bylaunchApp: clearStateand byclearState. This adds a fewdumpsyscalls to each clear, usually a few hundred milliseconds; an app that won't let go of its screens or task costs up to 5 s and a warning. Appium isn't covered: it clears throughmobile: clearApp, without a shell.- WDA:
clearStateon a real iPhone falls back to go-ios when devicectl is unavailable or fails. It uninstalled and reinstalled the app withxcrun devicectlalone, so it failed wherever devicectl was missing or refused.clearStateand the first install now share one installer: devicectl first, then go-ios over usbmuxd.MAESTRO_RUNNER_IOS_INSTALLERapplies to both, every install and uninstall has a time limit, andclearStatelogs which tool it used. A go-ios install that times out now closes its connection instead of leaving it open. - A character XML forbids in an app's text no longer breaks every lookup on the screen (UIAutomator2, Appium). One U+0000 or control character in any label made the page source unparseable, so every selector on that screen failed. Such characters now read as U+FFFD, which no selector text matches by accident.
- The update notice appears only when the latest release is newer than the one running. It announced any latest version that differed from the running one, so a build newer than the latest release, a source build such as
v1.1.29-22-gf72df97among them, was told to "update" to an older one. A build made after a release tag now counts as newer than that release. - WDA: a failed session no longer holds xcodebuild for ten minutes collecting diagnostics. On Xcode 26 and later, xcodebuild could run
simctl diagnoseafter a failure; WDA now starts with-collect-test-diagnostics neveron Xcode 15 and later, as DeviceLab iOS does. - The Appium server URL is shown without its credentials. The console and
maestro-runner.logprinted--appium-urlas given, so a Sauce Labs URL showed the account's access key; they now show it without the user, password and query. The--appium-session-filekeeps the full URL, since tools attach to the session with it. - A simulator the runner boots opens in DeviceHub on Xcode 27. Xcode 27 has no Simulator.app, so
open -a Simulatoropened nothing. Where the selected Xcode has no Simulator.app, the runner opens DeviceHub (com.apple.dt.Devices); older Xcode still opens Simulator. The run continues if neither opens. - Sending the usage event takes less of the end of each run. It took about 0.45 s, most of it opening the connection;
testnow opens it in the background when the run starts, and the event, unchanged, is sent over it at the end. A connection the server has closed in between is replaced. Nothing is opened when telemetry is off, and behind a proxy the send works as before. A harness that starts one process per flow paid this per flow. - DeviceLab Android: a window that shows nothing no longer refuses a tap as "covered by system window", and a refusal names the window. A tap was refused while any window layered above the app covered its point; on RNTester one refused taps for 17 s while the screen showed no window at all. The agent now ignores a window above the app whose bounds are empty, that is on another display, or that has no visible content (no readable root, a root not visible to the user, or a root with empty bounds), and a window with content covers only as far as that content. The keyboard still counts over its whole window. The refusal in the log now gives the window's type, title, package, bounds and layer, and the agent logs a window it ignored at the tap point with the reason.
- WDA:
inputTextinto the focused field reads the field back and retypes dropped characters, asinputTextwith a selector already did. Tapping a field and then typing is how most flows type, and a dropped character went unnoticed. The check is skipped when the text has a newline or a tab, when focus has moved to another field (code boxes, split date or card fields), and when the field gained a character that was not typed, as a mask or a formatter adds one; a field is retyped only when it holds what was typed with characters missing. - DeviceLab iOS: the fast
clearState(DEVICELAB_IOS_FAST_CLEARSTATE=1) clears the app'sUserDefaultson a simulator. It emptied the app's data container, plist included, but the simulator's preferences daemon (cfprefsd) kept the app's preferences in memory and served them to the next launch, so a flow that relied on the clear still saw its saved settings. The clear now restarts that daemon, after stopping the app and before emptying the container, so nothing it still held is written back; it costs onesimctl spawncall. The daemon serves the whole simulator, so a preference another app had not yet written to disk is lost. A clear that cannot restart it now fails instead of leaving the values in place. The same applies to--driver devicelab-legacy. The defaultclearState, which reinstalls the app, and real devices are unchanged. --driver devicelab-legacyon iOS: a screen with a wide slider no longer wedges the runner. A slider at least 160 pt wide (and 1.75× its height) counted as a collapsed tab bar, so every snapshot of that screen read each element of the app one attribute at a time on XCTest's main thread, which cannot be interrupted; on a crowded screen that could hold the runner long enough for it to be restarted as wedged (found and fixed in agent-device, whose runner ours derives from). Sliders no longer count, and a real collapsed tab bar or segmented control is read in one query, started only while the snapshot's 8 s budget still covers what reading the screen took; otherwise the snapshot is returned without those tabs and marked truncated.
Contributors
runScripthonourswhen:(#238)copyTextFromkeeps the text and leaves the device clipboard alone (#239)- Relative selectors honour
focused,enabled,selectedandcheckedwithout text or id (#240) childOf,belowand the other relative selectors keep their anchor's index (#241)- Appium
launchApppasses arguments and environment on iOS;clearStateon a real iPhone reinstalls--app-file(#244) - Appium on iOS:
selected: truematches a selected tab or segment (#245) inputTextretype: false(#247)- A
${…}whose JavaScript throws fails its step (#250) - WDA
launchAppright after a reinstall waits for iOS to register the app (#251, #257) - Asked for pausing a flow at its failing step (#246)
- The pre-session scan finds a
launchAppinretryandrepeat(#216) - WDA
clearStateon a real device without Xcode (#217) - WDA long press holds 3 s (#218)
- WDA reads the focused field back after typing (#219)
- WDA
copyTextFromnever copies an empty string for a failed read (#220) - Hosted-frame correction (
MAESTRO_WDA_HOSTED_FRAMES) (#223, #232) --wda-urlfor a WebDriverAgent already running (#224, #229, #233)- CI on Linux and cancellation tests (#215, #222)
launchApp: clearStateno longer loses its launch to Android's cleanup (#225)scrollUntilVisiblescrolls in a row that runs past its container (#227)
- Reported WDA slow since 1.1.28 (#212), asked for WDA reuse (#213), reported simulator settings left changed (#214) and permission prompts WDA could not see (#248)
- Asked for airplane mode on DeviceLab iOS (#252) and script-relative
require()(#253), reportedhideKeyboardleaving the keyboard up (#259)
- Reported hidden web view content on DeviceLab iOS (#211)
- Reported
index:picking a different element than Maestro (#254)
- Reported the screen size not following a rotation on Android (#256)
- Reported DeviceLab Android
tapOnignoringindex:(#260)
- Reported a clipped sliver counted as visible by
scrollUntilVisible(#164)
Installation
Quick Install
curl -fsSL https://open.devicelab.dev/install/maestro-runner | bashInstall a specific version
curl -fsSL https://open.devicelab.dev/install/maestro-runner | bash -s -- --version 1.1.31Manual install (from the assets below)
# 1. Download the tarball for your platform.
# 2. Extract:
tar -xzf maestro-runner-1.1.31-darwin-arm64.tar.gz
# 3. Run setup (installs into ~/.maestro-runner/bin/):
cd maestro-runner && ./setup.shVerify Installation
maestro-runner --versionPlatform Support
- macOS Intel (amd64) - Signed & Notarized
- macOS Apple Silicon (arm64) - Signed & Notarized
- Linux amd64
- Linux arm64
Built by DeviceLab.dev