skip to content

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%

answer

  1. read the 'app is busy' log
  2. debugSynchronization defaults to 10 s
  3. is the loader a bug?
  4. no API to exclude animations
  5. mock it in the e2e build

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.

solid answer

~50 s

First confirm the cause. Detox's synchronization debugging (`session.debugSynchronization`, 10 000 ms by default, or `detox test --debug-synchronization 5000`) prints "The app is busy with the following tasks" with the busy resources: pending animations, timers, network URLs. If it lists animations and no requests, ask why the loader never stops. Usually it is an app bug: the draw request got an error the UI never shows, or parsing the result threw, so the loader stays forever. Fix that; the hang found a real defect. If the loader loops by design, such as an idle attract animation, Detox has no API to exclude animations from synchronization, so replace the component with a static mock in the e2e build through Metro, or wrap only that part of the test in `device.disableSynchronization()` with explicit `waitFor(...).withTimeout(...)` and re-enable afterwards. Also check for hidden animations: an off-screen loader in an unvisited tab keeps the app busy too.

code

javascript · 14 lines
javascript
it('shows results even though the jackpot banner loops', async () => {
  await element(by.id('draw-button')).tap();

  // the banner never stops animating, so wait manually for this step only
  await device.disableSynchronization();
  await waitFor(element(by.id('results-list')))
    .toBeVisible()
    .withTimeout(8000);
  await element(by.id('close-banner')).tap();

  // back on a screen that can go idle
  await device.enableSynchronization();
  await expect(element(by.id('ticket-summary'))).toBeVisible();
});

go deeper

for a junior

Recall that an animation that never stops keeps Detox waiting, and that the debug log tells you what the app is busy with.

for a middle

Explain the debug-synchronization log, how to tell animation from network busyness, and why there is no animation blacklist.

for a senior

Separate app bugs (errors never shown, stuck loaders) from deliberate loops, and pick an e2e mock or a narrow sync-off region with explicit waits.

for a principal

Set a team rule that endless animations stop when off-screen and that sync-off regions need review, keeping the suite synchronized by default.

## Why the test hangs **Detox** runs every action and expectation only after the app is idle, and running animations count as busy. An `Animated.loop` spinner, a looping illustration or an animated GIF that never stops therefore means the app is **never** idle. The step waits until a timeout fails the test, often after a long pause that looks like a frozen run. This strictness is intentional. It often exposes a real bug, but not always, so the job is to find out which case you are in. ## Step 1: find the busy resource Detox's **synchronization debugging** is on by default. When an action or expectation takes longer than `session.debugSynchronization` (10 000 ms by default), Detox queries the app and logs what keeps it busy: ```text The app is busy with the following tasks: • UI elements are busy: - View animations pending: 2. - Layers pending animations: 7. • 1 enqueued native timers: ... ``` You can lower the threshold for a debugging run with `detox test --debug-synchronization 5000`, or set it in the config. The iOS and Android logs look different but name equivalent resources. On iOS, the launch arguments `DTXEnableVerboseSyncSystem` and `DTXEnableVerboseSyncResources` produce even more detail in the system log. ## Step 2: is it a bug? In the lottery app, the "drawing numbers" loader should be replaced by the results once the draw request returns. If it keeps spinning, check these in order: 1. **The server never answers.** The busy log also lists the in-flight **network URLs**. Fix the endpoint, the test environment or the mock server. 2. **The server answered with an error the UI ignores.** No URLs are pending, but the loader stays. Look in the app/device logs, which Detox can record as artifacts, for the error and make the UI show a failure state. 3. **The app threw while handling valid data**, for example parsing the draw result. The error with its stack trace appears in the logs. In all three cases the right fix is in the app: the hang caught a stuck loader a real user would also see. ## Step 3: an animation that loops on purpose Some animations never end by design: an attract loop on the home screen, a shimmering jackpot banner. Detox has **no API to exclude animations** from synchronization (it does for network URLs), so the options are: - **Mock the component in the e2e build.** Detox mocking works through Metro, not `jest.mock`: add a file such as `JackpotBanner.e2e.tsx` that renders a static version, and put `e2e.tsx` in front of Metro's `resolver.sourceExts` when bundling the build under test. The app under test no longer animates; every other test keeps full synchronization. - **Disable synchronization around the affected steps.** `await device.disableSynchronization()`, then wait explicitly for the elements you need with `waitFor(element(...)).toBeVisible().withTimeout(...)`, and `await device.enableSynchronization()` once you are back on a screen that can go idle. Keep this region as small as possible; everything in it is exposed to timing again. - **Stop the animation when it is not visible.** A loop that keeps running after its screen is covered is also wasted CPU for real users. ## Step 4: hidden animations The worst cases are animations you cannot see: - a loader in a bottom tab that was mounted at launch but never visited; - a list row far below the fold with an animated badge; - a small spinner leaked underneath the header. Useful techniques, from the Detox troubleshooting guide: step through the test in debug mode and explore the app by hand until Detox unblocks; capture a view hierarchy at the stuck moment; use Android Studio's Layout Inspector; or remove chunks of the screen until the blockage disappears, then narrow down. ## What not to do | Tempting move | Why it is wrong | |---|---| | Add `sleep()` calls | the app still never idles; the next step blocks again | | Raise every timeout | the run just hangs longer before failing | | Disable synchronization for the whole suite | every test loses automatic waiting and becomes timing-dependent | | Blacklist URLs | the busy resource is an animation, not the network | ## Summary Read the busy log, treat a stuck loader as a probable app bug, and only for a deliberately endless animation reach for an e2e mock or a narrowly scoped `disableSynchronization()`.

  • Why can't the loader be excluded the way a noisy URL can?
    Detox offers URL exclusion for network synchronization through `device.setURLBlacklist()` and the `detoxURLBlacklistRegex` launch argument, but its docs state there is no equivalent for animations. The supported routes are changing the app build under test, typically a Metro-resolved mock, or turning synchronization off for a stretch.
  • Why does jest.mock not help replace the looping component in a Detox test?
    Detox tests run in Node.js while the app runs its own JavaScript bundle on the device. `jest.mock` only changes modules in the test process. To change app code, the bundle itself must resolve a different file, which is why Detox mocking goes through Metro's source-extension resolution.
  • The busy log lists pending network URLs as well as animations. What does that change?
    It points at a server that is not answering: the loader is waiting on those requests. Investigate the endpoint or the test environment first; the animation is a symptom, and a mock of the loader would hide a broken backend dependency.

saying these in an interview costs you the question

  • Add a sleep before the next step so the animation can finish.
  • Use setURLBlacklist to make Detox ignore the loader animation.
  • Disable synchronization in beforeAll for the whole suite.
  • An endless loader is always a Detox bug, never an app bug.
  • jest.mock in the test file can replace the loader in the running app.