Your first test
From an Expo app with no warden setup to a passing smoke spec, by hand and then with warden e2e.
You use this page once per app: it scaffolds the config and a smoke spec, then runs them. Prerequisites: warden installed (Installation), bun 1.3 or newer on PATH (or WARDEN_BUN), and an Expo app.
warden init --bundle-id com.example.app
bun add -d @delacour/warden-e2e@<warden version>
warden e2eScaffold
warden init --bundle-id com.example.app--bundle-id is only needed when the app has none yet; it is written to app.json. init writes warden.config.ts (a project and an e2e suite named after the app: specsDir: "e2e", platform: "ios", isolate: "device", app: true, a metro block that turns the Expo dev menu off) and e2e/smoke.e2e.ts. Existing files are kept (--force overwrites), and an app that already has specs gets no smoke spec. --dry-run prints the plan. It also prints the install commands for anything missing, such as expo-dev-client.
Install the spec package
bun add -d @delacour/warden-e2e@<warden version>Same version as warden --version: the spec preload refuses any other.
Read the smoke spec
import { test } from "@delacour/warden-e2e";
test.use({ launch: "none", systemAlerts: "dismiss" });
test("the app opens", async ({ app }) => {
await app.open({ wait: "quiet" });
});launch: "none" skips the runner's own fresh app.open(), so the test opens the app itself. systemAlerts: "dismiss" answers a system prompt that appears over the app. Replace the comment init leaves with a real assertion:
import { expect, test } from "@delacour/warden-e2e";
test("home shows", async ({ app, screen }) => {
await app.open({ wait: { until: screen.getByTestId("home") } });
await expect(screen.getByTestId("home")).toBeVisible();
});The home view needs testID="home" and accessible in the component.
Run it by hand (optional)
Claim a simulator, install your build on it, and point the spec at it:
warden claim ios --json # -> udid
WARDEN_UDID=<udid> WARDEN_BUNDLE_ID=com.example.app \
bun test --preload @delacour/warden-e2e/preload e2e/smoke.e2e.ts
warden release <leaseId>WARDEN_UDID switches the runner on; without it the preload does nothing. Add WARDEN_METRO_URL for a dev client. A bare claim lasts 5 minutes. Details: Write specs.
Run it with warden
warden e2e --count 3With one suite in the config the name is optional. warden leases 3 simulators, installs the dev client, starts Metro and spreads the specs over the devices. The first run of a native fingerprint builds the app (minutes); later runs reuse the cache. A project without @delacour/warden-e2e fails before any device is claimed.
How warden e2e works
A spec file, a suite in warden.config.ts and one command, warden e2e, that leases devices, installs the build and runs the specs in parallel.
Write specs
The @delacour/warden-e2e spec API - test, describe and hooks, screen queries, locator actions and reads, the nine matchers, app and device, failure artifacts and running a spec by hand.