Why is Cypress saying my element is detached after just running a get command?

Objective: I want to click on a particular element on the page using an accessibility selector with cypress

Code

cy.findAllByRole('rowheader').eq(2).click();

Error

Timed out retrying: cy.click() failed because this element is detached from the DOM.

<th scope="row" data-automation-id="taskItem" aria-invalid="false" tabindex="-1" class="css-5xw9jq">...</th>

Cypress requires elements be attached in the DOM to interact with them.

The previous command that ran was:

  > cy.eq()

This DOM element likely became detached somewhere between the previous and current command.

Question: I can see in the DOM that this element is still present - there is no logic that will detach this element from the DOM, and the eq method certainly wouldn't do that. Additionally, the findAllByRow method is clearly working as it has found the correct th element I want to click on. How come its saying the element is detached? Is there a workaround for this situation?


Solution 1:

This could be bad advice, but can you try the following?

cy.findAllByRole('rowheader').eq(2).click({force: true})

Solution 2:

Here is something that helped me to avoid that detached issue. Using Cypress aliases and the built-in assertion retry-ability.

Example:

cy.get('some locator').first().find('another locator').eq(1).as('element');
cy.get('@element').should(***some assertion to be retried until met***);

So even though I'm traversing a bunch of elements (which could lead to problems because one of the parents could potentially get detached), in the end when I put an alias on top of it, I'm adding a direct link to the final element that was yielded at the end of the chain. Then when I refer to that alias Cypress will re-query the final element and retry it as needed based on any assertions added on top of it. Which indeed helped me a lot to prevent that detached problem.

Solution 3:

I'm running in the same issue, get the same error while running:

cy.get('<elementId>').should('be.visible').click();

I can see as the test runs that it finds the element (and highlights it), the assertion is validated and then somehow the .click() fails to find the element even though it is chained.

I found that adding a static wait before this line for a couple seconds addresses the issue, but I am not sure why I would need to do that and don't really want to use a static wait.

There are no asynchronous tasks running so there is no way to do a dynamic wait.

Solution 4:

Without a reproducible example, it's speculative, but try adding a guard before the click using Cypress.dom.isDetached

cy.findAllByRole('rowheader').eq(2)
  .should($el => {
    expect(Cypress.dom.isDetached($el).to.eq(false)
  })   
  .click()

If the expect fails, then a prior line is causing the detach and cy.findAllByRole is not re-querying the element correctly.

If so you might be able to substitute a plain cy.get().


I also like @AntonyFuentesArtavia idea of using an alias, because the alias mechanism saves the original query and specifically requeries the DOM when it finds it's subject is detached.

See my answer here

From the Cypress source

const resolveAlias = () => {
  // if this is a DOM element
  if ($dom.isElement(subject)) {
    let replayFrom = false

    const replay = () => {
      cy.replayCommandsFrom(command)

      // its important to return undefined
      // here else we trick cypress into thinking
      // we have a promise violation
      return undefined
    }

    // if we're missing any element
    // within our subject then filter out
    // anything not currently in the DOM
    if ($dom.isDetached(subject)) {
      subject = subject.filter((index, el) => $dom.isAttached(el))

      // if we have nothing left
      // just go replay the commands
      if (!subject.length) {
        return replay()
      }
    }