Hero Background

Next-Gen App & Browser Testing Cloud

Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Next-Gen App & Browser Testing Cloud

How to Debug Failures in Cypress tests?

To debug failures in Cypress tests, start by reading the error message, code frame, and command log in the test runner, then pause execution where it breaks using .debug(), cy.pause(), or a debugger statement with DevTools open. For headless runs, inspect the failure screenshot and video, turn on verbose logs with DEBUG=cypress:*, use test retries to surface flakiness, and replay the full run with Test Replay in Cypress Cloud. The right tool depends on whether you are debugging interactively or after a CI run.

Start by Reading the Failure

Cypress gives you a lot of context the moment a test fails. Before adding any breakpoints, read what the runner is already telling you. Most failures are solved here without writing a single debug line.

  • Error message and code frame: Cypress prints a readable error plus a code frame highlighting the exact line in your spec that threw. This usually identifies a missing element, a failed assertion, or a timeout immediately.
  • Command log: The left panel lists every command Cypress ran, in order, with status and duration. The failing command is marked in red, so you can see exactly which step broke and what ran before it.
  • Time-travel snapshots: Hover over any command in the log and Cypress shows you the DOM snapshot at that exact moment. Click a command to pin the snapshot and print details to the browser console.
  • Stack trace: Expand the error to see the file and line number where the failure originated, which is essential when the failure is inside a custom command or a helper function.

Debug Interactively in the Cypress Runner

When the command log is not enough, pause the test where it breaks and inspect the live application, the DOM, the network, and your variables. Launch the interactive runner first:

npx cypress open

.debug() and cy.debug()

The .debug() command pauses the run and triggers a debugger in the browser, yielding the previous subject so you can inspect it in the DevTools console. You can chain it after any command or drop in cy.debug() mid-test. DevTools must be open for the breakpoint to trip.

it('logs in the user', () => {
  cy.visit('/login')
  cy.get('[data-cy=email]')
    .type('[email protected]')
    .debug() // inspect the email field's subject in DevTools

  cy.debug() // pause here and inspect the page state
  cy.get('[data-cy=submit]').click()
})

cy.pause()

cy.pause() stops the run at that point and lets you step through the remaining commands one at a time using the controls in the command log. It is ideal for confirming that each command has the desired effect, since you can inspect the web application, the DOM, the network, or any storage between steps.

it('adds an item to the cart', () => {
  cy.visit('/products')
  cy.pause() // run stops; resume or step command-by-command
  cy.get('[data-cy=product-1]').click()
  cy.get('[data-cy=add-to-cart]').click()
})

The debugger Statement with DevTools

A plain debugger statement works too, but because Cypress commands are queued and run asynchronously, you must place it inside a .then() callback where the JavaScript actually executes. Putting it between two chained commands will not break where you expect.

cy.get('[data-cy=total]').then(($total) => {
  // $total is the real DOM element here
  debugger // breaks with DevTools open
  expect($total.text()).to.contain('$49.99')
})

Add Logging to Trace Values

Logging is the fastest way to confirm what a command actually returned without stopping the run. Use cy.log() to write a message into the Cypress Command Log so it lines up with your test timeline, and console.log() to dump objects or large payloads into the browser console for expansion.

cy.get('[data-cy=user-name]').then(($el) => {
  cy.log('User name text: ' + $el.text()) // shows in Command Log
  console.log('Element:', $el) // expandable in browser console
})

Inspect Failures After a Headless Run

On a headless cypress run, there is no live runner to watch, so Cypress captures artifacts instead. A screenshot is taken automatically at the exact point of failure, and a video of the entire spec is recorded if you enable it. These are invaluable for understanding a CI failure you cannot reproduce locally.

Note that from Cypress 13 onward, video is off by default and must be turned on explicitly:

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  video: true, // record a video of each spec
  screenshotOnRunFailure: true, // default true
  e2e: {
    // ...your e2e config
  },
})

You can also capture a screenshot on demand at any step with cy.screenshot() to record state before a known-tricky action.

cy.get('[data-cy=checkout]').click()
cy.screenshot('after-checkout-click') // saved to cypress/screenshots

You can also run a headless command in headed mode and keep the browser open afterward to inspect the final state:

npx cypress run --headed --no-exit --spec "cypress/e2e/login.cy.js"

Turn On Cypress Debug Logs

When a failure looks like an internal or environment issue rather than a test logic bug, enable Cypress's verbose debug output. Set the DEBUG environment variable before running, and scope it so you are not flooded with logs.

# macOS / Linux - all Cypress debug logs
DEBUG=cypress:* npx cypress run

# Narrow the scope to the server process
DEBUG=cypress:server:* npx cypress run

# Windows PowerShell
$env:DEBUG="cypress:*"; npx cypress run

Handle Flaky Failures with Test Retries

If a test fails intermittently, enable test retries so Cypress automatically re-runs a failed test before marking it failed. This both stabilizes CI and helps you tell a genuine bug from a flaky one. Retries should buy you time to fix the root cause, not hide it.

// cypress.config.js
module.exports = defineConfig({
  retries: {
    runMode: 2,  // retry up to 2 times in cypress run (CI)
    openMode: 0, // no retries in cypress open
  },
})

Timing problems are the most common source of flakiness. For the underlying patterns, see How to Handle Asynchronous Actions in Cypress?.

Debug Network-Related Failures

Many failures are not UI bugs at all, they are requests that returned the wrong data or had not finished when an assertion ran. Use cy.intercept() to spy on or stub network traffic, then wait on the aliased request so the test only continues once the response has arrived.

cy.intercept('GET', '/api/cart').as('getCart')
cy.visit('/cart')
cy.wait('@getCart').then((interception) => {
  // inspect the real request and response that the app received
  console.log(interception.response.statusCode, interception.response.body)
})
cy.get('[data-cy=cart-count]').should('have.text', '2')

Common Causes of Cypress Test Failures

Most Cypress failures fall into a handful of recurring categories. Matching the symptom to the cause points you straight at the fix.

CauseTypical SymptomFix
Async / timingElement or value not ready, passes locally and fails in CIUse retryable assertions instead of fixed cy.wait(ms); wait on cy.intercept() aliases
Detached DOM"Element is detached from the DOM" errorRe-query inside the chain; avoid storing element refs; add a guard .should()
Flaky selectorsSelector breaks when markup or classes changePrefer stable data-cy attributes over brittle CSS or XPath
Cross-origin navigationTest errors after navigating to a different domainWrap the cross-origin steps in cy.origin()
Unstubbed external servicesRandom failures tied to a third-party APIStub the response with cy.intercept() to remove external flakiness

Debug at Scale with Test Replay and a Cloud Grid

Local debugging works for one machine, but flaky failures in CI often only appear on a specific browser or OS. Test Replay in Cypress Cloud lets you replay a recorded run with the DOM, network, and console exactly as they were, instead of guessing from a screenshot. Record a run by passing your project's record key:

npx cypress run --record --key <your-record-key>

To find browser-specific failures before they reach your users, you can run the same Cypress suite across a wide matrix of browsers and versions on the Cypress, then use the captured logs, screenshots, and video to root-cause each failure without rebuilding the environment yourself. Running across real browser and OS combinations surfaces the timing and rendering issues that a single local browser hides.

Frequently Asked Questions

Why isn't my Cypress debugger or breakpoint stopping?

The debugger statement and .debug() only pause when the browser DevTools are open. Run cypress open, open DevTools in the spawned browser, then re-run the test. If you still don't break, make sure the debugger sits inside a .then() callback (synchronous JavaScript) rather than between two chained Cypress commands, which are only queued at that point.

What is the difference between cy.log() and console.log() in Cypress?

cy.log() prints a message into the Cypress Command Log next to your commands, so it is tied to the test timeline. console.log() writes to the browser's developer console and is better for dumping objects, variables, or large payloads you want to expand and inspect.

How do I see why a Cypress test failed in CI when there is no UI?

On a headless cypress run, Cypress saves a screenshot at the point of failure and, if video is enabled, a recording of the whole spec. Inspect those artifacts first, enable DEBUG=cypress:* for verbose logs, and use Test Replay in Cypress Cloud to replay the run with the DOM, network, and console exactly as they were.

Why does my Cypress test pass locally but fail randomly in CI?

Random failures are almost always timing or flakiness issues. Replace fixed cy.wait(ms) calls with assertions that Cypress retries automatically, wait on the network with cy.intercept(), and re-query elements that the app re-renders. Enabling test retries buys stability while you find the root cause, but it should not replace the fix.

Does enabling video slow down Cypress, and is it on by default?

From Cypress 13 onward, video recording is off by default and must be enabled with video: true in cypress.config.js. Recording adds a small amount of overhead per spec, which is usually worth it in CI because the video often shows the failure cause without a re-run.

How do I debug a detached DOM element error in Cypress?

A "detached from the DOM" error means the element you queried was re-rendered before Cypress acted on it. Avoid storing element references in variables, re-query inside the same chain, and add a guard assertion such as .should('be.visible') so Cypress retries against the fresh element.

Related Questions

Test Your Website on 3000+ Browsers

Get 100 minutes of automation test minutes FREE!!

Test Now...

KaneAI - Testing Assistant

World’s first AI-Native E2E testing agent.

...

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests