1. Detox Setup & Config
npm install detox --save-dev
npx detox init
module.exports = {
testRunner: { args: { config: 'e2e/jest.config.js' } },
apps: {
ios: {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
},
devices: {
simulator: { type: 'ios.simulator', device: { type: 'iPhone 15' } },
},
configurations: {
'ios.sim.debug': { device: 'simulator', app: 'ios' },
},
};
Unlike Jest's mocked native modules, Detox drives a real, fully built app
— which means it needs a real native build to exist first (the same
expo run:ios/run:android build Week 12's native module
also required), not just the JS bundle.
2. Writing an E2E Test
describe('Sign in flow', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('signs in and lands on the feed', async () => {
await element(by.id('email-input')).typeText('user@example.com');
await element(by.id('password-input')).typeText('correct-password');
await element(by.id('sign-in-button')).tap();
await expect(element(by.text('Feed'))).toBeVisible();
});
it('shows an error on wrong credentials', async () => {
await element(by.id('email-input')).typeText('user@example.com');
await element(by.id('password-input')).typeText('wrong-password');
await element(by.id('sign-in-button')).tap();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
});
by.id matches a component's testID prop, set deliberately
for testing — more stable than matching visible text, which changes with copy edits
and localization. Reserve by.text for assertions on the actual
user-visible outcome.
3. Waiting for Async UI
await element(by.id('load-posts-button')).tap();
await waitFor(element(by.id('posts-list')))
.toBeVisible()
.withTimeout(5000);
Detox has its own automatic synchronization with idle timers, animations and
network requests in most cases — but a genuinely slow API call, a long animation,
or a WebView still needs an explicit waitFor(...).withTimeout(), the
Detox equivalent of Week 16's waitFor from RNTL. The most common
source of a flaky Detox suite is skipping this and asserting immediately after an
action that triggers async work.
4. Running Detox in CI
Detox in CI has requirements beyond a local run: a machine that can actually launch a simulator or emulator (macOS runners for iOS; a Linux runner with KVM, or a macOS runner, for Android), the app pre-built as a CI step before tests run, and meaningfully longer job times than a unit test suite — a full E2E run genuinely takes minutes, not seconds.
- run: npx detox build --configuration ios.sim.debug
- run: npx detox test --configuration ios.sim.debug --cleanup
Given the cost, most teams run the full unit/component suite (Week 16) on every push, and reserve the E2E suite for pull requests into a main branch, or a nightly scheduled run — a pattern worth remembering going into Week 19's CI/CD lesson.
5. Hands-on Exercise
Write a Detox E2E flow
Drive the auth-gated app from Week 5 through Detox, end to end.
Requirements:
- Set up Detox for at least one platform (iOS simulator or Android emulator), including a working build configuration.
- Add
testIDprops to the sign-in form and key screens from Week 5's exercise. - A Detox test that signs in and asserts the main tabs become visible.
- A second test asserting an error message appears on invalid credentials.
- At least one
waitFor(...).withTimeout()covering a screen that loads data asynchronously, with a short comment explaining why it was needed there and not elsewhere.
If tests pass individually but fail when run together, check beforeEach — a missing device.reloadReactNative() (or equivalent app-state reset) between tests means state from one test (like being signed in) leaks into the next, which expects to start from a clean, signed-out state.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why does Detox need a real native build of the app, where Week 16's Jest tests didn't?
Why does Detox need a real native build of the app, where Week 16's Jest tests didn't?
Detox drives the actual compiled app running on a real simulator/emulator, interacting with real native UI — there's no JS-only environment to run it in the way Jest runs in Node. Jest tests instead run components in a simulated environment with native modules mocked out entirely, which is faster but doesn't exercise the real native build.
Q2
Why prefer by.id (testID) over by.text for locating elements to interact with in a Detox test?
Why prefer by.id (testID) over by.text for locating elements to interact with in a Detox test?
A testID is a stable identifier set deliberately for testing, so it survives copy changes, localization, or minor UI rewording untouched. Matching by visible text ties the test to exact wording, which breaks the test on changes that have nothing to do with actual functionality — text matching is better reserved for asserting on the outcome the user would actually see.
Q3
What causes a Detox test to become flaky when asserting right after an action that triggers a network call?
What causes a Detox test to become flaky when asserting right after an action that triggers a network call?
Detox's automatic synchronization handles many cases, but a genuinely slow request can still resolve after the assertion runs, so the element being asserted on isn't there yet — intermittently, depending on network conditions. Wrapping the assertion in waitFor(...).withTimeout() polls until the element appears (or a real timeout is hit) instead of asserting at a single, unreliable moment.
Q4
Why do most teams run the full E2E suite less often (e.g. only on merges to main, or nightly) than the unit/component suite?
Why do most teams run the full E2E suite less often (e.g. only on merges to main, or nightly) than the unit/component suite?
A real E2E run needs an actual simulator/emulator and a full native build, which takes meaningfully longer than a mocked unit-test run — running it on every single push would slow down every contributor's feedback loop for a cost that isn't worth paying that often. The fast unit/component suite catches most regressions cheaply on every push; E2E is reserved for the checkpoints where a native-level check is worth the wait.