The device is a web page at /device/{udid}?app={bundleId}, so Playwright, Cypress, or Puppeteer
drive it with ordinary selectors — the app's accessibility ids become data-testid, roles and
labels become ARIA. Nothing mobile-specific.
You don't need a mobile test suite to start:
- Let your agent write it — the four steps in the README.
- Record it. Playwright's playwright-cli records
what you do on the device page (
recording-start…recording-stop) and writes ordinary locators — here is a recorded login. Add one wait for the device to echo typed text before submitting (see Drive it below). - Start from an example — copy a project from
examples/and point it at your app.
-
Point your framework's
baseURLathttp://127.0.0.1:8787. -
Run one worker per device — see below.
-
Launch the app fresh in a fixture so the first action lands:
await request.post(`/api/devices/${UDID}/app/launch`, { data: { app: APP } });
It blocks until the app is taking input — absorbing the boot, the engine warm-up, and the post-launch tap-swallow window. It clears the app's data first, so the test starts at a first-run screen; post to
/app/launch?reset=noto resume where the app was left instead. -
Let Playwright start DeviceDeck, if you installed it with npm:
webServer: { command: 'npx devicedeck --app path/to/MyApp.app', url: 'http://127.0.0.1:8787', reuseExistingServer: true },
await page.goto(`/device/${UDID}?app=${APP}`);
await page.getByTestId('username-input').fill('devicelab');
await page.getByTestId('login-button').click();
await expect(page.getByTestId('cart-button')).toBeVisible();- Typing:
locator.fill(text). It returns once the device holds the value, so there is nothing to wait for before submitting. Avoidkeyboard.type: key by key it raises Android's soft keyboard, and the next tap can be spent closing it. The device's own read-back of a field is ondata-dd-device-value(a secure field reports bullets). - The device in a spec: read it from an environment variable (
DEVICEDECK_UDID) rather than hard-coding the UDID of your simulator, so the spec runs on a teammate's Mac and in CI. - First render waits on the tree-engine warm-up, so give the first selector ~30s.
- Native gestures a DOM event can't express, on
window.devicedeck:page.evaluate(() => devicedeck.gesture('home'))— alsoswipe,button,keyandscreenshot(); see behaviors. The HTTP equivalents are in the CLI reference.
A device takes one driver at a time: two clients tapping at once would interleave into nonsense, so
a second one is refused and told who holds the device. Test runners go parallel by default, so set
one worker per device (workers: 1 in Playwright) — and to run in parallel, boot more devices and
give each worker its own. A tab left open on the device in the console counts as a
driver too.
Runnable projects, each with a login + checkout journey:
examples/playwright · examples/cypress ·
examples/puppeteer. They drive the same DOM; only the idiom differs —
Playwright auto-waits, Cypress retries via .should(), Puppeteer waits manually.