A Cypress failure screenshot shows the ticket queue looking healthy. Why?
answer
- The picture is taken after the fact
- Roughly a tenth of a second later
- The app keeps running meanwhile
- Cypress's own log paints asynchronously
- Timers and animations are frozen, requests are not
basics
~20 sTaking a Cypress screenshot is asynchronous and lands roughly 100ms after the failure, and the application keeps running in that window. The Command Log paints asynchronously too, so the error text can be missing as well.
solid answer
~40 sThe automatic failure screenshot is best-effort, not a freeze-frame of the failing moment. Taking it is an asynchronous operation of roughly 100ms, and the application keeps living inside the browser during that window — the ticket row `cy.get()` waited four seconds for can finally render just after the timeout was declared, so the image shows a healthy queue. The second half is Cypress's own UI: the Command Log renders through React on an animation frame, so a `runner` capture can be written before the error message has painted. `disableTimersAndAnimations` (default `true`) freezes JavaScript timers and CSS animations during the capture, which narrows the gap but does not close it. Treat the terminal's failure output as authoritative and the image as supporting evidence.
go deeper
Know that the failure image is captured shortly after the failure, so it may not match the moment the test gave up.
Explain the two mechanisms: the roughly 100ms asynchronous capture, and the Command Log rendering on an animation frame.
Show how you actually diagnose an intermittent CI failure when the image is unhelpful, and where a named capture or a recording earns its place.
Set the expectation across teams that a still image is supporting evidence, and decide what a pipeline captures so nobody argues from a misleading frame.
## The image is taken after the failure, not at it When a test fails during `cypress run`, Cypress reacts to the failure and then captures the browser. Taking a screenshot is an asynchronous operation that takes roughly **100 milliseconds** to complete, and during that time the application under test is still running in the same browser: timers you did not freeze fire, requests land, React re-renders. That is exactly the window that makes a failure screenshot look wrong: - `cy.get('[data-cy=ticket-row]')` times out after four seconds and the test fails. - The queue's slow request resolves 40ms later and the rows render. - The capture is written 100ms after that — showing a fully populated ticket queue and no error at all. The image is not lying and Cypress is not broken. It is a photograph of a moving scene taken a fraction of a second after the event you cared about. ## The second half: Cypress's own UI paints asynchronously Every automatic failure capture is coerced to a `runner` capture, so the Command Log is in the frame. But the Command Log is a React application that renders during an animation frame, and the capture can be written before the error message has been painted into it. You can end up with an image that shows the command list without the red failure entry you were looking for. This is precisely why the runner offers a video of the whole spec as well: a still frame is one moment chosen by a race, while the recording contains the seconds either side of it. ## What Cypress does do to steady the frame | Option | Default | Effect on the image | |---|---|---| | `disableTimersAndAnimations` | `true` | Stops JavaScript timers and CSS animations while the capture is taken | | `capture` (on failure) | coerced to `runner` | Puts the command list and error text in the frame, when they have painted | | `scale` (for `runner`) | forced `true` | Fits the browser into the image rather than cropping it | `disableTimersAndAnimations` narrows the drift, because a spinner or a CSS transition mid-flight is the most common way a screenshot disagrees with the failure message. It cannot stop work that is already in flight — a pending fetch that resolves is not a timer. ## How to diagnose the run anyway 1. **Read the terminal output first.** The reporter's error, its stack and the failing command are authoritative for *what* failed. The image only ever tells you what the page looked like a moment later. 2. **Add a named capture at the decisive step.** `cy.screenshot('queue-before-filter')` before the action that fails gives you a frame you chose rather than one a race chose, and it is written in `cypress open` as well. 3. **Turn on the spec video for that pipeline.** The recording covers the seconds before the failure, which is where an intermittent ticket queue usually gives itself away. 4. **Or replay the run instead of filming it.** Cypress Cloud's Test Replay is the CI-side alternative: rather than reading a still, you step back through the run as it executed. It is a Cloud feature and out of scope here, but it is the reason many teams stop tuning screenshots. ## Reading a screenshot honestly Things a failure screenshot **can** settle: - Whether the app rendered an error page, an empty state, or a login wall instead of the ticket queue. - Whether the viewport was the size you expected. - Whether the data on screen is the data your seeding step was supposed to create. Things it **cannot** settle: - Precisely what the DOM looked like when the assertion ran. - Whether the element was there "just before" — a healthy-looking queue is entirely consistent with a genuine four-second timeout. - Anything about a request that failed silently, since none of that is on screen. A candidate who says "the screenshot shows it passing, so the test is wrong" has drawn the wrong conclusion; the right one is that the failure message and the image are describing two different moments about a tenth of a second apart.
- Does `disableTimersAndAnimations` fix this?Only partly. It defaults to `true` and stops JavaScript timers and CSS animations for the duration of the capture, which removes mid-flight spinners and transitions. It cannot pause work already in flight, so a request that resolves between the failure and the capture still changes the page.
- Why is the error message sometimes missing from the failure screenshot?The failure capture is a `runner` capture, so the Command Log is in frame, but that log is a React UI that renders on an animation frame. The image can be written before the failure entry has painted, which is why the terminal output, not the picture, is the record of what failed.
It is a long-exposure photograph of a moving scene, not the freeze-frame the error message describes — by the time the shutter closes, the thing you were waiting for may have arrived.
saying these in an interview costs you the question
- Concludes the test is wrong because the image looks fine
- Believes the screenshot is a synchronous freeze-frame
- Expects the error text to always be in the image
- Thinks disableTimersAndAnimations pauses pending requests
- Ignores the terminal error and argues from pixels