🔧 Error Fixes
· 2 min read
Last updated on

Cypress: Element Is Detached from the DOM — How to Fix It


CypressError: cy.click() failed because the element has been detached from the DOM

What causes this

Cypress found the element, but by the time it tried to interact with it, the element was removed from the DOM and re-created. This is extremely common with React, Vue, and other frameworks that re-render components — the old DOM node gets replaced with a new one, but Cypress is still holding a reference to the old one.

Typical scenarios:

  • A state update causes a re-render between cy.get() and .click()
  • An API response arrives and replaces the content
  • A loading spinner disappears and the real content mounts
  • A parent component re-renders, destroying and recreating child elements

Fix 1: Don’t store element references

The most common mistake — storing an element and using it later:

// ❌ Element might be stale by the time you click
cy.get('.button').then($btn => {
  cy.get('.input').type('hello');  // This might cause a re-render
  cy.wrap($btn).click();  // Detached!
});

// ✅ Re-query the element — Cypress auto-retries
cy.get('.input').type('hello');
cy.get('.button').click();

Cypress commands that re-query (like cy.get()) automatically wait for the element to exist. Wrapped references don’t.

Fix 2: Use assertions to wait for stability

Tell Cypress to wait until the element is in a stable state before interacting:

// ✅ Wait for the element to be visible and stable
cy.get('[data-testid="submit-btn"]')
  .should('be.visible')
  .and('not.be.disabled')
  .click();

The .should() assertion retries until it passes, which means Cypress will wait for re-renders to finish.

Fix 3: Use data-testid attributes

cy.get('.button') might match different elements as the DOM changes. Use stable selectors:

// ❌ Class-based selectors are fragile
cy.get('.btn-primary').click();

// ✅ data-testid is stable across re-renders
cy.get('[data-testid="submit-btn"]').click();

Fix 4: Wait for the re-render trigger to complete

If you know what causes the re-render, wait for it:

// Wait for the API call to finish before clicking
cy.intercept('GET', '/api/data').as('getData');
cy.visit('/page');
cy.wait('@getData');
cy.get('[data-testid="action-btn"]').click();

Fix 5: Use cy.contains() for text-based elements

cy.contains() re-queries the DOM on each retry:

// ✅ Cypress will find the element even after re-renders
cy.contains('button', 'Submit').click();

How to prevent it

  • Never store DOM references with .then() and use them later — always re-query
  • Use data-testid attributes for stable element selection
  • Add .should('be.visible') before interactions to wait for re-renders
  • Use cy.intercept() and cy.wait() to synchronize with API calls
  • If a specific component causes frequent detachment issues, consider adding a loading state that Cypress can wait for