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
peach detox e2e/login.test.ts
peach detox init
peach detox --headed
peach detox -t "login"
The driver creates its own browser context. --no-launch skips the
running-shell reachability check; it does not attach to an existing sim.
The runner picks up config from --config <path>, else
peach-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)
by.text(text)
by.label(label)
by.role(role)
by.type(componentType)
by.text('Welcome') matches the full text exactly. Use a regular expression
for partial text or case-insensitive matching. by.id, by.text, and
by.label accept regular expressions:
by.text(/Welcome/)
by.id(/^login-/)
by.label(/^sign in$/i)
The i, m, and s flags are supported. Matching always uses Unicode;
g and y are ignored, so repeated lookups do not advance the expression.
#Element actions
element(matcher).tap(point?)
element(matcher).multiTap(times)
element(matcher).longPress(duration?)
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)
element(matcher).scrollTo(edge)
element(matcher).swipe(direction, speed?, percentage?)
element(matcher).pinch(scale, speed?, angle?)
element(matcher).rotate(radians, speed?)
element(matcher).takeScreenshot(name)
element(matcher).getAttributes()
element(matcher).atIndex(i)
#Assertions
expect(element(matcher)).toExist()
expect(element(matcher)).toBeVisible()
expect(element(matcher)).toBeNotVisible()
expect(element(matcher)).toHaveText(text)
expect(element(matcher)).toHaveId(id)
expect(element(matcher)).toHaveLabel(label)
expect(element(matcher)).toHaveAccessibilityRole(role)
expect(element(matcher)).toBeEnabled()
expect(element(matcher)).toHaveValue(value)
toBeFocused checks focus. toHaveSliderPosition and toHaveToggleValue
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)
device.startBridgeRecording(opts?)
device.stopAndSaveBridgeRecording(outPath)
device.getPlatform()
device.pressBack()
device.setLocation(latitude, longitude)
No-ops kept for source compatibility (there is no native process in the
browser): installApp, uninstallApp, shake,
setURLBlacklist, sendToHome.