Skip to Content
DocsMigratingFrom Maestro

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.

  1. Self-healing matcher. Maestro’s id: / text: selectors are exact — when a screen redesign moves a button or renames a testID, the flow fails until someone hand-fixes the selector. klera walks a strategy ladder (testIDaccessibilityLabel → role+text → fuzzy text) and records drift events without rewriting the flow file.
  2. 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.
  3. 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.
  4. Network mocking + state assertions in IR. mockNetwork and assertJS are first-class steps; no out-of-band fixture servers or shell-out helpers required.
  5. 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.json

If 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.html

You 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.yml

Generated 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

- tapOn: 'Sign In' - tapOn: id: 'submit-button'

Typing

The converter infers the typing target from the preceding tapOn:

- 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

- assertVisible: 'Welcome back' - assertNotVisible: 'Error'

Swipes and scrolls

- swipe: direction: LEFT - scroll - scroll: direction: UP

LEFT / RIGHT / UP / DOWN normalise to lowercase on the way in.

Lifecycle

- launchApp - launchApp: arguments: ['--seeded']

Hardware keys

# Android hardware back - pressKey: Back # Android home key - pressKey: Home

Screenshots

- takeScreenshot: 'checkout'

The converter appends .png if the name has no extension; an explicit .png is preserved.

Synchronisation

- waitForAnimationToEnd - waitForAnimationToEnd: timeout: 8000

klera’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

Maestroklera
tapOntap
inputTexttype (target inferred from preceding tapOn)
assertVisibleassert: { visible: ... }
assertNotVisibleassert: { notVisible: ... }
swipeswipe
scrollscroll
launchApprelaunch
pressKey: BackpressBack (Android only)
pressKey: HomebackgroundApp
takeScreenshot: namescreenshot: { path: "name.png" }
waitForAnimationToEndwaitForIdle: { 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 the klera run invocation level.
  • launchApp: { stopApp: false } — klera’s relaunch always terminates and relaunches. Rewrite in klera shape, which can omit relaunch entirely 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.
  • tapOn with index:. Maestro’s nth-match resolver has no klera analogue today; the converter drops index: and lets the strategy ladder pick.
  • swipe with from: / to: selectors. klera’s swipe is 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 changesThe flow describes intent more than mechanics
Your team’s authoring is engineer-drivenPMs / QA / designers want to contribute scenarios
You want a no-LLM authoring pathYou want the planner to figure out steps + selectors
You rely on a Maestro keyword the loader coversYou 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

Last updated on