Device conditions

Change the network, GPS location, clock, biometrics, permissions, camera feed, locale, accessibility settings and battery of a simulator or emulator from a spec, a suite or warden ui, and have it undone on release.

warden ui network set offline, edge, online: the app's own requests are shaped, nothing else on the Mac slows down.

A spec can put the device into a condition (offline, 3G, one host failing, a moving GPS fix, a clock two weeks ahead, a failed Face ID, a denied permission, a QR code in front of the camera) and check what the app does. Everything a test changes belongs to its lease: when the test ends, or the lease is released or reclaimed, warden puts it back, so the next agent never inherits a shaped device.

e2e/network-offline.e2e.ts
import { expect, test } from "@delacour/warden-e2e";

test("cold start offline shows the banner, going online recovers the fetch", async ({ app, device, screen }) => {
  await device.network.set("offline"); // before the app opens: a cold start offline
  await app.open();
  await expect(screen.getByTestId("conditions-offline-banner")).toBeVisible({ timeout: 15_000 });

  await device.network.set("online");
  await expect(screen.getByTestId("conditions-offline-banner")).toBeHidden({ timeout: 15_000 });
});

The example app has a Conditions screen (warden-example://conditions) and a spec for each area in apps/example/e2e.

Conditions

Platform support

Simulators and emulators only: every condition fails E_UNSUPPORTED on a physical device.

ConditioniOS simulatorAndroid emulator
Networkthe app's NSURLSession traffic, through a per-app proxynative speed and offline; loss and faults through the system proxy
Locationpoints and routes; a route plays oncepoints and routes, looped or not; the last fix stays
Clockthe app's clock; can be frozenthe emulator's clock; cannot be frozen
BiometricsFace ID and Touch IDfingerprint
Permissionsevery name but camera and notificationsthe names the manifest declares
Cameraimage or video, swapped live; QR decodeda --camera emulator; a video feed reboots it
Locale and timezonethe next launchlive; locale needs Android 13+
Accessibility settingstext size, bold, contrast, reduce motion; a simulated screen readertext size, bold, reduce motion; real TalkBack
Batterystatus bar onlywhat the app reads
Push and memory warningspushmemory warning

On iOS, network and clock reach only an app launched with warden's network dylib: declare them in the suite's conditions, or pass { relaunch: true } (how it reaches the app).

Three places to set one

The same conditions are available from a spec, as a suite's starting state, and by hand while you drive a device.

await device.network.set("3g");
await device.location.scenario("city-run");
await device.clock.advance("15d");
await device.permissions.set({ location: "denied" });

Suite baseline: the conditions key

conditions at the top level of warden.config.ts, or inside a suite, is the device state every spec starts from. It is applied before the app launches and put back after each spec.

warden.config.ts
e2e: {
  conditions: {
    ...exampleSuite,
    include: ["{network-*,location-*,clock-*}"],
    conditions: { network: "online" },
  },
},
AreaValue
network"online", "offline", a preset or { latencyMs, jitterMs, downKbps, upKbps, lossPct }
location{ lat, lon, altM? } or a scenario name
clockan ISO instant (it keeps running), { at, frozen? } or { offset: "2d" }
permissions{ location: "denied", camera: "granted", … }
localea BCP 47 tag
timezonean IANA name
a11y{ textSize?, boldText?, contrast?, reduceMotion? }

A suite's key replaces the top-level one for that area. A bad value fails config load. On iOS, declaring network or clock launches the app with the dylib, which is why the example runs its condition specs in a separate conditions suite: the other specs keep a plain launch.

warden e2e conditions --specs network-offline,network-slow,location-route,clock-trial

The camera feed is the suite's own camera key, not a conditions key (Camera). Biometrics is not one either: enrolled devices come from biometrics: true (Biometrics).

Timeline

const run = await device.timeline([
  { at: "0s", network: "offline" },
  { at: "5s", network: "online", location: { lat: 37.33, lon: -122.0 } },
]);

A timeline changes the network and the location on a schedule. at is an offset from now. A step sets a condition, not a flap. The returned run has the same stop(), done and status() as a flap.

What is undone on release

Release, gc and the end of a spec put the device back:

  • network online, faults cleared, the shaping proxy, the flap scheduler and the route ticker stopped
  • the location cleared on iOS (Android keeps the last GPS fix: the emulator cannot unset one)
  • the clock real again; a still-running injected iOS app is reset to online and the real clock
  • permissions reset, text size and contrast or font scale and bold back to defaults, the app locale cleared, the timezone back, the battery real, the camera feed off

Each change a spec makes is a condition line in results.jsonl.

On this page