Skip to main content
@e2edev/agent-device drives iOS simulators and Android emulators. Devices share the agent, screen, and expect APIs with browser targets, but app setup follows the platform: a device attempt starts in its configured app instead of opening a URL. This page covers setup, the device fixture, and the operations that differ from a browser.

Set up

You need Xcode with an iOS simulator runtime, or the Android SDK with an emulator. Run the doctor once before handing the target to the runner:
Then scaffold, choosing Mobile (iOS/Android) in the wizard:
init defaults to iOS on macOS and Android elsewhere, pins the Settings app so the first run works on any machine, and sets one worker. There is no APP_URL; a device target declares no URL.
e2e.config.ts
tests/example.e2e.ts
The devices boot before the run’s clock starts. On a CI runner, where a cold boot takes a minute or more, that time is not charged to launchTimeout.

Point at your app

Replace Settings with your app’s bundle id or package name. app is opened fresh at the start of every attempt and unlocks app.restart() and app.clearState(). To install a build first, add appPath:
Every optional value accepts undefined, so an environment variable passes straight through. Without app, the engine observes whatever is in the foreground and the model opens apps with the open_app tool. Pin app whenever you can: it gives every attempt the same starting screen, which the trace cache needs to replay.

Several devices

One engine per device family; a config declares as many as it has devices:
e2e.config.ts
A deterministic check that names a platform label is scoped with platforms: ['ios'] or platforms: ['android']; everything else runs on both. To split a target’s files across several simulators, pass a pool and let workers be at least its size. Each entry is one worker, and worker n drives the nth device. With no device at all, every booted device of the platform is eligible for the pool, up to the run’s worker slots. Four booted simulators can run four files at a time when workers is at least four. With none booted, agent-device boots one and the target uses one worker.
The engine declares one worker per device, so a device target never has two workers driving one simulator, whatever workers allows.

Writing a device test

screen queries work as on the web. iOS Button is button, TextField is textbox, Cell is listitem, Switch is switch with checked. Android TextView is text, EditText is textbox, RecyclerView is list. Element identifiers surface under getByTestId.
Text queries answer with the innermost match, so a host view that echoes its child’s label counts once. A tap on a switch lands on its innermost control. selectOption, setInputFiles, and scrollIntoView are UNSUPPORTED_CAPABILITY on a device; swipe instead.

The device fixture

device is deterministic device management, recorded as device.<method> steps and never involving a model: network and airplane mode, permissions, location, appearance, orientation, biometrics, install and open apps, home and back, system alerts, the keyboard, the clipboard. device.locator('...') mints a locator from an agent-device selector for nodes the closed screen vocabulary cannot name:
device.installApp puts a build on the device from inside a test, for upgrade and fresh-install paths:
Every method is in the agent-device reference.

Agent tools

The grammar verbs (tap, type, scroll) come from the engine and need no tool. For app switching, free-form swipes, typing into editors that hide the focused field, and system alerts, hand the agent the device tool pack:
e2e.config.ts
Pass every device engine the config declares. The pack is scoped to their platforms and dispatches each call to the engine whose attempt is running. A mutating tool call is a replay gap, so prefer the grammar inside a step. The read-only screenshot tool creates no gap.

What differs from the web

agent-device reference

Every option, the device fixture, and the tool pack.

Writing tests

Goals, checks, and the deterministic APIs, on any platform.