Headless e2e
How warden runs simulators without a window, and the --headless switch that guarantees it even with Simulator.app open.
Headless mode keeps simulator windows closed during a run, even with Simulator.app open; use it when you don't want windows popping up or tiling over your screen.
warden e2e <suite> --headlessA headless run shows no simulator window and never moves your own windows.
How headless e2e works
- Boot. warden boots iOS simulators with
xcrun simctl bootstatus <udid> -b(which boots a shut-down sim;simctl bootis the fallback). Simulator.app is only a viewer: the simulated iOS (launchd_sim, SpringBoard, your app) runs inside CoreSimulator whether or not a window exists. warden never launches Simulator.app. Android emulators start withemulator -no-window -read-only, so they are always headless. - Render. The app draws into the device's own framebuffer.
simctl io screenshotand the driver's accessibility snapshots read that, so screenshots and failure evidence need no window. - Drive.
warden e2eruns the specs withbun testand@delacour/warden-e2e, whose iOS driver talks towarden-simd(iOS driver helper). The helper sends touches and keys to the simulator through CoreSimulator and reads the accessibility tree through a guest that runs inside it, so no Simulator.app window is needed and there is no runner app to install on the sim. Links and installs go throughsimctl. Nothing touches a window. - Where windows come from. If Simulator.app is already open, it attaches a window to every booted device, warden's included. Without
--headless, every iOS claim then tiles those windows (warden arrange, off withWARDEN_ARRANGE=0). A reused sim that you already had open keeps its window. - Watching a run.
open -a Simulator(Xcode ≤ 26) attaches windows to the booted sims without touching leases; on Xcode 27 see Device Hub. ⌘W closes a window. Quitting Simulator.app does not always keep the devices booted.
What --headless does
- After the claim, warden closes the Simulator.app windows of the devices this run leased, instead of tiling them. A window is matched by its device name (the title before
– iOS …). Windows of other devices are never touched, and a reused sim that was open before has its window closed too. warden e2e/warden batchclose them again just before the first job, after Metro,serveandsetupare up, because a window can attach late (a slow boot, or Simulator.app opened in the meantime).- By default Simulator.app shuts a device down when you close its window. For the close only, warden switches on Simulator's "keep running" setting (
defaults write com.apple.iphonesimulator DetachOnWindowClose -bool true). The window detaches and the device stays booted. Afterwards warden puts the setting back exactly as it was: unset,falseortrue. - If a device still shuts down, warden waits for it to finish shutting down, boots it again without a window (
simctl boot+bootstatus -b), closes any window Simulator.app reattached, and prints a warning. - Simulator.app is never launched. When it isn't running, there is nothing to do.
- Headless never fails a run. A missing Accessibility permission or an osascript error is a yellow warning.
- Serve, setup and spec processes get
WARDEN_HEADLESS=1, so your own tools can honour it. On a run that isn't headless,WARDEN_HEADLESSis removed from their env. e2e-report.jsonandbatch.jsonrecord"headless": true | false.- Android: no change, because emulators already run with
-no-window.
Closing windows uses System Events, so your terminal needs Accessibility access (System Settings → Privacy & Security → Accessibility), the same as warden arrange.
Xcode 27: Device Hub
Xcode 27 removed Simulator.app. Its viewer is Device Hub (Xcode.app/Contents/Applications/DeviceHub.app, bundle id com.apple.dt.Devices). Unlike Simulator.app it opens no window when a sim boots, so warden asks for them.
- warden looks for the running viewer's process:
Simulatorfirst, thenDeviceHub. It doesn't go by the Xcode version, so a Mac with Xcode 26 and 27 side by side still gets Simulator.app windows tiled or closed. - Not headless, Device Hub already running: every sim the claim booted gets its own Device Hub window (
open -g devices://device/open?id=<udid>, your frontmost app stays in front), then the windows are tiled like Simulator.app's: name order, left to right, overlapping when the row is wider than the screen. Device Hub windows can't be resized either. Sims that were already running keep whatever window you gave them.warden arrangetiles on demand;warden arrange --open <udid…>also reopens those sims' windows. - Device Hub not running: warden never launches it (opening a
devices://link would), so there are no windows, exactly as with Xcode 26 and Simulator.app closed. - Headless: warden opens no Device Hub window, never launches it and writes no
DetachOnWindowClose. Device Hub opens none by itself, so there is nothing to close. - Don't quit Device Hub during a run, headless or not. A normal quit shuts down every booted simulator, including ones you never opened there. Killing it leaves them booted but a sim it had open can drop warden's input (
E_HID).warden e2ehandles either once: it reboots the sim, reopens its window and tiles again when the run isn't headless, then reruns the test. - Booting is unchanged:
simctl bootbrings a device up without a window on every Xcode, and the e2e driver never needs one. - Don't tap or type into a leased sim from Device Hub during a run. Device Hub reportedly takes over the device's touch input after you interact with it there, and other input (warden's taps included) can be dropped until the sim reboots, even after Device Hub quits.
Turning it on
| Where | Applies to |
|---|---|
--headless / --no-headless | claim, run, batch, e2e; dev takes --headless only |
e2e.<suite>.headless | that suite |
batches.<name>.headless | that preset |
top-level headless | every suite and preset that doesn't set its own |
WARDEN_HEADLESS=1 | every claim, run, batch, e2e that nothing above decides |
The first one that is set wins: CLI flag, then suite / preset, then top-level, then WARDEN_HEADLESS. Otherwise headless is off.
export default defineConfig({
headless: true, // every suite and preset
e2e: {
mobile: { /* … */ }, // headless (top-level)
demo: { headless: false, /* … */ }, // keeps its windows
},
});warden dev is headless only with an explicit --headless. It ignores the top-level headless and WARDEN_HEADLESS, since dev is usually something you watch.
Watching a headless run
open -a Simulator # Xcode ≤ 26: windows appear for the booted sims; leases are untouched
open "devices://device/open?id=<udid>" # Xcode 27: Device Hub shows that simA headless run closes Simulator.app windows only at claim time and before the first job. Windows you open later stay open until you close them (⌘W). On Xcode 27, look but don't touch (see Device Hub).
More: Run e2e suites, configuration, environment.
Profile an app (warden perf)
warden perf - CPU, memory, frames, startup, JS, React and native profiling of an app on a warden simulator or emulator, as one combined session with a ranked report, drill-down queries, before/after diffs and e2e budgets.
App builds
Install the native build matching your Expo fingerprint — from the device, cache, EAS or a local build.