Detox Driver

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

Running your suite

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

terminal

sootsim detox # discover e2e/ (or test/e2e, detox/) and run
sootsim detox e2e/login.test.ts # run specific files
sootsim detox init # scaffold sootsim-detox.config.cjs + e2e/example.test.ts
sootsim detox --headed # watch it run in a visible browser
sootsim 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 sootsim-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) // sootsim 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.

Ready to build?

Run your React Native app in the browser. No simulators, no native toolchain, no waiting.

curl -fsSL https://sootsim.com/install.sh | sh