Write the test once. Prove it three ways.
Plain TypeScript tests, run in the app, visually and from outside, on a warm simulator.
It reads like a test.
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.
In-app
Taps and reads run inside the app.
Visual
Named screens compared with a baseline.
Black-box
Touch from outside, like a person.
Where the time goes.
5–8×
faster per scenario
a six-step scenario, warm iPhone simulator
| Phase | From outside | In-app |
|---|---|---|
| Reach the start | 1.1s | 0.15s |
| 6 taps | 2.0s | 12ms |
| 6 waits | 3.6s | 0.98s |
| Reads | 0.20s | 2ms |
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
- Passed:
log in
/home
- Passed:
open the product
/product/kettle
- Failed:
add to basket
/basket
- Not run:
pay
/order/confirmed
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() }
#endifReact 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.