Quickstart
From a freshly cloned Expo app to a passing flow in under five minutes.
The contract is one terminal, one command, one green tick.
Prerequisites
- Node ≥ 20
- Expo SDK 54+ with the New Architecture enabled (Bridgeless /
Fabric, default since RN 0.74). The runtime probes
global.RN$Bridgelessat startup and refuses to register on Old Architecture. - Xcode (for iOS) or Android Studio (for Android) installed on your host machine.
- A booted simulator or attached device.
xcrun simctl boot "iPhone 16 Pro"for iOS;emulator -avd <name>for Android.
That’s it. No API keys, no native module setup, no XCUITest project scaffolding.
Run klera init
From the root of your Expo app:
cd my-expo-app
npx @klera/cli initinit is opinionated. It detects your Expo SDK, picks the right native
driver packages, installs them with the package manager you already
use, codemods your entry file to wrap the root in
<KleraRuntimeProvider>, scaffolds a smoke flow, scaffolds the
project’s klera config, and runs klera doctor inline before it hands
control back to you.
Output looks like:
✓ detected Expo SDK 54.0.0 (NewArch: enabled)
✓ platforms: iOS + Android
✓ scaffold flows/welcome.flow.md (prose)
✓ scaffold flows/welcome.flow.json (pre-baked IR cache)
✓ scaffold .klera/config.yaml
✓ wrap App.tsx root in <KleraRuntimeProvider>
✓ install klera, @klera/runtime, @klera/native-driver-ios,
@klera/native-driver-android via pnpm add -D ...
running klera doctor:
✓ Node ≥ 20 v20.x.x
✓ dist/ artefacts cli, mcp, engine built
✓ iOS Simulator 1 booted
✓ DeviceDriver native linked + reachable
✓ New Architecture Bridgeless detected
✓ Planner transport claude (subscription-paid)init is idempotent. Re-running on an already-set-up project shows
↺ for steps that already applied. Useful flags:
--dry-run— print the plan without touching disk.--force— re-apply every step, overwriting existing scaffolds.--no-install— skip the install step (yarn workspaces, lockfile bots, etc.).--no-doctor— skip the inline doctor invocation.--yaml— opt back into the YAML scaffold instead of prose.
If klera init cannot codemod your entry file (exotic root-component
shape), it writes .klera/manual-patch.md with the exact snippet to
apply by hand. The codemod handles the unmodified
npx create-expo-app template plus the most common variants.
Look at the scaffolded flow
flows/welcome.flow.md:
# Welcome smoke
Wait for the welcome screen to settle (animations and network idle),
assert that the text "Welcome" is visible, then capture a visual
snapshot called "welcome".The paired flows/welcome.flow.json cache compiles down to three IR
steps:
waitForIdle— wait for animations and network requests to settle. On a freshly launched app this gives the welcome screen time to render before the assertion fires.assert— match any element whose text contains “Welcome” via the matcher’s strategy ladder (testID → accessibilityLabel → role+text → fuzzy text).visualSnapshot— capture the current screen as a baseline PNG on first run; pixel-diff against it on subsequent runs.
The first run consumes the pre-baked cache — no LLM call needed.
Run your first flow
pnpm
pnpm exec klera run flows/welcome.flow.mdGreen-tick output:
▸ flows/welcome.flow.md
✓ waitForIdle 420ms (animations + network idle)
✓ assert "Welcome" 38ms (testID strategy)
✓ visualSnapshot "welcome" 91ms (baseline captured)
✓ welcome — passed in 549ms (3 steps)
report: .klera/reports/welcome-2026-05-01T12-04-22.jsonIterate without re-running by hand
Add --watch and the run loop reuses the same bridge across saves:
# Terminal 1: keep the dev client + bridge running.
pnpm exec klera serve
# Terminal 2: watch a flow.
pnpm exec klera run flows/welcome.flow.md \
--attach ws://127.0.0.1:7345 \
--watch
# ✓ welcome — passed in 480ms (3 steps)
#
# (edit flows/welcome.flow.md, save)
# ↻ flows/welcome.flow.md changed; re-running
# ✓ welcome — passed in 410ms (3 steps)Iteration latency drops from ~1.5s of process startup to ~50ms of
re-parse + dispatch. Network-mock fixture JSON referenced from the
flow is watched too — editing fixtures/login-response.json triggers
the same re-run loop.
--watch requires --attach so the bridge stays alive across saves;
mutually exclusive with --parallel. Ctrl-C exits cleanly.
See the report
Every run writes a JSON report. Render an HTML viewer with side-by-side visual diffs, idle-wait bars, the matcher trace, and (on failure) the triage card:
pnpm exec klera report .klera/reports/welcome-*.json --html report.html
open report.htmlFor CI test-results panes, derive JUnit XML:
pnpm exec klera report .klera/reports/welcome-*.json --junit report.xmlEditor support for hand-authored YAML
The prose path doesn’t need editor schema support — prose is just
markdown. Editor support kicks in for adopters who reach for the YAML
escape hatch (--yaml on klera init, or hand-authored YAML
flows alongside prose). The YAML scaffold ships a one-line directive
that wires up tooling across every editor that runs
yaml-language-server (VS Code, JetBrains, Neovim, Helix, Sublime):
# yaml-language-server: $schema=../.klera/flow.schema.json
name: Welcome smoke
steps:
- waitForIdle: { animations: true, network: true }
- assert: { visible: Welcome }
- visualSnapshot: { id: welcome }Materialise the schema once:
pnpm exec klera schema --out .klera/flow.schema.jsonRe-run klera schema --out ... whenever you upgrade klera to refresh
the schema for the new IR.
Troubleshooting
If klera doctor failed during init, the check that flagged the issue
is the place to start. The typical first-time culprits:
- No booted simulator.
xcrun simctl boot "iPhone 16 Pro". - Dev client not yet rebuilt.
pnpm exec expo run:iosorexpo run:android. - Old Architecture detected. Bridgeless / Fabric is required.
Check
app.jsonfor"newArchEnabled": true(default since Expo SDK 51).
If the install step failed, run it by hand with the displayed command —
init’s log line shows the exact <bin> add -D <pkg…> invocation it
tried.