Skip to main content
mobster import maestro reads Maestro flows and writes Mobster checks that mobster test, mobster verify --check and the run_tests MCP tool run. It keeps each flow’s name and the order of its commands. It reads the flows as data with a YAML parser, never runs a flow, and never sends a flow’s text to a model as an instruction. (mobster test import maestro is the same command.) No command is dropped without a line in the output. A command Mobster can’t run becomes a step marked TODO that Smart fills on the check’s first run, and the summary table counts it under “Needs a first run”. An assertion Mobster can’t judge is never turned into a step, because a model never decides a Mobster verdict: the flow is refused and counted under “Unsupported”.

A flow and its check

A Maestro flow, .maestro/checkout.yaml:
The check mobster import maestro .maestro writes to .mobster/checks/checkout.yaml:
The commands map one to one and keep their order. ${PROMO} was filled from the flow’s env. The two TODO steps are the commands Mobster doesn’t run. The expectations come last, because Mobster judges them on the accessibility tree once the steps end.

Import a folder of flows

From your project folder:
PATH is a flow file or a folder. A folder is read for its top-level .yaml and .yml files; a flow in a subfolder is imported when another flow runs it with runFlow. Checks go to .mobster/checks, or to the folder you give with -o.
The command exits 1 when it refused a flow and 0 otherwise. Each check starts with a comment that names the flow it came from. Converted counts the commands that became steps or expectations as written, including the commands of a runFlow subflow. Import twice and you get the same files: a check already on disk exactly as imported prints unchanged. A check that differs, such as one you edited, is kept and the output says so. Only --force replaces it.

Run them

The steps are plain English, so Smart runs them the first time and records the path. After a pass, the next runs replay the recorded path with no model (Replay and heals). The expectations are judged on the accessibility tree and never by a model. A check run on a real iPhone uses the same license gate as any other check: mobster test --device NAME.

What converts

${NAME} is replaced from the flow’s env, the runFlow env and the appId. A name with no value is refused.

How text matches

Maestro matches text as a pattern against the whole text and ignores case. The check keeps that: assertVisible: "get started" becomes /^get started$/i, so it matches the label GET STARTED. A selector that is already a pattern, such as "Order #[0-9]+", is anchored the same way. An id stays exact. The match is on the element’s accessibility label. The Expo starter’s documentation link has the label Expo documentation, arrow.up.right.square, so assertVisible: "Expo documentation" fails and assertVisible: "Expo documentation.*" passes. The failure names the closest label it found.

Assertions in the middle of a flow

Mobster judges every expectation once, after the last step. A flow that asserts between actions would lose those assertions if it became one check. Mobster writes one check per group instead: flow-part-1 holds the steps up to the first assertion and that assertion, flow-part-2 holds the steps up to the second, and so on. Each part replays the steps from the start. No assertion is dropped. Steps after the last assertion come after everything a check can judge. The output lists them, because no expectation covers them.

What is ignored

These options change timing or wording and are dropped, with no effect on what a check proves: label, duration, speed, timeout, retryTapIfNoChange, waitToSettleTimeoutMs, delay. optional: true on an assertion is refused, because an optional assertion judges nothing. On a tap it becomes a step that skips when the element isn’t there.

What needs a first run, and what is refused

Every row names the command and says what to do instead. A command that isn’t an assertion becomes a TODO step. Run the check once with Smart and read its report: Smart does what the step needs at that point, or nothing, and replay keeps the path it took. An assertion is refused, and so is a command with a value Mobster can’t read. The rows that name an assertion (assertTrue, assertWithAI, assertNoDefectsWithAI, assertScreenshot) refuse the flow. Every other row becomes a TODO step.

Check the result

Read the imported checks before you commit them. The import can’t tell whether a Maestro pattern matches the same label on iOS as it does in Maestro, and a flow written for Android may name text your iOS app doesn’t show. Run mobster test once and read the first report (Reports).