In a Detox test, why is no sleep() needed between tapping a Draw button and asserting the results screen, and what is Detox waiting for?
answer
- gray box, not black box
- the app reports when it is idle
- network, timers, animations, layout
- JS thread and main queue
- every action and expectation waits
basics
~20 sDetox is gray-box: code inside the app tracks in-flight network requests, short timers, animations, UI layout, the main queue and the React Native JS thread, and Detox runs each action or expectation only once all of them are idle.
solid answer
~50 sDetox runs a native client inside the app under test, so it can see what the app is doing instead of guessing from the outside. Before every action and every expectation it waits until the app is **idle**: no in-flight network requests, no pending work on the main thread or its dispatch queue, no pending UI layout, no running animations (including React Native `Animated` and Reanimated), no short `setTimeout` timers, and no pending work on the React Native JavaScript and native-modules threads. After `await element(by.id('draw-button')).tap()`, the app fetches the draw, animates and navigates; Detox's next `expect(element(by.id('results-list'))).toBeVisible()` simply waits for all of that to settle. That is why sleeps are unnecessary and harmful: they guess a duration, while synchronization waits for the actual work. The price is strictness: if something never goes idle, the test blocks instead of proceeding.
code
javascript · 11 linesdescribe('lottery draw', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
it('shows results after a draw', async () => {
await element(by.id('draw-button')).tap();
// no sleep: Detox waits for the request, animation and layout to finish
await expect(element(by.id('results-list'))).toBeVisible();
});
});go deeper
Recall that Detox waits for the app to be idle before each action and expectation, so tests do not need sleep calls.
Name the resources Detox tracks (network, timers, animations, layout, main queue, JS thread) and explain the 1.5 second setTimeout rule.
Explain the strictness trade-off: an app that never idles blocks the test, and that blockage usually points at a real app bug.
Weigh gray-box synchronization against black-box tools for the team's e2e strategy, including the cost of instrumenting the app under test.
## Black box versus gray box End-to-end tests drive a real app on a simulator or emulator. The hardest part is **timing**: after a tap, the app may call a server, run a transition and render new data, and each step takes a different amount of time on every run and every machine. A **black-box** tool sees only the screen, so tests fill the gaps with `sleep()` calls or polling waits and become flaky or slow. **Detox** takes a **gray-box** approach. When a test starts, a Detox native client is integrated into the app under test. It connects to the test process through a small WebSocket server, executes commands (taps, scrolls, typing), evaluates expectations on the device, and, crucially, **monitors what the app is busy with**. ## What Detox waits for Detox's documentation lists the operations it synchronizes with automatically: | Resource | What "busy" means | |---|---| | Network | requests in flight that have not been answered | | Main thread | pending native work on the main dispatch queue and operation queue | | UI layout | pending layout operations, including React Native's layout step | | Timers | pending timers, with special handling of JavaScript `setTimeout` | | Animations | running animations and transitions, including React Native `Animated` and Reanimated | | JavaScript thread | pending operations on the React Native JS thread | | Native-modules thread | pending native module calls on their dedicated thread | In apps that still have the old React Native bridge, Detox also watches the bridge's message traffic; on a New Architecture app, the only architecture in current React Native, there is no bridge to watch. The timer rule has a detail worth knowing: Detox waits only for `setTimeout` calls of up to **1.5 seconds** and ignores `setInterval`, so a long timer or a repeating interval does not block tests. ## What happens on each step Take the lottery app's core flow: ```js await element(by.id('draw-button')).tap(); await expect(element(by.id('results-list'))).toBeVisible(); ``` 1. Detox waits for the app to be idle, then taps the button. 2. The app starts a network request for the draw, runs a short reveal animation, and navigates to the results screen. 3. Before evaluating the expectation, Detox waits again until the request has completed, the animation has finished, layout is done and the JS thread has no pending work. 4. Only then does it check that `results-list` is visible, on the device. No step in the test says how long to wait, and the test is as fast as the app allows. ## Why a sleep is worse - **Too short** on a slow CI machine: the element is not there yet, and the test fails intermittently. - **Too long** everywhere else: every run pays for the worst case. - **Wrong signal**: a sleep waits for time, not for the work to finish. Detox's design principles make this explicit: synchronizing with the app's activity fights flakiness at its core, instead of papering over it with delays. ## The price: strictness Because Detox waits for true idleness, anything that **never** becomes idle blocks the test: an endless loading animation, a recursive short `setTimeout` polling loop, or a long-polling request that never completes. Detox then reports what is busy through its synchronization debug log (by default after an action takes more than 10 seconds) and the test eventually fails on a timeout. That is often a genuine bug, such as a loader that is never replaced when the server returns an error, and occasionally a deliberate behaviour that needs an escape hatch: excluding URLs from network tracking, or switching synchronization off for a short stretch of the test. ## Summary for an interview - Detox is gray-box: it runs code inside the app and knows when the app is idle. - It waits for network, timers, animations, layout, the main queue and the JS thread before every action and expectation. - Sleeps become unnecessary and are discouraged. - The trade-off is that an app that never settles makes the test block, which is a signal to investigate rather than to add delays.
- Where are Detox expectations evaluated, and why does that matter for synchronization?On the device, inside the app, by Detox's native client, not in the Node.js test process. Because the same client that tracks idleness also checks the expectation, it can wait for idle and then evaluate against the settled UI without shipping the view hierarchy back to the test process.
- Does Detox wait for every JavaScript timer the app schedules?No. It waits for `setTimeout` timers of up to 1.5 seconds and ignores `setInterval`. A short recursive `setTimeout` loop therefore keeps the app busy indefinitely, while the same polling written with `setInterval` does not block tests.
saying these in an interview costs you the question
- Detox needs a sleep after each tap because it cannot see network calls.
- Detox only waits for the element to appear, like a polling black-box tool.
- Detox waits for every timer, including setInterval, before each step.
- Synchronization makes Detox immune to an app that never becomes idle.
- Detox evaluates expectations in the Node.js test process.