Sign inRequest access

Write the test once. Prove it three ways.

Plain TypeScript tests, run in the app, visually and from outside, on a warm simulator.

In development

It reads like a test.

  • await each action
  • expect waits for the app
  • no sleeps, no YAML
import { test, expect } from '@simbase/test';

test('user can start charging their EV', async ({ app, given }) => {
  await given.fixture('ev', { connected: true, battery: 42 });

  await app.tap('ev-card');
  await app.tap('start-charging');

  await expect(app).toBeOn('/ev/charging');
  await expect(app.node('charging-status')).toHaveText('Charging');
  await expect(app.request('POST /charging/start')).toHaveStatus(200);
});

One test. Three layers. One page.

checkout.sim.ts
  1. In-app

    Taps and reads run inside the app.

    tap ≈ 2ms

  2. Visual

    Named screens compared with a baseline.

    diff per checkpoint

  3. Black-box

    Touch from outside, like a person.

    tap ≈ 330ms

One evidence page, a verdict per layer

Where the time goes.

5–8×

faster per scenario

6.8s→1.1s

a six-step scenario, warm iPhone simulator

Seconds per phase, from outside versus in-app
PhaseFrom outsideIn-app
Reach the start1.1s0.15s
6 taps2.0s12ms
6 waits3.6s0.98s
Reads0.20s2ms
Desk estimate. Taps and reads are measured; in-app reset and settle on real apps are targets we’ll measure and publish.

Failures say where.

Each step is a screen. The whole journey is checked, not just the last one.

checked at every settle · no crash · no 5xx · POST /orders at most once

  1. Passed:

    log in

    /home

  2. Passed:

    open the product

    /product/kettle

  3. Failed:

    add to basket

    /basket

  4. Not run:

    pay

    /order/confirmed

✗ add to basket · expected /basket, still on /product/kettle

Snapshot, network and console attached.

An optional SDK. About ten lines.

Name your routes, fixtures and state. The same calls on every platform.

Swift

#if DEBUG
SimbaseTest.route("ev/charging") { EVChargingView.present() }
SimbaseTest.fixture("ev") { p in
    EVStore.shared.seed(connected: p.bool("connected"))
}
SimbaseTest.reset { EVStore.shared.clear() }
#endif

React Native

if (__DEV__) {
  SimbaseTest.register({
    navigation: navigationRef,
    state: { ev: () => store.getState().ev },
    fixtures: { ev: (p) => store.dispatch(seedEv(p)) },
    reset: () => store.dispatch(resetAll()),
  });
}

Kotlin

if (BuildConfig.DEBUG) {
    SimbaseTest.route("ev/charging") { EvChargingActivity.start(it) }
    SimbaseTest.fixture("ev") { p -> EvStore.seed(p.bool("connected")) }
    SimbaseTest.reset { EvStore.clear() }
}
  • Debug only

    Not in release builds. No flag turns it on.

  • Fails closed

    No run token, nothing listens.

  • Redacted in the app

    Only what you register. Secrets never leave.

  • Can’t cheat

    No SDK taps. State is read-only.

Questions.

Can I use this today?

Not yet. It’s in development. The beta runs builds with explicit steps, as the docs describe.

Do I have to add the SDK?

No. Without it you still get taps, the screen hierarchy, settle, network and console. The SDK adds routes, fixtures, state and events.

Do my existing runs change?

No. The framework uses the same devices, evidence pages and PR comments.

Is it open source?

The in-app runtime and the test API are planned as MIT. The black-box drivers and the fleet stay closed.

Start with explicit steps today.