skip to content

Idle-State Waiting

Detox tracks in-flight network requests, timers, animations and the JS thread, and acts only when all are idle. Interviewers ask what to do when an endless animation keeps the app busy.

on this pageshow

explore

questions

5

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?

level: juniorimportance: must knowfreq 55%

answer

  1. gray box, not black box
  2. the app reports when it is idle
  3. network, timers, animations, layout
  4. JS thread and main queue
  5. every action and expectation waits

basics

~20 s

Detox 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 s

Detox 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 lines
javascript
describe('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

for a junior

Recall that Detox waits for the app to be idle before each action and expectation, so tests do not need sleep calls.

for a middle

Name the resources Detox tracks (network, timers, animations, layout, main queue, JS thread) and explain the 1.5 second setTimeout rule.

for a senior

Explain the strictness trade-off: an app that never idles blocks the test, and that blockage usually points at a real app bug.

for a principal

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.
open as a page

A Detox test in a lottery app hangs after launch because a 'drawing numbers' loader animates endlessly; how do you diagnose and fix it?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Read Detox's synchronization debug log to confirm animations are the busy resource, then fix the app if the loader should have ended; if it loops by design, swap it for a static mock in the e2e build, or disable synchronization only around it.

open as a page

In Detox, what do device.disableSynchronization() and device.enableSynchronization() do, and what must the test do differently while synchronization is off?

level: middleimportance: should knowfreq 35%

basics

~20 s

disableSynchronization() makes Detox stop waiting for the app to be idle; enableSynchronization() turns waiting back on and resolves only when the app next goes idle. While off, the test must wait explicitly for each element with waitFor(...).withTimeout(...).

open as a page

A lottery app polls draw status with a recursive setTimeout every second, and every Detox test after launch blocks; why, and what changes fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Detox treats a pending setTimeout of 1.5 seconds or less as busy and ignores setInterval. A recursive one-second setTimeout always has the next timer queued, so the app never idles. Switch the loop to setInterval, lengthen it, or stop polling when not needed.

open as a page

In Detox, when do you use device.setURLBlacklist() or the detoxURLBlacklistRegex launch argument, and how do the patterns have to be written?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Use them when requests that never finish, such as long polling or background uploads, keep Detox's network synchronization busy. setURLBlacklist works mid-test; detoxURLBlacklistRegex applies from launch. Both take regex strings or RegExp objects with only the i, m and s flags.

open as a page