Your Playwright tests and AI agents drive it with the browser tools they already use. No Appium, no new tool.
Stock Playwright driving a real iOS Simulator from another machine — the same DOM an AI agent drives too.
Get started · What it does · Using it · Docs · Known limits
Needs a Mac with Xcode (and the Android SDK for Android) — devicedeck doctor says what's missing.
# 1. Install, then start DeviceDeck with your app build (.app for a simulator, .apk for an emulator)
curl -fsSL https://open.devicelab.dev/install/devicedeck | bash
export PATH="$HOME/.devicedeck/bin:$PATH" # or open a new terminal
devicedeck --app path/to/MyApp.app
# 2. Give Claude Code the browser tool and DeviceDeck's plugin
claude mcp add playwright npx @playwright/mcp@latest
claude plugin marketplace add devicelab-dev/DeviceDeck
claude plugin install devicedeck@devicedeck-marketplace
# then start claude: /mcp should list both playwright and devicedeck
# 3. Ask Claude — it finds your app, boots a simulator, launches it, drives it, writes the test
# "Write a Playwright test that logs in to my app"
# 4. Run it
npx playwright testNeed a simulator build of your app, or the step-by-step? See docs/getting-started.md.
Using Gemini CLI, Codex, VS Code or Cursor? Only step 2 changes — see Other agents.
Prefer to use devices by hand? Open the console at http://127.0.0.1:8787.
In a Playwright project, add DeviceDeck as a dev dependency, so every machine and CI job runs the
same version — no install script, nothing downloaded after npm install:
npm install --save-dev devicedeck
npx devicedeck --app path/to/MyApp.appOr try it without installing: npx devicedeck --app path/to/MyApp.app. For the Claude plugin, which
starts devicedeck mcp itself, install it globally so it is on your PATH: npm install -g devicedeck. Playwright can also start it for you, in playwright.config.ts:
webServer: { command: 'npx devicedeck --app path/to/MyApp.app', url: 'http://127.0.0.1:8787', reuseExistingServer: true },Share your simulators. Run devicedeck on a Mac and everyone on your network can drive its
simulators and emulators from their own browser — tap, type, and Inspect the native UI — with
no Xcode, no Android Studio, and no device of their own.
Automate them with the tools you already use. The app's native UI is served as an ordinary web page, so Playwright, Cypress and Puppeteer tests drive it by selector, and your AI agent drives it through Playwright MCP — the same browser tool it uses for the web. Nothing mobile-specific to learn.
Record a flow. Use the app by hand in the console and DeviceDeck writes it down as a Maestro flow, with durable selectors, ready to review and replay — unchanged on real devices at devicelab.dev.
One binary behind all three — nothing to fork, no Xcode project to open.
The native UI tree is mirrored as real DOM — accessibility ids become data-testid, element
types become ARIA roles — so stock selectors drive it, whether from an agent or a test:
await page.goto(`/device/${udid}?app=dev.devicelab.testhive`);
await page.getByRole('textbox', { name: 'Username' }).fill('devicelab');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.getByText('Hello, devicelab!')).toBeVisible();A single Go binary streams the simulator or emulator to the browser and serves that DOM — the same page and selectors drive iOS and Android alike.
The agent drives the device the way it drives any web page: it snapshots the page, reasons over the tree, and acts by ref — no coordinates, no vision model. Here it picks the right Add among five identical ones:
In Get started, steps 1, 3 and 4 are the same; only step 2 changes.
| Agent | Step 2 |
|---|---|
| Gemini CLI | gemini extensions install https://github.com/devicelab-dev/DeviceDeck |
| Codex CLI | codex mcp add playwright -- npx @playwright/mcp@latestcodex mcp add devicedeck -- devicedeck mcpnpx skills add devicelab-dev/DeviceDeck |
| VS Code / Copilot | code --add-mcp '{"name":"playwright","command":"npx","args":["@playwright/mcp@latest"]}'code --add-mcp '{"name":"devicedeck","command":"devicedeck","args":["mcp"]}'npx skills add devicelab-dev/DeviceDeck |
| Cursor, and others | add the same two MCP servers in the agent's settings, then npx skills add devicelab-dev/DeviceDeck — docs/agents.md |
Each agent gets the same three pieces: Playwright MCP to drive the device page, DeviceDeck's device tools to boot devices and launch apps, and its skills for writing tests, recording flows and triaging failures.
Point Playwright, Cypress or Puppeteer at http://127.0.0.1:8787/device/{udid}?app={bundleId},
run one worker per device, and select by data-testid (the app's accessibility id) or by role.
To record a test instead, or start from a template, see docs/testing.md.
The console lists every simulator, emulator and --app build on the Mac. Pick a device to boot and
stream it, Launch an app onto it, and drive it with your mouse and keyboard; Inspect shows
each element's id, role and text — the selectors your tests use. Teammates open the network address
devicedeck prints. Guide: docs/console.md.
Press Record, use the app, press Stop: DeviceDeck writes it as a Maestro flow with graded, durable selectors — replayable with maestro-runner and unchanged on real devices at devicelab.dev. Guide: docs/flows.md.
With npm: npm install --save-dev devicedeck in a project, or npm install -g devicedeck — see
Or install with npm. The host is a Mac; installing it on Linux (a CI job that
drives a remote Mac, say) is harmless.
The install script puts DeviceDeck in ~/.devicedeck and adds its bin folder to your PATH — no
sudo, and nothing else to install: the Android driver ships inside the binary. Pin a version with
curl -fsSL https://open.devicelab.dev/install/devicedeck | bash -s -- --version 0.1.0.
Or download an archive from Releases and run
it in place (the two sidecars sit beside the binary in bin/):
tar xzf devicedeck-<version>-darwin-arm64.tar.gz
./devicedeck-<version>-darwin-arm64/bin/devicedeckOr build from source (needs the Xcode toolchain for the Swift sidecars): make sidecar && make build.
devicedeck on its own starts the console at http://127.0.0.1:8787. --app takes .app simulator
builds, .apk files, or a folder of them, and installs each the first time it is launched on a
device. On start it prints the console link (and the network address teammates use), your apps, how
to connect Claude Code, and any missing tools with how to fix them.
Everything DeviceDeck writes lives in ~/.devicedeck (set DEVICEDECK_HOME to move it). To
uninstall, delete that folder and the # DeviceDeck line from your shell profile.
- A Mac to host. iOS Simulators,
simctl/CoreSimulator and the Swift sidecars are macOS-only, so the machine that runs the devices is a Mac. Clients can be any OS: the surface is a web page, so people, tests and agents drive it from Linux, Windows or another Mac. - Xcode with at least one iOS Simulator runtime — iOS 26.2 or newer is strongly recommended; on 18.6 the simulator's render server crashes under repeated capture.
- For Android: the Android SDK, with
adbandemulatoronPATH, and at least one virtual device.
devicedeck doctor checks all of this and says how to fix anything missing.
Early release. Both platforms drive end to end: Playwright logs into and checks out of TestHive on
iOS and Android, an agent writes those tests itself through Playwright MCP, and the console drives any
device by hand. click() and fill() return only once the device has acted, and a filled value is
read back from the device — on iOS and on Android's masked password fields. It is an ordinary web
page, so other browser drivers (Cypress, Puppeteer) see the same DOM, but this release is tested with
Playwright.
It has run on a handful of Macs so far, so expect some first-contact problems — please open an issue with the log folder it prints. See Known limits.
Simulators and emulators only — no real hardware, no camera, biometrics or carrier. Within that, the ceiling is physics, not an artificial limit.
- The server is unauthenticated. It listens on all interfaces (
0.0.0.0:8787) by default, so anyone on your network can view and drive your devices. That suits a trusted office or home network; on shared Wi-Fi rundevicedeck --addr 127.0.0.1:8787to keep it to this Mac. There is no access control yet — do not put it on the open internet as-is. - A driver that dies without closing its connection holds its device for up to half a minute, until a missed ping releases it — see docs/testing.md.
- Android video is ~18 fps and heavier than iOS. The emulator's gRPC screenshot stream offers no video codec, so every frame is a full PNG rather than an H.264 delta. Emulators DeviceDeck boots run headless, because macOS throttles an occluded window — and the emulator's window is occluded exactly when you are watching the browser.
- A freshly launched app swallows touches for about a second after its screen is already in the
accessibility tree.
POST /app/launchwaits this window out, so a test that launches through it can act at once; a raw terminate-and-launch outside it cannot. - A raw relaunch does not reset app state — a native app stays logged in across
terminate-and-launch.
POST /app/launchwipes the app's data first by default, starting at a first-run screen; pass?reset=noto resume where it was left. - Two-finger gestures are dropped on Android. They work on iOS; the Android driver has no mapping for them, so they are discarded rather than guessed at.
Every command, flag, environment variable and HTTP endpoint: docs/cli-reference.md.
devicedeck doctor checks the tools DeviceDeck needs: Xcode, the iOS runtime, adb, an Android
emulator, Node.js, Claude Code and maestro-runner.
Every run writes a folder under ~/.devicedeck/logs (the path is printed at startup):
devicedeck.log with every request, device event and tool call, runner.log from the device
driver, one log per sidecar and device, and crash.log if the process panics. The terminal shows
only what needs attention; DEVICEDECK_LOG=info or debug shows more there too. The last 20 runs
are kept — attach the folder to an issue.
Claude wrote a web test, or says the folder is empty. It has no DeviceDeck tools: run /mcp in
Claude. If devicedeck is missing, the plugin is not installed — run all three commands of step 2
(marketplace add alone is not enough), then start Claude again. If it is listed but failing, the
devicedeck binary is not on the PATH Claude was started from. Also check that devicedeck is
running with your --app build in another terminal.
Apache License 2.0 — see LICENSE.
maestro-runner for the device drivers, Apache-2.0.
The Swift sidecars derive from baguette (Apache-2.0) and
tapflow (MIT); ATTRIBUTION.md records what
was reused and where it lives.
Built by devicelab.dev
