Migrating from Maestro
If you have an existing Maestro suite, the
migration is even smoother than a wholesale rewrite — klera reads your
existing Maestro YAML directly. Point klera run at a flows/login.yaml
file authored against Maestro and it works.
This guide covers what the compatibility loader does, where it draws the line, the keyword-by-keyword mapping, and the recommended incremental migration recipe so you can adopt klera-only features flow-by-flow without rewriting the suite up front.
What klera adds
Maestro shipped declarative YAML for mobile E2E testing, and klera shares that posture. The differences are what you get on top.
- Self-healing matcher. Maestro’s
id:/text:selectors are exact — when a screen redesign moves a button or renames atestID, the flow fails until someone hand-fixes the selector. klera walks a strategy ladder (testID→accessibilityLabel→ role+text → fuzzy text) and records drift events without rewriting the flow file. - Failed runs auto-classify. A failing klera flow produces a
verdict —
regression/drift/flake/data— plus a PM-readable narrative and a list of suspect commits. - OpenTelemetry-native runs. One env var
(
OTEL_EXPORTER_OTLP_ENDPOINT) ships traces, metrics, and logs to your existing OTel stack. No custom exporter to write. - Network mocking + state assertions in IR.
mockNetworkandassertJSare first-class steps; no out-of-band fixture servers or shell-out helpers required. - Lockstep cross-platform divergence. A single flow with
parallel: [ios, android]runs both platforms in step and surfaces divergences as typed report blocks.
Plus the lower-friction surface: zero-config adoption (klera init),
watch-mode authoring (klera run --watch), JSON Schema in the editor,
generated CI configs (klera ci), prose authoring with BYO-LLM.
The five-minute migration
Most suites adopt klera in a single afternoon. The sequence is short.
Run klera against your existing Maestro YAML
klera run flows/login.yaml --report reports/login.jsonIf the output is green, you are done. The compatibility loader detected the Maestro shape, converted the steps to klera IR in memory, and ran them on the same executor as a hand-authored klera flow.
Render the report
klera report reports/login.json --html reports/login.htmlYou now have a self-contained HTML page with per-step screenshots, a matcher trace, and — on failure — an auto-triage verdict.
Wire CI
klera ci github-actions --out .github/workflows/klera.ymlGenerated workflow runs the flows, converts the JSON report to JUnit XML, and surfaces failed steps in the GitHub Actions test-results pane.
That is the lowest-friction starting point: keep authoring in Maestro shape, get klera’s run-side advantages (self-healing, auto-triage, OTel, JUnit, HTML report) immediately. Rewrite to klera shape only when you need a klera-specific feature.
How the compatibility loader works
The detector runs as the first stage of loadFlowFromYaml. Two
top-level shapes are recognised:
# Multi-doc Maestro: appId config + steps array.
appId: com.example.app
---
- tapOn: 'Sign In'
- assertVisible: 'Welcome'# Bare-array Maestro: omit the appId doc entirely.
- tapOn: 'Sign In'
- assertVisible: 'Welcome'Either shape converts to klera IR in memory before the Zod parser
validates it. klera IR remains canonical. The loader is one-way: we
accept Maestro YAML, we do not export to it. Once you rewrite a flow in
klera shape to use klera-only features (mockNetwork, assertJS,
parallel:, prose flows, fixtures, visual snapshots), the Maestro path
is gone for that flow.
Keyword-by-keyword mapping
Each pair below shows a Maestro fragment and the klera IR it converts to. Both forms run on the same executor; the mapping is deterministic.
Tapping
Maestro
- tapOn: 'Sign In'
- tapOn:
id: 'submit-button'Typing
The converter infers the typing target from the preceding tapOn:
Maestro
- tapOn:
id: email
- inputText: 'user@example.com'inputText with no preceding tapOn is rejected with a clear hint
pointing at the explicit-target form.
Assertions
Maestro
- assertVisible: 'Welcome back'
- assertNotVisible: 'Error'Swipes and scrolls
Maestro
- swipe:
direction: LEFT
- scroll
- scroll:
direction: UPLEFT / RIGHT / UP / DOWN normalise to lowercase on the way in.
Lifecycle
Maestro
- launchApp
- launchApp:
arguments: ['--seeded']Hardware keys
Maestro
# Android hardware back
- pressKey: Back
# Android home key
- pressKey: HomeScreenshots
Maestro
- takeScreenshot: 'checkout'The converter appends .png if the name has no extension; an explicit
.png is preserved.
Synchronisation
Maestro
- waitForAnimationToEnd
- waitForAnimationToEnd:
timeout: 8000klera’s waitForIdle also gates on network activity. The auto-conversion
sets only the animations flag to preserve Maestro semantics exactly; if
you want both gates, rewrite as
waitForIdle: { animations: true, network: true, timeoutMs: ... }.
Coverage table
| Maestro | klera |
|---|---|
tapOn | tap |
inputText | type (target inferred from preceding tapOn) |
assertVisible | assert: { visible: ... } |
assertNotVisible | assert: { notVisible: ... } |
swipe | swipe |
scroll | scroll |
launchApp | relaunch |
pressKey: Back | pressBack (Android only) |
pressKey: Home | backgroundApp |
takeScreenshot: name | screenshot: { path: "name.png" } |
waitForAnimationToEnd | waitForIdle: { animations: true } |
waitForAnimationToEnd: { ... } | waitForIdle: { animations: true, timeoutMs: ... } |
What the loader does NOT handle
Honest list:
runFlow:— Maestro’s inline-subflow primitive. klera has no IR-level subflow step today; the converter rejects with a hint pointing at inlining or splitting at theklera runinvocation level.launchApp: { stopApp: false }— klera’srelaunchalways terminates and relaunches. Rewrite in klera shape, which can omitrelaunchentirely to keep a warm app.- Conditional / repeat blocks (
when:,repeat:,extendedWaitUntil:). klera flows are linear; conditional flow control belongs in the prose layer or in the orchestration around the CLI invocation. tapOnwithindex:. Maestro’s nth-match resolver has no klera analogue today; the converter dropsindex:and lets the strategy ladder pick.swipewithfrom:/to:selectors. klera’sswipeis coordinate-based; element-anchored swipes are rejected with a hint to rewrite as klera-shape with explicit coordinates.- Maestro Studio recordings with embedded device-state metadata. The metadata is dropped; steps convert as documented. klera’s own recording mode produces prose flows.
- Cloud runners. Maestro Cloud is a Mobile Dev Inc. product. klera
ships open-source; the equivalent is to wire
klera ci <target>into your existing CI provider.
File an issue with a representative test if any of these block you.
When to migrate to prose vs stay on YAML
Both have a place. Pick per flow.
| Stay on Maestro YAML when… | Move to klera prose when… |
|---|---|
| The flow already works and rarely changes | The flow describes intent more than mechanics |
| Your team’s authoring is engineer-driven | PMs / QA / designers want to contribute scenarios |
| You want a no-LLM authoring path | You want the planner to figure out steps + selectors |
| You rely on a Maestro keyword the loader covers | You need klera-specific features (mockNetwork, assertJS, parallel:, fixtures) |
The brittle flows (the ones rewritten most often) are typically the ones whose selectors moved most — exactly the cases klera’s matcher self-heals through. Keep Maestro-shape flows as-is and rewrite only when you need a klera-specific feature.
Side-by-side: Maestro YAML and klera prose
A login flow you might already have in Maestro shape, and the klera prose it lifts to.
Maestro YAML
# flows/login.yaml — runs unmodified on klera via the compatibility loader.
appId: com.example.app
---
- launchApp
- tapOn:
id: email
- inputText: 'user@test.com'
- tapOn:
id: password
- inputText: 'correct horse'
- tapOn: 'Sign In'
- assertVisible: 'Welcome back'
- takeScreenshot: 'after-login'klera prose
# Sign in with the seeded user
Relaunch the app. Type the seeded email into the email field and the
seeded password into the password field. Tap Sign In and assert the
welcome greeting is visible. Take a screenshot called "after-login".klera shape with klera-only features
When the flow needs deterministic network behaviour and credential management, rewrite in klera shape:
# yaml-language-server: $schema=../.klera/flow.schema.json
name: Sign in (klera shape)
steps:
- mockNetwork:
method: GET
path: '**/api/me'
status: 200
body: { name: 'Test User' }
- tap: { testID: email }
- type:
into: { testID: email }
value: ${secret:TEST_USER_EMAIL}
- tap: { testID: password }
- type:
into: { testID: password }
value: ${secret:TEST_USER_PASSWORD}
- tap: 'Sign In'
- assert: { visible: 'Welcome back' }
- visualSnapshot: { id: after-login, tolerancePercent: 1 }Editor autocomplete + hover docs come from the JSON Schema export automatically. The rewritten flow runs on the same executor as the Maestro-shape flows in the same suite — there is no “klera mode” toggle, just a parse-time shape detection.
${secret:KEY} values are redacted at every wire egress — failure
evidence, report JSON, OTel spans, recordings, triage payloads, and
logs. They never leak past the process boundary. See
fixtures and secrets.
Reference
- Quickstart —
klera initwalkthrough - Prose flows — the lead authoring surface
- YAML flows — hand-author IR directly
- IR reference — every step, every field
- Maestro docs — incumbent reference