[ Documentation ]
Detox Driver

Detox Driver

Drop-in replacement for import { by, element, expect, waitFor, device } from 'detox'. Your existing Detox specs run against Peach’s canvas with no native build.

Running your suite

You don’t wire this up by hand. peach detox runs your own Jest, forcing a preset that remaps ^detox$ to the Peach driver, so import ... from 'detox' resolves to Peach without touching your test files:

terminal
peach detox # discover e2e/ (or test/e2e, detox/) and run
peach detox e2e/login.test.ts # run specific files
peach detox init # scaffold rnx-detox.config.cjs + e2e/example.test.ts
peach detox --headed # watch it run in a visible browser
peach detox -t "login" # jest --testNamePattern (also --grep)

It launches a simulator for the run (pass --no-launch to attach to one you already have) and picks up config from --config <path>, else rnx-detox.config.cjs / jest.config.cjs / jest.config.js, else a generated temp config. Your Jest version, transforms, and setup files are preserved.

Matchers

by.id(testID) // match by testID
by.text(text) // match by text content
by.label(label) // match by accessibility label
by.role(role) // match by accessibility role
by.type(componentType) // match by component type

Element actions

element(matcher).tap(point?) // optional {x,y} offset
element(matcher).multiTap(times)
element(matcher).longPress(duration?) // default 1000ms
element(matcher).longPressAndDrag(duration, startX, startY, target, endX, endY)
element(matcher).typeText(text)
element(matcher).replaceText(text)
element(matcher).clearText()
element(matcher).scroll(pixels, direction) // 'up' | 'down' | 'left' | 'right'
element(matcher).scrollTo(edge) // 'top' | 'bottom' | 'left' | 'right'
element(matcher).swipe(direction, speed?, percentage?)
element(matcher).pinch(scale, speed?, angle?) // angle in radians
element(matcher).rotate(radians, speed?)
element(matcher).takeScreenshot(name)
element(matcher).getAttributes()
element(matcher).atIndex(i) // peach resolves one node per testID

Assertions

expect(element(matcher)).toExist()
expect(element(matcher)).toBeVisible() // 75% visible-area threshold
expect(element(matcher)).toBeNotVisible()
expect(element(matcher)).toHaveText(text) // exact or substring
expect(element(matcher)).toHaveId(id)
expect(element(matcher)).toHaveLabel(label)
expect(element(matcher)).toHaveAccessibilityRole(role)
expect(element(matcher)).toBeEnabled()
expect(element(matcher)).toHaveValue(value)
// all support the .not prefix

toBeFocused, toHaveSliderPosition, and toHaveToggleValue exist for API compatibility but currently only assert existence.

waitFor

waitFor(element(matcher)).toExist().withTimeout(ms)
waitFor(element(matcher)).toBeVisible().withTimeout(ms)
waitFor(element(matcher)).toBeNotVisible().withTimeout(ms)
waitFor(element(matcher)).toHaveText(text).withTimeout(ms)
waitFor(element(matcher)).toHaveValue(value).withTimeout(ms)
waitFor(element(matcher)).whileElement(by.id('list')).scroll(200, 'down')

The waitFor chain intentionally exposes a narrower set of conditions than expect.

Device

device.launchApp(opts?)
device.reloadReactNative()
device.terminateApp()
device.openURL({ url })
device.takeScreenshot(name)
device.takeScreenshotFast(name) // no stability wait
device.startBridgeRecording(opts?)
device.stopAndSaveBridgeRecording(outPath)
device.getPlatform() // 'ios' | 'android'
device.pressBack() // android only

No-ops kept for source compatibility (there is no native process in the browser): installApp, uninstallApp, shake, setLocation, setURLBlacklist, sendToHome.