Cypress tests are failing, and you’re staring at a cryptic error message or a test that just… stops. You need to see what’s happening right now in the browser.

Debug Failing Cypress Tests with DevTools and cy.pause()

The core issue is that Cypress, by default, runs your tests in an isolated environment and cleans up after itself. When a test fails, this cleanup often happens too quickly, leaving you with no trace of the problematic state.

Here’s how to dive in and find the root cause:

1. The Browser is Frozen (and You Don’t Know Why)

What broke: Your test execution halted mid-flight, and you have no idea what the browser was doing, what elements were present, or what network requests were flying.

Common Causes & Fixes:

  • cy.pause() is your best friend: This is the most direct way to stop execution at a specific point.

    • Diagnosis: Add cy.pause() directly before the line of code you suspect is failing or causing the issue.
    • Fix:
      cy.get('input[name="username"]').type('testuser');
      cy.pause(); // Execution will stop here
      cy.get('input[name="password"]').type('password123');
      cy.get('button[type="submit"]').click();
      
    • Why it works: When the test hits cy.pause(), it stops indefinitely. Your browser’s DevTools remain attached and fully functional, allowing you to inspect the DOM, network, console, etc., exactly as they were when the pause occurred. You can then manually resume the test by clicking the "Resume" button in the Cypress command log.
  • Uncaught Exceptions: JavaScript errors in your application code that aren’t caught can terminate the Cypress test runner.

    • Diagnosis: Look for "Uncaught Error" messages in the Cypress command log or your browser’s console.
    • Fix: Add an event listener to catch these errors and prevent Cypress from stopping.
      Cypress.on('uncaught:exception', (err, runnable) => {
        // Ignore specific errors or log them without failing the test
        // Example: Ignore errors related to a specific third-party script
        if (err.message.includes('some-third-party-script')) {
          return false; // Prevents Cypress from failing the test
        }
        // For other errors, you might want to log them
        console.error('Uncaught Exception:', err);
        // If you want to allow the test to continue for other errors:
        // return false;
      });
      
    • Why it works: By default, Cypress fails the test on uncaught exceptions. This listener intercepts these exceptions. Returning false tells Cypress to ignore the exception and continue running the test, allowing you to inspect the state after the error occurred.
  • Stale Element References: The DOM element your test is trying to interact with has been removed or modified by the application since it was last referenced.

    • Diagnosis: Errors like "Timed out retrying: cy.click() failed because this element is detached from the DOM." or similar messages indicating the element is no longer present.
    • Fix: Ensure your selectors are robust and that you’re not interacting with elements before they are ready. Use cy.get() with appropriate selectors, and consider using cy.wait() judiciously or, better yet, cy.intercept() for network-driven UI updates. Often, re-selecting the element just before interaction is the simplest fix.
      // Instead of:
      // cy.get('.my-button').click();
      
      // Try:
      cy.get('.my-button').then(($button) => {
        // Now interact with the button
        cy.wrap($button).click();
      });
      // Or if it's due to a slow UI update:
      cy.get('.my-button').should('be.visible').click();
      
    • Why it works: cy.get() automatically retries for a period. However, if the element is removed and then re-added with a new instance, the previous reference becomes stale. Re-selecting ensures you’re always working with a current DOM reference. cy.should('be.visible') adds an explicit wait for the element to be interactable.
  • Incorrect Wait Strategies: Relying on fixed cy.wait(milliseconds) is brittle and a common source of flakiness and hidden errors.

    • Diagnosis: Tests pass intermittently, or fail when the application is slightly slower than usual. The error might be a timeout because an element isn’t present or interactable yet.
    • Fix: Use Cypress’s built-in retryability and explicit waits. Wait for specific conditions rather than arbitrary time.
      // Bad:
      // cy.get('#submit-button').click();
      // cy.wait(2000); // Wait for something to happen
      
      // Good: Wait for the element to be visible and enabled
      cy.get('#submit-button').should('be.visible').and('be.enabled').click();
      
      // Even better: Wait for a specific network response
      cy.intercept('POST', '/api/save').as('saveRequest');
      cy.get('#submit-button').click();
      cy.wait('@saveRequest').its('response.statusCode').should('eq', 200);
      
    • Why it works: Cypress commands like cy.get() and cy.click() automatically retry for a default timeout (usually 4 seconds). cy.should() adds further assertions that also retry. cy.intercept() and cy.wait('@alias') specifically wait for network activity, ensuring the UI is in the expected state after an operation completes, not just after a fixed duration.
  • Iframes and Cross-Origin Issues: Interacting with content inside an iframe or from a different origin can be tricky.

    • Diagnosis: Commands targeting elements within an iframe fail with "cannot access contents of the frame" or similar errors, or selectors don’t match.
    • Fix: Use cy.frameLoaded() and cy.iframe() to get a reference to the iframe’s content.
      // Assuming your iframe has id="my-iframe"
      cy.get('#my-iframe').frameLoaded(); // Ensure the iframe content is loaded
      cy.iframe().find('input[name="iframe-field"]').type('hello');
      
    • Why it works: Cypress treats iframes as separate documents. cy.frameLoaded() waits for the iframe to finish loading its content. cy.iframe() then provides a jQuery object that allows you to query within that iframe’s document, making elements inside accessible to Cypress commands.
  • Browser Development Tools are Your Debugging Partner: Don’t forget the browser’s built-in tools!

    • Diagnosis: When a test fails or pauses, open your browser’s DevTools (usually F12).
    • Fix: Use the Elements tab to inspect the DOM structure and attributes. Use the Console tab to see JavaScript errors and log messages. Use the Network tab to examine API requests and responses that might be causing UI changes or errors. If you used cy.pause(), you can interact with these tools to understand the state.
    • Why it works: DevTools provide a real-time, interactive view of your application’s state. By combining cy.pause() with DevTools, you can freeze the execution at the exact moment of failure and meticulously examine all aspects of the browser environment.

The Next Hurdle

Once you’ve fixed the immediate failure, you might find your test now correctly executes but fails to assert the expected outcome, leading you to refine your cy.get() selectors or add more specific cy.contains() checks.

Want structured learning?

Take the full Cypress course →