How to Debug Cypress Tests
Find the first meaningful Cypress failure, inspect the right browser state, and isolate flaky or CI-only behavior with a practical debugging workflow.
Start with the earliest failed Cypress command, then inspect the application state at that exact point. Reduce the failure to a small reproduction, compare one execution condition at a time, and collect only the logs or artifacts that can explain the difference. A retry that passes is evidence of a flaky test or environment, not proof that the underlying issue is fixed.
1. Read the first failure
In the Cypress runner, begin with the earliest failed command rather than the final error in a long test. Read the error type and message, code frame, and stack trace. Cypress errors may include a “Learn more” link. The highlighted source location often points to the assertion or command whose assumptions stopped holding.
In open mode, click a command in the Command Log while browser DevTools are open. Cypress can print the command’s subject and yielded result to the console. Use the command history and snapshots to time-travel through earlier DOM states and see when the selector, response, or UI transition diverged. See Debugging in Cypress and Open mode in the Cypress app.
2. Inspect state at the right time
Cypress queues commands and executes them after the test callback has enqueued them. This means a bare JavaScript debugger placed after Cypress commands may pause before those commands have run. Put the breakpoint in a .then() callback when you need to inspect state after a preceding query, append .debug() to expose the current subject as subject in DevTools, or use cy.pause() to step through commands.
it('shows the saved profile', () => {
cy.visit('/profile')
cy.get('[data-cy=save]').click()
cy.get('[data-cy=status]')
.should('contain', 'Saved')
.then(($status) => {
debugger // Inspect after the query/assertion has yielded its subject.
console.log($status.text())
})
})
For a subject-focused pause, use cy.get('[data-cy=status]').debug(). To step through commands interactively, insert cy.pause() before the command of interest, then inspect the DOM, network activity, and storage in the browser. Remove breakpoints and pauses once the diagnosis is complete.
3. Decide whether it is waiting or failing
Cypress retry-ability and test retries address different situations:
| Mechanism | What it repeats | Use it for |
|---|---|---|
| Retry-ability | Queries and their assertions while the application changes | Expected asynchronous UI updates, such as an element appearing after a request |
| Configured test retries | The whole failed test, for a limited number of additional attempts | Detecting or mitigating intermittent failures while investigating their cause |
Test retries rerun beforeEach and afterEach. Failures in before and after hooks do not trigger a retry. A test that passes on a later attempt is still a flake signal: inspect what changed between attempts, including shared data, timing, and environment. Do not use a larger timeout or more retries to hide a wrong selector, missing state setup, or application defect. See Retry-ability and Test retries.
4. Make the failure small and repeatable
- Run only the failing spec, then only the failing test if practical.
- Keep the application build, test data, and relevant configuration fixed.
- Split a large spec or long test until you have the smallest reproduction that still fails.
- Change one comparison axis at a time: local versus CI, headed versus headless, browser family or version, isolated test versus full spec, or first attempt versus retry.
- When a change makes the failure disappear, repeat the comparison to check whether that condition was causal.
This sequence helps distinguish test assumptions from application timing, browser differences, and CI environment differences. Cypress’s troubleshooting guide also recommends reviewing available screenshots, video, or replay and reducing the reproduction.
5. Reproduce a headless-only failure in a visible browser
When CI fails in headless mode but local runs pass, try a headed run with the same browser and test setup. For example:
npx cypress run --headed --no-exit --browser chrome
--headed displays the browser, while --no-exit leaves Cypress open after the run so you can inspect the Command Log and final application state. Match the CI browser family and version where possible; changing browser and headed/headless mode together makes it harder to identify the cause. Cypress documents browser selection and launch behavior in Launching browsers.
6. Collect screenshots, video, and CI evidence
- Screenshots:
cypress runautomatically captures screenshots on failure. This does not happen automatically incypress open. - Video: Video is off by default. Set
video: truein Cypress configuration to record specs incypress run. Cypress does not record video incypress open. - Default folders: Screenshots go to
cypress/screenshotsand videos tocypress/videos. A run clears these folders before execution unless you configure otherwise, so copy artifacts elsewhere first if your workflow needs to preserve them. - Recorded CI runs: For runs recorded with Cypress Cloud, inspect the error, retry attempts, artifacts, test history, and Test Replay. Replay can help when the original browser session is gone and reproducing the same conditions locally is difficult.
See Capture screenshots and videos in Cypress and Debug failing tests in CI with Cypress Cloud.
7. Turn on Cypress diagnostic logs selectively
For Cypress runner or project problems, set a DEBUG namespace before starting Cypress. Broad logs can be large and may affect performance, so begin with a narrower namespace if you know which subsystem is involved.
# macOS or Linux: broad Cypress diagnostics
DEBUG=cypress:* npx cypress run
# macOS or Linux: narrower examples
DEBUG=cypress:server:project npx cypress run
DEBUG='cypress:server:browsers*' npx cypress open
# PowerShell
$env:DEBUG = 'cypress:*'
npx cypress run
In browser open mode, Cypress also documents enabling browser logs in DevTools with localStorage.debug = 'cypress*', then reloading. Turn verbose logging off after collecting the relevant evidence. Details and additional namespaces are in Troubleshooting the Cypress App.
Common Cypress debugging problems
| Symptom | Likely cause | What to try |
|---|---|---|
debugger pauses before the UI action |
It runs in the synchronous test callback while Cypress commands are still queued. | Move it inside a .then() after the query or action whose resulting state you want to inspect. |
| An element is missing intermittently | The test may be asserting before an asynchronous transition, or the selector/state may be unstable. | Use a retryable query and assertion for expected UI changes; inspect snapshots and confirm the selector identifies the intended element. |
| The test passes on retry | Some condition differs between attempts, such as timing, shared test data, or environment state. | Inspect each attempt and make the test’s setup and data deterministic. Treat retries as a diagnostic signal. |
| It fails only in CI or headless mode | Browser, execution mode, application build, or environment differs from local. | Reproduce headed with the same browser where possible, then vary one axis at a time and compare artifacts. |
| No screenshot appears in open mode | Failure screenshots are automatic in cypress run, not cypress open. |
Use run mode to collect the automatic failure screenshot, or inspect the open-mode Command Log and browser state. |
| No video appears | Video is disabled by default, and open mode does not record it. | Set video: true and run the spec with cypress run. |
| Debug output is too large or the run slows down | DEBUG=cypress:* enables broad internal logging. |
Use a specific namespace and enable it only for the diagnostic run. |
Performance and reliability notes
- Broad DEBUG logging can produce substantial output and affect performance; narrow it to the relevant subsystem.
- Use retries to reveal intermittent failures, then fix the underlying instability. A passing retry alone does not establish reliability.
- Keep comparisons controlled: if browser, mode, build, and data all change at once, the result is difficult to interpret.
- Preserve CI artifacts before a subsequent run if you need them for investigation, because Cypress clears the default screenshot and video folders before a run unless configured otherwise.
- When debugging a slow or failing page load, separate the Cypress command that timed out from the application’s network and rendering behavior using the Command Log, browser DevTools, and the narrowest useful Cypress logs.
Or skip the browser setup
If the missing evidence is what a page looked like at the time of failure, ScreenshotNeo provides a website screenshot API and MCP server for developers. It can capture a page as PNG, JPEG, WebP, or PDF. A screenshot is a useful page artifact; it does not replace Cypress’s DOM, network, or test-state inspection.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before a shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, no card required.
FAQ
Should I add a delay when an assertion fails?
Only when the behavior truly depends on a known delay. Prefer a query and assertion that Cypress can retry while the UI changes; a fixed delay can slow every run and still miss variable timing.
Does a passing retry mean the test is fixed?
No. A pass on retry indicates the result varied across attempts. Find and remove the unstable condition before treating the test as reliable.
Can Cypress video show a failure from cypress open?
No. Cypress records video for specs run with cypress run when video is enabled; open mode does not record video.
What should I compare first for a CI-only failure?
Keep the build and test data fixed, then compare execution mode or browser version one at a time. Use the failure screenshot, retry history, and recorded-run artifacts to guide the next comparison.


