Get started
Claim a simulator, let an agent drive your app on it and verify a screen, then release it.
Set up with your agent
Paste this into your coding agent. It installs warden if needed, then claims a simulator, drives your app, verifies a screen and hands the device back.
Use warden to drive this app on an iOS simulator and verify a screen. 1. If warden is missing: install it with bunx @delacour/warden install if bun is installed (check with command -v bun), otherwise npx @delacour/warden install. Show me the diff before you approve it. If warden is not found afterwards, add ~/.local/bin to PATH. Then run warden doctor and fix what it reports. 2. Run warden skill show, read it, and follow its rules: never touch a device you did not claim. 3. Run warden dev ios --json in the background and take the udid from its first line. Pass it on every call. 4. Run warden ui snapshot -i and find the elements you need. Prefer id= selectors over @e refs. 5. Act with warden ui press and warden ui fill, then verify with warden ui wait, is or get. A screenshot is not verification. 6. If an element has no testID, add testID and accessible in the component instead of retrying. 7. Stop warden dev when done; it releases the simulator and the Metro port. Report what you verified and the selectors you used.
The agent claims, looks, acts, verifies and releases. If a step fails it reads the exit code and hint, snapshots again and retries with a selector. Details of the loop: Agents drive devices. If the agent does not know warden yet: Set up your agent.
Or set up manually
By the end of this page your agent has its own simulator, has opened your app on it, read the screen as text, tapped and typed, checked the result and handed the device back. Nothing else on the Mac could take that simulator in the meantime, so several agents can do the same thing at once.
You need an app to drive and one of:
- macOS with Xcode and an iOS runtime, for simulators.
- The Android SDK, for emulators.
Install warden and the agent skill
npm i -g @delacour/warden && warden installwarden install links the binary, installs the skill that teaches your agent the rules and wires the SessionEnd hook for Claude Code and Codex. It shows a diff and asks first. Other agents and options: Set up your agent.
Claim a device
warden claim ios --jsonwarden reuses a free warden simulator, boots a stopped one or clones a new one, then waits until it is ready. If the host is full the claim waits in the boot queue (default --wait 30m). --json prints one document:
{
"leases": [{ "leaseId": "…", "udid": "…", "name": "warden-iphone-17-1", "action": "reuse", "label": "…" }],
"udids": ["…"],
"reclaimed": []
}Keep the udid (Android: the serial) and pass it on every later call. Do not pick a device from simctl list or adb devices: you may take another agent's.
A bare claim by an agent lasts 5 minutes and nothing refreshes it. Every warden ui call heartbeats the lease, so a device you keep driving stays yours. See Claim & release devices.
Run the app
In your app's directory:
warden dev ios --jsonwarden installs the dev build cached for your native fingerprint, starts Metro on a leased port and opens the dev client. The first stdout line is { udid, port, metroUrl, leaseIds, source }; read the udid from it instead of running step 2. Ctrl-C releases the device and the port. See warden dev.
To skip Metro and use the build with this worktree's JS repacked in, open the app on a claimed device:
warden ui open com.example.app --kind simulator --udid $UDIDOpen the app and read the screen
warden ui open com.example.app --udid $UDID
warden ui snapshot -i --udid $UDIDUnder warden dev, open attaches to the running dev client. snapshot -i prints the interactive elements and anything with a testID:
generation 4 · axbridge · 393×852@3x · 5 of 41 elements
@e1 [textbox] "Add a todo" id=todo-input value="" enabled hittable (24,118 345×48)
@e2 [button] "Add" id=todo-add enabled hittable (24,176 345×44)Each line is @eN [role] "name" id=<testID> value="…" <states> (x,y w×h). A ref is valid until the next snapshot; a selector such as id=todo-add outlives screen changes. Add testID and accessible in the component for anything the agent must touch.
Act
warden ui fill id=todo-input "Buy milk" --udid $UDID
warden ui press id=todo-add --settle --udid $UDIDfill types per key and reads the text back. --settle waits for a quiet screen and prints what changed as - / + lines. scroll, gesture, alert and key cover the rest: warden ui.
Verify
warden ui wait text "Buy milk" --udid $UDID
warden ui is id=todo-add enabled --udid $UDID
warden ui get id=todo-input value --udid $UDIDwait polls until the element or text appears (default --timeout 5000). is exits 0 for true and 1 for false. get reads text, value, label, id, role, frame or count. A screenshot is not verification.
Release
warden ui close --release --udid $UDIDThis stops the device's helper and frees the lease. Without it the bare claim expires after 5 minutes. For a claim you made without warden ui: warden release <leaseId> [--shutdown]. A device held by your running warden dev stays leased until you stop warden dev.
Control the device
Change what the device sees: network, location, clock, biometrics, permissions, camera feed, locale and timezone, accessibility settings, battery, and one-off push or memory-warning events. Each is recorded on your lease and undone on release.
warden ui network set 3g --udid $UDID
warden ui location set 37.3349,-122.009 --udid $UDIDBiometrics run the real Face ID, Touch ID or fingerprint prompt, with no app-side bypass:
warden ui biometrics enroll --udid $UDID
warden ui biometrics match --udid $UDIDEvery condition, its platform support and how to set it from a spec, a suite or warden ui: Device conditions. One page each: Network, Location, Clock, Biometrics, Permissions, Camera, Locale and timezone, Accessibility settings, Battery, Push and memory warnings.
Feed a picture or clip into the app's real camera, or check what a screen reader would say:
warden ui camera set fixtures/qr-hello.png --udid $UDID
warden ui a11y audit --udid $UDIDAudits, reading order, announcements and TalkBack: Accessibility testing.
Run many in parallel
Each agent claims its own device, so five agents get five simulators. For your own script, warden run claims devices and ports, runs a command and releases everything on exit:
warden run ios --count 2 --port 8091:20 -- bun run e2ewarden batch hands a list of independent jobs to N devices, next job to whichever frees up first. How many boot at once depends on the host's RAM and CPU: warden capacity shows the budget and warden queue your place in line. See Scripts and Capacity & queue.
Keep it as a test
A check that must keep passing is a spec run by warden e2e. It uses the same driver, so a selector that works in warden ui works in a spec.
What else is here
Agent control
Agents drive devices
The claim, look, act, verify, release loop.
warden ui
Every command, selector and exit code.
warden dev
A dev client and Metro per worktree.
Debug the running app
Metro output, console, exceptions, network, eval.
Agent rules
Sharing, subagents, hooks.
Device control
Claim & release devices
Claim, keep alive, see who holds what.
Device conditions
Network, location, clock, biometrics, permissions, camera, locale, accessibility, battery, events.
Accessibility testing
Audits, screen readers, announcements.
Physical devices
Android phones and iPhones, opt-in.
Profile an app
CPU, memory, frames, startup.