Skip to Content
DocsMigratingFrom Detox

Migrating from Detox

If you have an existing Detox  suite, you already invested in selectors, assertions, and CI plumbing that work. klera is not a rebuild — adopt it incrementally, keep Detox running flow-by-flow, and retire the old suite once the new one has equivalent coverage.

This guide maps the patterns Detox tests use in practice to their klera equivalents, calls out the model-level differences worth understanding before you start, and ends with an honest “what doesn’t migrate yet” list.

What klera adds

Detox’s matchers, taps, types, swipes, and simulator control all have direct klera equivalents — the surface is familiar. The differences are what runs on top of that surface.

  1. Self-healing matcher. Detox’s by.id / by.text / by.label are exact matchers — when a screen redesign moves a button into a new container or renames a testID, the test 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.
  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. Detox surfaces the matcher failure; you triage from there.
  3. Lower-friction surface. Zero-config adoption (klera init), watch-mode authoring (klera run --watch), JSON Schema in the editor, generated CI configs (klera ci), OpenTelemetry-native runs.

Model differences worth understanding

Detox tests are imperative JavaScript that drive a device through function calls, run by a test runner (typically Jest). klera flows are declarative prose or YAML that an engine executes.

Detoxklera
Authoring surfaceJS / TS test filesProse (*.flow.md) or YAML (*.flow.yaml)
Execution modelTest runner orchestrates it blocksEngine reads IR, dispatches step-by-step
Selector bindingby.id('email') resolves at call time, exactStrategy ladder + drift recovery
Sync modeldevice.disableSynchronization() opt-outwaitForIdle step + automatic per-step settling
OutputTest runner report (Jest)Versioned JSON + HTML render + JUnit + OTel spans
Failure dossierStack trace + screenshotBoundary snapshots, frames, matcher trace, suspect commit

The shift: “set up the device, write a sequence of actions, assert” becomes “declare the steps in order; the engine handles wait, retry, drift, capture.”

Pattern-by-pattern mapping

Each pair below shows a Detox fragment and its klera equivalent. The klera column uses the short-form sugar where the meaning is unambiguous; long form is always available.

Tapping

await element(by.id('submit')).tap(); await element(by.text('Sign In')).tap();

Typing

await element(by.id('email')).typeText('user@example.com');

Scrolling and swiping

await element(by.id('list')).scroll(200, 'down'); await element(by.id('card')).swipe('left', 'fast');

Assertions

await expect(element(by.id('welcome'))).toBeVisible(); await expect(element(by.id('error'))).not.toBeVisible(); await expect(element(by.id('greeting'))).toHaveText('Welcome back');

Waits and synchronisation

Detox synchronisation is opt-out — you disable it when it gets in the way and remember to re-enable it. klera flips this: per-step settling is automatic, and you opt in to gate-aware idle when you need a specific signal (animations done, network quiet, both).

// explicit timeout await waitFor(element(by.id('toast'))).toBeVisible().withTimeout(5000); // disable sync, manual sleep await device.disableSynchronization(); await new Promise(r => setTimeout(r, 1000)); await device.enableSynchronization();

The runtime tracks Animated / LayoutAnimation / rAF for animations and wraps fetch / XHR / WebSocket for network; waitForIdle reports idle the moment both gates are clear.

Device + lifecycle

await device.reloadReactNative(); await device.launchApp({ url: 'myapp://welcome' }); await device.setLocation(48.8566, 2.3522); await device.matchFace(); await device.sendToHome(); await device.launchApp({ newInstance: false });

Permissions

Detox grants permissions at launch via launchApp({ permissions }). klera uses an explicit grantPermission step so the grant lives in the flow, visible to the reader.

await device.launchApp({ permissions: { camera: 'YES', notifications: 'YES' }, });

grantPermission is currently Android-only; iOS rejects with capability_unsupported. iOS adopters use the existing system-dialog auto-allow that ships with relaunch.

System UI

await element(by.label('OK')).atIndex(0).tap(); // dismiss alert await element(by.id('submit')).tap(); await element(by.label('Cancel')).atIndex(0).tap(); // action sheet

Visual snapshots

Detox’s screenshot diff requires the detox-snapshot-diff plugin and manual baseline management. klera ships visual regression as a first-class step:

- visualSnapshot: id: onboarding-step-1 tolerancePercent: 1

Baselines land at __baselines__/<flow>/<id>.png; mismatch produces a diff PNG triplet (actual / baseline / diff) embedded in the HTML report.

Worked example

A 30-line Detox spec rewritten as prose plus the IR cache klera commits alongside.

Detox

// e2e/login.test.js describe('Login', () => { beforeAll(async () => { await device.launchApp({ newInstance: true, permissions: { notifications: 'YES' }, }); }); it('signs in with the seeded user', async () => { await waitFor(element(by.id('email'))).toBeVisible().withTimeout(5000); await element(by.id('email')).typeText('user@test.com'); await element(by.id('password')).typeText('correct horse'); await element(by.id('submit')).tap(); await expect(element(by.text('Welcome back'))).toBeVisible(); }); });

klera prose

# Sign in with the seeded user Relaunch the app and grant notification permissions. Wait for the email field, type the seeded email and password, tap Sign In, and assert that the welcome greeting is visible.

That is the whole flow file (flows/login.flow.md). Save it and the Metro plugin compiles the prose to the IR cache below.

klera IR cache (committed plumbing)

# flows/login.flow.yaml — same flow, hand-authored. name: Sign in with the seeded user steps: - relaunch: true - grantPermission: notifications - wait: for: { testID: email } timeoutMs: 5000 - type: into: { testID: email } value: user@test.com - type: into: { testID: password } value: correct horse - tap: { testID: submit } - assert: { visible: Welcome back }

Both forms run on the same executor. Hand-author YAML when you want a no-LLM authoring path; write prose when you want the planner to figure out the steps.

Incremental migration

Two patterns work well in practice. You do not need to commit to a big-bang rewrite.

Side-by-side runs

Run Detox and klera in parallel during the migration window. Both suites run on the same dev client; klera attaches to a fresh bridge port. CI scripts add a klera step alongside the existing Detox step:

detox test --configuration ios.sim.release # existing klera run flows/login.flow.md \ --report reports/login.json # added

When klera coverage equals Detox coverage for a flow, drop the Detox test. The two engines do not conflict — Detox uses Earl Grey / XCUITest through the launchscreen; klera uses its own runtime + bridge.

Flow-by-flow rewrite

Pick the most-broken Detox test — the one rewritten most often, or the one that flakes most — and re-author it as a klera flow. Compare run-counts over a week. If klera wins on stability or debuggability, migrate the next flow. Repeat until the Detox suite is empty.

This recipe converges faster than wholesale rewrite because the most brittle Detox tests are typically the ones whose selectors moved most — exactly the cases klera’s matcher self-heals through.

Both engines share the simulator, not the runtime. Run them back-to-back rather than concurrently against the same simulator process — they will fight for control if they both think they own the app.

What does not migrate cleanly yet

An honest list, no marketing.

  • Custom matchers and helpers. Extracted helper modules are imperative JS — you re-author the steps as a flow, not the JS. The klera equivalent is shared flows via fixture files for parameter data.
  • Code-heavy assertions. Detox lets you call into JS to compute an expected value mid-test. klera’s prose path handles many of these by letting the planner derive the IR; for the rest, assertJS covers most cases.
  • Network mocking. klera mocks at the IR level — straight port for response-shape mocks, not yet for request-recording. See network mocking.
  • Element introspection. getAttributes() returns a JS object you can inspect mid-test. klera’s matcher trace is per-step in the report; mid-flow introspection happens via assertJS.
  • Multi-app flows. klera assumes one app per bridge today.

If your suite leans heavily on any of these, file an issue with a representative test.

Reference

Last updated on