Framework comparison
The same 8-test suite on one build and one device in warden, Argent, agent-device and tester.army. Suite time, per-test medians, cold start, resources and footprint.
Every framework runs the same 8 parity tests against the same .app, with no LLM involved. Every run happens on EAS Workflows, one macos-large worker and one fresh simulator per framework (see iOS run 2026-10-25 on EAS for how, and for every artifact). See Benchmark e2e frameworks to run it yourself.
On the 2026-10-25 run (warden 0.3.1) warden finished the warm suite in 86.30 s: 1.56× faster than Argent, 1.06× faster than agent-device (which dropped one test) and 1.54× faster than tester.army. Per step its median was 85 ms against 321 to 481 ms (3.8× to 5.7×), on 2.97 host CPU-seconds per rep against 4.13 to 17.74 (1.4× to 6.0×).
iOS run of 2026-10-25 · full report
Fastest, all tests passing
86.30 s
warden, warm suite median
Ratio spread
1× to 1.56×
warden to Argent
Passed every test
3 of 4
agent-device 97.5%
Suite
8 tests
5 runs each, run 1 cold
- warden
- Argent
- agent-device
- tester.army
Suite time
Warm suite wall time, measured by the harness around the whole suite process. Warm numbers leave out the first, cold run.
Per test
Warm median per canonical test. Compare these before the step numbers.
| Test | warden | Argent | agent-device | tester.army |
|---|---|---|---|---|
| onboarding steps through to sign-in | 2.93 s1.00× | 8.30 s2.83× | 3.83 s1.31× | 5.96 s2.04× |
| invalid email and short password show field errors | 16.05 s1.00× | 18.22 s1.13× | ✗ 16.58 s1.03× | 21.12 s1.32× |
| a locked account shows the form alert | 10.10 s1.04× | 14.44 s1.49× | 9.67 s1.00× | 12.91 s1.34× |
| valid credentials land on todos | 9.79 s1.00× | 15.42 s1.58× | 9.78 s1.00× | 14.85 s1.52× |
| add, complete and filter todos | 14.49 s1.00× | 21.11 s1.46× | 15.47 s1.07× | 21.23 s1.46× |
| open a todo, mark it done, then delete it | 11.09 s1.00× | 19.11 s1.72× | 12.42 s1.12× | 18.12 s1.63× |
| deep link opens a todo by id | 10.66 s1.02× | 17.81 s1.70× | 10.48 s1.00× | 18.09 s1.73× |
| settings shows the account, toggles switches and signs out | 10.95 s1.00× | 19.62 s1.79× | 13.14 s1.20× | 18.07 s1.65× |
Bold: fastest in the row. ✗: the test failed in at least one run, so the time is not comparable.
Step latency
Steps are not the same size in every tool. warden's fill is one step covering the focus tap, select-all, typing and the read-back confirm, so its long tail is the typing cost of a text field (the per-test totals above are the like-for-like number). argent reports a separate idle step and into …, agent-device reports fill and type separately, and tester.army reports locator steps. The table under the chart shows which verbs the step time goes to.
| Verb | n | Share | Median | p95 | Max |
|---|---|---|---|---|---|
| warden | |||||
| fill | 68 | 80.4% | 3.38 s | 4.20 s | 4.98 s |
| tap | 180 | 10.6% | 75 ms | 333 ms | 620 ms |
| expect | 256 | 9.1% | 21 ms | 558 ms | 578 ms |
| Argent | |||||
| into | 68 | 27.9% | 2.15 s | 2.81 s | 2.82 s |
| idle | 232 | 27.5% | 925 ms | 1.04 s | 1.24 s |
| visible | 320 | 18.3% | 13 ms | 1.03 s | 1.04 s |
| id= | 380 | 12.8% | 322 ms | 352 ms | 372 ms |
| launch | 32 | 10.9% | 1.80 s | 1.82 s | 1.83 s |
| hidden | 32 | 2.4% | 16 ms | 1.05 s | 1.05 s |
| open-url | 4 | 0.1% | 116 ms | 124 ms | 124 ms |
| next-test.yaml | 28 | 0% | 4 ms | 6 ms | 6 ms |
| other | 96 | 0% | 1 ms | 5 ms | 6 ms |
| agent-device | |||||
| press | 244 | 40.6% | 520 ms | 1.02 s | 1.09 s |
| type | 64 | 23.9% | 1.26 s | 1.86 s | 1.89 s |
| wait | 276 | 20.9% | 55 ms | 969 ms | 1.08 s |
| open | 36 | 9% | 1.02 s | 1.07 s | 1.10 s |
| fill | 12 | 5.5% | 1.69 s | 1.75 s | 1.75 s |
| tester.army | |||||
| getbytestid | 496 | 69.5% | 267 ms | 3.23 s | 4.01 s |
| dismiss | 52 | 22.3% | 2.26 s | 2.33 s | 2.33 s |
| (unlabelled) | 32 | 6.3% | 1.03 s | 1.05 s | 1.06 s |
| accept | 4 | 1.6% | 2.02 s | 2.03 s | 2.03 s |
| warden-example | 4 | 0.2% | 290 ms | 295 ms | 295 ms |
| getbytext | 4 | 0.1% | 65 ms | 66 ms | 66 ms |
Share is of the framework's total step time, from the warm runs. What one step covers differs per tool, so compare a verb's share within a tool, not its median across tools.
Cold start, prepare, teardown
Cold-start cost is the first run minus the warm median, plus the time spent in prepare: what installing a runner app or starting a daemon costs, not steady-state speed. Overhead is suite wall time minus the sum of test durations.
A negative cold-start cost is not a duration. It means the cold first run finished faster than the warm median (run-to-run noise on a framework with little startup cost), so the table shows "none (rep 1 not slower, ...)" and leaves it out of the fastest-cell highlight.
| Metric | warden | Argent | agent-device | tester.army |
|---|---|---|---|---|
| Warm suite median | 86.30 s | 134.32 s | 91.61 s | 132.52 s |
| Warm suite p95 | 86.71 s | 134.65 s | 91.70 s | 132.89 s |
| Cold first run | 71.98 s | 133.61 s | 87.47 s | 150.63 s |
| Cold-start cost | none (rep 1 not slower, -13.72 s) | 1.01 s | 12.23 s | 18.11 s |
| Prepare | 596 ms | 1.72 s | 16.37 s | 0 ms |
| Teardown | 72 ms | 293 ms | 315 ms | 495 ms |
| Overhead outside tests | 67 ms | 163 ms | 213 ms | 2.24 s |
| Run-to-run spread (cv) | 0.5% | 0.2% | 0.2% | 0.2% |
| Pass rate | 100% | 100% | 97.5% | 100% |
| Flake rate | 0% | 0% | 2.5% | 0% |
Resources
CPU is judged in CPU-seconds per rep: the CPU time the framework's own processes used during one timed run (warm median), from per-process deltas of ps time. The sampled %cpu is shown last as a secondary number. Memory is the framework's own processes, sampled every 500 ms.
| Metric | warden | Argent | agent-device | tester.army |
|---|---|---|---|---|
| Host CPU-s / rep | 3.0 s | 4.1 s | 11.2 s | 17.7 s |
| Host memory avg | 145.9 MB | 181.4 MB | 711.9 MB | 598.2 MB |
| Host memory peak | 158.6 MB | 236.7 MB | 2.4 GB | 2.5 GB |
| Host %cpu avg | 3.0% | 2.7% | 13.6% | 13.8% |
| Host %cpu peak | 39% | 42% | 612% | 611% |
| Simulator %cpu avg | 74.3% | 44.5% | 86.0% | 84.1% |
| Simulator %cpu peak | 283% | 210% | 249% | 248% |
Host CPU-s / rep: warm median of the timed run. %cpu is a decaying average, inflated by a short suite; compare CPU-seconds.
Where the CPU goes
The same CPU-seconds split by process name, from the warm-median rep. A name keeps its colour in every bar, the six biggest names are shown and the rest are folded into other. The sampler ticks every 500 ms, so a process that lives between two ticks only shows up through the exit charge booked when its parent reaps it.
Host processes (the framework's own tree)
- xcodebuild
- simulator-server
- bun cli.ts
- warden-simd
- snapshot-bridge
- node entry.js
- other
- warden2.9 s
- Argent4.1 s
- agent-device11.1 s
- tester.army17.7 s
Table
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| warden-simd | 1.8 s | 60.0% | 56.5 MB |
| bun test | 1.2 s | 39.5% | 90.1 MB |
| bun test (exit charge) | 0.0 s | 0.5% | 0 B |
| bun cli.ts | 0.0 s | 0.0% | 0 B |
| simctl | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| simulator-server | 3.9 s | 95.6% | 96.0 MB |
| node argent | 0.2 s | 4.3% | 85.7 MB |
| node argent (exit charge) | 0.0 s | 0.1% | 0 B |
| (simctl) | 0.0 s | 0.0% | 0 B |
| node tool-server.cjs | 0.0 s | 0.0% | 0 B |
| simctl | 0.0 s | 0.0% | 0 B |
| system_profiler | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| xcodebuild | 8.0 s | 71.9% | 219.6 MB |
| bun cli.ts | 1.9 s | 17.4% | 239.8 MB |
| snapshot-bridge | 0.8 s | 7.0% | 116.6 MB |
| ps | 0.2 s | 2.0% | 1.8 MB |
| node agent-device | 0.1 s | 1.3% | 61.1 MB |
| simctl | 0.0 s | 0.4% | 20.3 MB |
| node agent-device (exit charge) | 0.0 s | 0.1% | 0 B |
| DTServiceHub | 0.0 s | 0.0% | 16.4 MB |
| caffeinate | 0.0 s | 0.0% | 7.1 MB |
| (clang) | 0.0 s | 0.0% | 0 B |
| (ps) | 0.0 s | 0.0% | 0 B |
| (simctl) | 0.0 s | 0.0% | 0 B |
| other | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| xcodebuild | 14.7 s | 83.0% | 221.6 MB |
| node entry.js | 1.2 s | 6.7% | 85.2 MB |
| node e2e (exit charge) | 0.8 s | 4.3% | 0 B |
| snapshot-bridge | 0.6 s | 3.5% | 116.4 MB |
| node e2e | 0.4 s | 2.4% | 104.8 MB |
| other (exit charge) | 0.0 s | 0.2% | 0 B |
| simctl | 0.0 s | 0.0% | 20.5 MB |
| DTServiceHub | 0.0 s | 0.0% | 16.8 MB |
| (ps) | 0.0 s | 0.0% | 5.0 KB |
| (clang) | 0.0 s | 0.0% | 0 B |
| (pgrep) | 0.0 s | 0.0% | 0 B |
| (plutil) | 0.0 s | 0.0% | 0 B |
| other | 0.0 s | 0.0% | 0 B |
Simulator processes
- wardenexample
- iOS
- AgentDeviceRunnerUITests-Runner
- bun cli.ts
- SimulatorFrameworkBridge-iOS serve
- CoreSimulatorBridge
- other
- warden65.8 s
- Argent63.7 s
- agent-device85.2 s
- tester.army122.6 s
Table
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| wardenexample | 32.1 s | 48.8% | 373.1 MB |
| iOS | 25.4 s | 38.6% | 9.7 GB |
| SimulatorFrameworkBridge-iOS serve | 3.5 s | 5.4% | 185.6 MB |
| bun cli.ts | 2.3 s | 3.5% | 228.3 MB |
| CoreSimulatorBridge | 1.3 s | 1.9% | 84.9 MB |
| ps | 0.7 s | 1.0% | 1.8 MB |
| launchd_sim | 0.3 s | 0.5% | 36.8 MB |
| xpcproxy_sim | 0.2 s | 0.3% | 411.4 KB |
| pkd | 0.0 s | 0.0% | 58.9 MB |
| aslmanager | 0.0 s | 0.0% | 21.2 MB |
| caffeinate | 0.0 s | 0.0% | 7.3 MB |
| (launchctl) | 0.0 s | 0.0% | 0 B |
| other | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| wardenexample | 33.7 s | 52.9% | 374.7 MB |
| iOS | 24.3 s | 38.2% | 9.7 GB |
| bun cli.ts | 3.3 s | 5.2% | 225.2 MB |
| ps | 1.1 s | 1.7% | 1.8 MB |
| CoreSimulatorBridge | 0.5 s | 0.8% | 117.8 MB |
| simctl | 0.4 s | 0.6% | 351.9 KB |
| launchd_sim | 0.2 s | 0.3% | 36.9 MB |
| xpcproxy_sim | 0.1 s | 0.2% | 155.7 KB |
| (wardenexample) | 0.0 s | 0.1% | 1.5 MB |
| pkd | 0.0 s | 0.0% | 59.0 MB |
| aslmanager | 0.0 s | 0.0% | 21.1 MB |
| caffeinate | 0.0 s | 0.0% | 7.2 MB |
| other | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| iOS | 37.6 s | 44.2% | 10.1 GB |
| wardenexample | 36.0 s | 42.3% | 375.2 MB |
| AgentDeviceRunnerUITests-Runner | 10.0 s | 11.7% | 269.7 MB |
| simctl | 0.6 s | 0.6% | 749.3 KB |
| CoreSimulatorBridge | 0.5 s | 0.6% | 81.6 MB |
| launchd_sim | 0.2 s | 0.2% | 37.0 MB |
| xpcproxy_sim | 0.2 s | 0.2% | 257.5 KB |
| launchctl | 0.1 s | 0.1% | 24.8 KB |
| pkd | 0.0 s | 0.0% | 59.1 MB |
| aslmanager | 0.0 s | 0.0% | 21.2 MB |
| (launchctl) | 0.0 s | 0.0% | 0 B |
| (managedappdistri) | 0.0 s | 0.0% | 0 B |
| other | 0.0 s | 0.0% | 0 B |
| Process | CPU-s | Share | RSS avg |
|---|---|---|---|
| wardenexample | 50.8 s | 41.4% | 381.6 MB |
| iOS | 50.7 s | 41.4% | 10.1 GB |
| AgentDeviceRunnerUITests-Runner | 16.4 s | 13.4% | 271.0 MB |
| bun cli.ts | 2.5 s | 2.0% | 264.5 MB |
| CoreSimulatorBridge | 0.6 s | 0.5% | 81.5 MB |
| simctl | 0.6 s | 0.5% | 563.5 KB |
| launchd_sim | 0.4 s | 0.3% | 37.7 MB |
| (launchctl) | 0.1 s | 0.1% | 62.1 KB |
| ps | 0.1 s | 0.1% | 1.8 MB |
| (managedappdistri) | 0.1 s | 0.1% | 68.6 KB |
| xpcproxy_sim | 0.1 s | 0.1% | 119.0 KB |
| launchctl | 0.1 s | 0.1% | 10.4 KB |
| other | 0.0 s | 0.0% | 87.2 MB |
Residue and footprint
Residue is what is left on the machine after teardown. Footprint is how much suite source you maintain and how large the runner binaries are.
| Metric | warden | Argent | agent-device | tester.army |
|---|---|---|---|---|
| Suite source (lines) | 379 | 161 | 158 | 307 |
| Suite source (size) | 17.5 KB | 7.1 KB | 5.4 KB | 13.8 KB |
| Runner binaries on disk | 7.5 MB | 77.8 MB | 6.9 MB | — |
| Leftover processes | 0 | 0 | 0 | 0 |
| Leftover driver apps | 0 | 0 | 1 | 1 |
| Leftover directories | 0 | 1 | 1 | 1 |
Versions and run details
| Framework | Version | Status |
|---|---|---|
| warden | bun 1.3.14 + warden-e2e | ran, all tests passed |
| Argent | 0.30.1 | ran, all tests passed |
| agent-device | agent-device 0.21.31 | ran, 97.5% of tests passed |
| tester.army | e2e 0.20.0 | ran, all tests passed |
- Host
- VirtualMac2,1, Apple M4 Pro (Virtual), 10 cores, 40.0 GB, macOS 26.5.2
- Device
- warden-iphone-17-1 iOS 26.5
- App
- nz.co.delacour.warden.example, 96.4 MB, build import:c283e, sha256 c283eae41c7a
- Tools
- bun 1.3.14, warden 0.3.1
- Run
- 5 runs per framework, 0 retries, load average 2.87 at start
- Report
- docs/bench/ios-2026-10-25.md
Fairness rules
- Same
.appfor every framework, with its sha256 recorded. - Zero LLM calls.
- Same assertions per canonical test id.
- Same retry helpers: dropped first tap, dropped deep link, system alerts.
- The iOS "Open in …?" sheet is answered once per boot by the harness before every framework's prepare (iOS asks it for the first custom-scheme link after a boot, not again).
- Fresh app launch per test,
retries=0. - Frameworks run sequentially with daemons stopped between them, or, on EAS, each on its own worker and simulator.
caffeinate -dimsheld for the run, host otherwise idle, load average recorded.- Warm medians exclude rep 1.
- Machine-readable reporters only.
- A framework with any failing test never sets the pace, so it is never the fastest.
Caveats per framework
| Framework | Gap |
|---|---|
| warden | Copied from the example app, retries written with warden's own options: tap({ until }), a deep-link retry, systemAlerts: "dismiss". The hand-rolled helpers it replaced ran the published headline numbers; the idiomatic run is reported beside them. |
| Argent | Retries are nested when: guards capped at two retaps. Flow selectors read only the app's view hierarchy, never SpringBoard, so the flow cannot answer the "Open in …?" sheet itself. A checkbox's checked state is not readable, so the remaining-count assertion covers the effect. |
| agent-device | .ad scripts have no retry or conditional, only wait and press, so the deep-link script cannot answer a sheet that may or may not show. Needs a runner prepare step and a daemon. On the 2026-10-25 run its fill read back an empty field once (run 1, sign-in/field-errors), so it is not clean and never sets the pace. |
| tester.army | Has its own copy of the hand-rolled helpers warden used before. Runs with a private agent-device state dir and Node 24 on PATH. |
Each run is a snapshot on one machine class with few warm repetitions: read the ratios as a guide, not a verdict. The full Markdown report for each run is in docs/bench/ in the repo; the EAS runs also publish every worker's artifact on their run page.