skip to content

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%

answer

  1. stop waiting for idle
  2. re-enabled on every new app instance
  3. enable blocks until idle again
  4. detoxEnableSynchronization: 0 at launch
  5. explicit waits with withTimeout

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(...).

solid answer

~40 s

`await device.disableSynchronization()` tells Detox to stop monitoring idle and busy state, so actions and expectations run immediately. That is the escape hatch for screens that never go idle, or for catching a transient toast before it disappears. The cost is that the test is timing-dependent again: every step inside the region needs an explicit `waitFor(element(...)).toBeVisible().withTimeout(ms)`. `await device.enableSynchronization()` turns monitoring back on at once, and its promise resolves only when the app becomes idle, so call it only after navigating back to a screen that can settle, or the test blocks. Synchronization is on by default and is re-enabled on every launch of a new app instance. To start an app with it off, pass the launch argument `detoxEnableSynchronization: 0` to `device.launchApp()`. Keep sync-off regions as short as possible.

code

javascript · 13 lines
javascript
it('shows the saved-ticket toast', async () => {
  await element(by.id('pick-numbers')).tap();

  // the toast hides after 1 s; with sync on, Detox would wait until it was gone
  await device.disableSynchronization();
  await element(by.id('save-ticket')).tap();
  await waitFor(element(by.text('Ticket saved')))
    .toBeVisible()
    .withTimeout(3000);
  await device.enableSynchronization();

  await expect(element(by.id('my-tickets'))).toBeVisible();
});

go deeper

for a junior

Recall that disableSynchronization stops Detox waiting for idle, so steps inside need explicit waits, and enableSynchronization turns it back on.

for a middle

Explain that enableSynchronization blocks until idle, that each new app instance starts synchronized, and the detoxEnableSynchronization launch argument.

for a senior

Scope sync-off regions to the minimum, re-enable only from a screen that can settle, and prefer URL exclusion or mocks when they fit.

for a principal

Treat every sync-off region as test debt with an owner, and track them so the suite does not drift back to timing-based waits.

## The switch and why it exists **Detox** synchronizes every action and expectation with the app's idle state. Two `device` methods turn that off and on during a test: - `await device.disableSynchronization()`: Detox stops waiting for idleness. Commands are sent immediately. - `await device.enableSynchronization()`: Detox resumes waiting. The call's promise resolves only when the app is idle again. Detox's documentation describes disabling synchronization as a **last resort** with two legitimate uses: 1. A screen that never becomes idle and cannot be fixed or mocked right now, such as a deliberately endless animation in the lottery app's draw screen. 2. A **transient** element, such as a "Ticket saved" toast that hides itself after one second: with synchronization on, Detox waits for the toast's short hide timer (under the 1.5 second tracking limit) and its animations to finish, so by the time it checks, the toast is gone. ## What changes inside a sync-off region With synchronization off, Detox behaves like a black-box tool. Every step must say how long it may wait: ```js await device.disableSynchronization(); await element(by.id('save-ticket')).tap(); await waitFor(element(by.text('Ticket saved'))).toBeVisible().withTimeout(3000); await device.enableSynchronization(); ``` - `waitFor(...).toBeVisible().withTimeout(ms)` polls until the condition holds or the timeout expires. - Plain `expect(...)` checks immediately, which is often too early without synchronization. - Taps may land while a transition is still running. Detox's docs warn that sync-off code tends to accumulate `sleep()` calls and "can never be 100% stable", so the rule is to keep the region to the bare minimum. ## `enableSynchronization()` can block | Call | Effect | Resolves when | |---|---|---| | `device.disableSynchronization()` | stop idle monitoring | immediately | | `device.enableSynchronization()` | resume idle monitoring | the app is idle again | | `device.launchApp({ newInstance: true })` | fresh process | launch completes; sync is on again | Because `enableSynchronization()` waits for idleness, calling it while the endless animation is still on screen makes the test block until a safeguard timeout. Navigate back to a "safe zone", a screen that can settle, before re-enabling. ## Synchronization and app launches - Synchronization is **on by default**. - It is **re-enabled on every launch of a new app instance**, so a `disableSynchronization()` in one test does not survive a `launchApp({ newInstance: true })` in the next. - Some apps cannot be synchronized during launch itself, for example a splash animation that loops until a remote config arrives. For that case, launch with synchronization off: ```js await device.launchApp({ newInstance: true, launchArgs: { detoxEnableSynchronization: 0 }, }); ``` Then call `device.enableSynchronization()` once the app has reached a screen that can go idle. ## Choosing between disabling and fixing Disabling is the right tool when the non-idle behaviour is intended and short-lived in the flow. It is the wrong tool when: - the busy resource is a **network endpoint** such as a long-poll or a WebSocket, where excluding that URL keeps every other form of synchronization; - the busy resource is an **app bug**, such as a loader never replaced after an error; - the animation can be replaced by a **mock** in the e2e build, which keeps synchronization on for every step. Apart from excluding specific URLs, there is no supported way to switch off one kind of resource, such as timers but not animations; `disableSynchronization()` is all or nothing. ## A checklist for a sync-off region 1. Read the busy log first and confirm the blocker cannot be fixed, mocked in the e2e build, or excluded as a URL. 2. Disable immediately before the steps that need it, not in `beforeAll`. 3. Give every step inside an explicit `waitFor(...).withTimeout(...)` sized for the slowest CI machine, not your laptop. 4. Navigate to a screen that can settle before re-enabling. 5. Re-enable, and let the next assertion run synchronized again. 6. Leave a comment naming the busy resource, so the region can be deleted when the app changes. ## Summary Disable late, re-enable early, wait explicitly in between, and never re-enable while the app is still busy. Remember that each new app instance starts with synchronization on, and use `detoxEnableSynchronization: 0` only when the launch itself cannot be synchronized.

  • Why can't a synchronized Detox test see a toast that hides itself after one second?
    With synchronization on, Detox waits for the app to be idle before checking, and the toast's show animation and its one-second hide timer, which is under the 1.5 second tracking limit, keep the app busy. By the time everything is idle, the toast has been removed. Disabling synchronization lets the test check while it is still on screen.
  • A test disables synchronization, then the next test in the file launches with newInstance: true. Is synchronization still off?
    No. Detox re-enables synchronization on every launch of a new app instance, so the next test starts synchronized. Tests that rely on sync being off must disable it again, or launch with the `detoxEnableSynchronization: 0` launch argument.

saying these in an interview costs you the question

  • enableSynchronization returns immediately, so it is safe to call anywhere.
  • After disableSynchronization, plain expect calls still wait for the UI.
  • Disabling synchronization once keeps it off for the rest of the suite.
  • You can disable only timer synchronization and keep network tracking.
  • Disabling synchronization for the whole suite is the standard setup.