A Detox test in a lottery app hangs after launch because a 'drawing numbers' loader animates endlessly; how do you diagnose and fix it?
answer
- read the 'app is busy' log
- debugSynchronization defaults to 10 s
- is the loader a bug?
- no API to exclude animations
- mock it in the e2e build
basics
~20 sRead 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 sFirst 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 linesit('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
Recall that an animation that never stops keeps Detox waiting, and that the debug log tells you what the app is busy with.
Explain the debug-synchronization log, how to tell animation from network busyness, and why there is no animation blacklist.
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.
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.