Runtime / Mobile / Device infrastructure
Maestro
Run a supported native flow through the Revyl driver and the genuine Maestro runtime.
The Revyl Maestro runner embeds Maestro's published YAML parser and execution engine with a custom Revyl device driver. Maestro still interprets the flow and evaluates its assertions; the driver sends supported native operations through Revyl's authenticated device API and its existing viewer control channel.
This is a source-installed runner, not a plugin for stock maestro test. It
supports the native commands listed below, and text input goes through the
viewer channel with the limits in
Text input through the viewer channel.
This is not full Maestro compatibility.
For released device lifecycle commands, use the device quickstart. A session UUID or viewer URL does not establish an ADB connection or make stock Maestro discover the device.
How it connects
Your Maestro YAML flow
|
Local Revyl runner + Maestro 2.10.0 runtime
|
Revyl driver + authenticated device API / viewer control channel
|
Existing Android emulator / iOS simulator sessionThe runner is a separate JVM application. It does not require local Android SDK/ADB, Xcode, a Maestro device-side driver installation, or Revyl's AI test runner. It does not provide the stock CLI's workspace selection, sharding, or reporting features.
Source access and session preparation
Get the standalone Maestro driver
and follow its README to build the revyl-maestro launcher with Java 21 before
loading runtime credentials. There is no package registry release yet.
git clone https://github.com/RevylAI/maestro-revyl-driver.git
cd maestro-revyl-driver
./gradlew --no-daemon installDistSelect an existing, running Revyl session with your app already installed and
launched. Keep its session UUID, and provide REVYL_API_KEY through the
runner's environment or your secret manager. Never put a key into the YAML
flow or command-line arguments. Session ownership is checked by the backend
on attachment and every proxied operation. Text input obtains its session-bound
control connection from the authenticated backend; do not supply a viewer or
worker URL yourself.
Write a supported flow
The sample flow requires an app whose initial screen contains a visible
Continue button. Tapping it must produce the visible text Welcome:
appId: com.example.app
---
- assertVisible: "Continue"
- tapOn: "Continue"
- assertVisible: "Welcome"
- assertNotVisible: "Continue"
- takeScreenshot: smokeReplace the app ID, selectors, and expected outcome with your app's actual
values. The example deliberately omits launchApp: the app must already be
running. A saved screenshot is supporting evidence, not a replacement for an
assertion.
From the source package directory, run the built launcher against the exact session:
build/install/revyl-maestro/bin/revyl-maestro \
--session YOUR_RUNNING_REVYL_SESSION_UUID \
--platform android examples/smoke.yamlUse --platform ios for an iOS session. This command interacts with a real
device when configured with real credentials. Keep the runner's exit code in
CI; parse errors, unsupported commands, failed assertions, and transport
failures must fail the job. Keep screenshots private.
Supported native subset
The runner supports hierarchy-backed assertVisible and
assertNotVisible, single selector or coordinate taps, and PNG screenshots.
extendedWaitUntil polls an assertion, while waitForAnimationToEnd performs
a bounded screenshot comparison rather than proving a specific final state.
Assertions use the native accessibility hierarchy, not an independent visual
or occlusion check. Native resource IDs and text can differ between platforms;
adapt selectors rather than assuming an Android ID is an iOS accessibility ID.
Screenshot names must be unique simple names without paths or extensions.
takeScreenshot: smoke writes a private .revyl-maestro/smoke.png file;
existing files are rejected rather than overwritten.
| Command | Behavior and limits |
|---|---|
longPressOn | A 3000-ms hold at an element center or native/percent point. |
tapOn with repeat, doubleTapOn | An intentional sequence of individually acknowledged taps. Counts are 1–100; delays are 0–10000 ms, with at most 120000 ms total requested delay per sequence. This does not guarantee native double-tap timing. |
pressKey | Enter, Backspace, and Home; Back and the back command are Android-only. |
eraseText | Sends 1–100 Backspaces to current focus. Omitted count means 50 Backspaces, not a guarantee that the field is empty. |
setClipboard, copyTextFrom | Store text in Maestro's local clipboard variable, not the device's OS clipboard. Literal clipboard text is limited to 16 KiB UTF-8. See the text input limitations before using pasteText. |
inputText, pasteText | Use the existing viewer control channel on Android and iOS; no new runtime endpoint is required. Read the focus, completion, character, and privacy limitations below. |
setLocation | Sends finite latitude in −90…90 and longitude in −180…180 degrees. |
setDarkMode | Explicit enabled or disabled; no toggle or appearance-state assertion. |
openLink | Plain absolute HTTP(S) or app-scheme links with autoVerify: false and browser: false. Uses system routing without forcing an app. |
These platform claims concern Android emulators and iOS simulators, not physical iOS devices. Native flows verified long press, repeated taps, focused deletion, Enter, Home, appearance changes, and deep links on both platforms, plus Android Back. Local clipboard commands also ran in those flows.
The complete flow is checked before attachment. The limits are 999 source
commands and 1000 expanded commands, counting each repeated tap or Backspace.
Optional commands, scripts/expressions, hooks, nested flows, general repeat/retry
blocks, retryTapIfNoChange, and waitUntilVisible are rejected. Unsupported
driver operations also fail explicitly during execution.
Launch is intentionally narrow
Stock Maestro's default launch stops the app and changes permissions. The current Revyl proxy does not provide that complete lifecycle contract. The only supported form requests neither operation:
- launchApp:
stopApp: false
permissions: {}Do not add clear-state/keychain flags or launch arguments. Preparing the app before running the flow is the simpler path.
Text input through the viewer channel
inputText and pasteText use Revyl's existing manual-input control channel
on Android emulators and iOS simulators. They do not depend on a new focused-input
endpoint or worker capability. pasteText sends the value from an earlier
setClipboard or copyTextFrom; it does not read the device's OS clipboard.
Literal and dynamically copied input must be nonempty valid Unicode, at most
16 KiB UTF-8. Control characters are rejected except tab, newline, and carriage
return. Passing this validation does not guarantee the device transport supports
every character.
The runner requests input at current focus without clearing or tapping. Android uses the worker's text provider; iOS uses its paste path. These are the viewer's existing behaviors, not a stronger native text contract:
- Focus and selection: the worker's
skip_tapoption permits a refocusing fallback when passthrough is unavailable. The adapter does not deliberately tap a field, but cannot guarantee caret or selection preservation on every worker. Focus the intended field before input and assert the resulting value. - Completion: the runner waits for the matching completion acknowledgment, not just receipt. This means the handler returned successfully, not that the app's field value was verified. A missing, failed, or invalid acknowledgment stops the flow without replaying the input or sending later mutations.
- Viewer interaction: close the live viewer while running a flow with input. A connected viewer's WebRTC data channel can receive the acknowledgment instead of the runner's WebSocket. A timeout may mean text was already inserted; inspect the device before rerunning the flow.
- Character support: Android fallback transports have different character limits. iOS input can change the device clipboard, and the pasteboard fallback can show a paste-permission dialog. Unicode validation is not proof of reliable insertion across those paths.
- Privacy: the existing worker input path can log or record typed text. Do not use it for passwords, tokens, or other sensitive content. Adapter diagnostics omit text and control-connection credentials, but cannot change server-side recording behavior.
Live text insertion through this path hasn't been verified.
Swipe, scroll, and unsupported commands
swipe and scroll run only when the session's device worker supports
duration-preserving drags. If it doesn't, Revyl rejects the whole flow before
any step runs, including earlier taps or launches.
Verified clear, app stop/kill/reset, keychain, permissions, orientation, keyboard dismissal, media, recording, proxy, airplane mode, AI, and WebView operations remain unsupported.
Evidence, retries, and cleanup
The adapter adds no HTTP retries, input replay, or Maestro recovery-tap retries. A timeout can still mean an action executed; inspect the device before repeating it. Existing worker-side retry behavior is unchanged, so this is not an end-to-end exactly-once guarantee. Avoid destructive flows and concurrent controllers.
The runner only attaches and detaches. It never allocates, installs, resets, or stops the borrowed Revyl session. The job that owns the allocation must release it separately, including after test failures:
revyl device stop --session-id "$REVYL_SESSION_ID"Do not submit this flow to Revyl's own YAML test runner: its schema and execution model are different. For broader Maestro compatibility, retain your current execution environment until a verified connection and command contract is available.
Next steps
- Prepare an app and session with the device quickstart.
- Compare the Appium driver.
- Check artifact requirements before uploading a build.