Cypress: Re-query elements that are found 'detached' from the DOM

Created on 12 May 2020  ·  87Comments  ·  Source: cypress-io/cypress

Current behavior:

Currently when Cypress queries an element that was passed from a parent element (say in .get(), .click() or .find()), if the element at some point becomes detached from the DOM, we throw an error:

CypressError: cy.click() failed because this element is detached from the DOM.

<element>

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

The previous command that ran was:

  cy.get()

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

Common situations why this happens:
  - Your JS framework re-rendered asynchronously
  - Your app code reacted to an event firing and removed the element

You typically need to re-query for the element or add 'guards' which delay Cypress from running new commands.

https://on.cypress.io/element-has-detached-from-dom

Screen Shot 2020-11-02 at 10 27 54 AM

Desired behavior:

Really users just want to re-query the DOM for the previously found element and click on the new element. Because it's more often than not that the framework underneath has created a completely new DOM element and inserted it into the DOM.

The proposal is to re-query the previously found element and continue the command with any newfound elements. Perhaps this could be an option passed to the command to allow this or not.

Reproducible Example

Provided in https://github.com/cypress-io/cypress/issues/9032

index.html

<form>
    <select onchange="document.forms[0].submit()">
        <option value="1">First</option>
    </select>
    <input />
</form>

spec.js

it('this will fail but should not', () => {
    cy.visit('index.html')
    cy.get("select").select("First")
    // adding any wait time will make it pass
    // cy.wait(0)
    cy.get("input").type("Hallo")
})

Related issues

pkdriver proposal 💡 feature

Most helpful comment

All solutions here are at best hacky workarounds.
The Cypress spirit is to interact with UIs like an user would do.
This issue is indeed very technical and obviously an user is not supposed to know if an element is render one or multiple times, so as the test writer.

IMO, This re-query should be done systematically, without option to enforce the Retry-ability core-concept of Cypress.

For now, my only not-100%-safe workaround is a cy.wait(xxx) before any risky selector. It goes against core-concepts of this project.

All 87 comments

Is there a workaround available in the meantime? Perhaps writing a custom uncaught:exception handler?

Tracking this issue as it's affecting at least 20 of my automated scripts that contain an each loop to fail.

Yeah, I've experienced this a lot too, yet to find a decent solution...really love cypress but this issue annoys me!

Per https://github.com/cypress-io/cypress/issues/5743#issuecomment-622596999:

.click({force: true})

... is a decent enough solution, but it's vague without a comment.

@stevenvachon I tried that several times in several of the scripts and it still hasn't worked for me.

We ran into this issue so many times it was making our tests very flakey. We've started using the waitUntil command this week and has made our tests much more reliable, I'd recommend giving it a go. https://github.com/NoriSte/cypress-wait-until#readme

Per #5743 (comment):

.click({force: true})

... is a decent enough solution, but it's vague without a comment.

force may work but is not ideal here, since you're forcing your test to interact with an element the user may ultimately not be able to interact with (what if the element never gets reattached?). You want to wait until the element can be interacted with, not force interaction on a detached element. I currently have a cy.wait(5000) in our test, but that hack goes against the best practices of Cypress.

@FayP how can you use waitUntil with an each loop? here is one of my each loops that I like to loop through.

cy.get(".mx-auto > .checkbox-col > .form-check")
          .each(($el, index, $list) => {
              cy.wrap($el).click({force: true});
              cy.get(".col-md-4 > .actions-contianer > .btn-secondary").click();
              cy.get(".mb-0 > :nth-child(2) > .btn").click();
              cy.get(".jobs-list-nav > :nth-child(1) > .nav-link").click();
          })

@eabusharkh0 I'm not sure which element you're trying to target in that code snippet. But in theory you could target anything with a waitUntil. For example cy.waitUntil(() => cy.wrap($el))).click(); I would expect to work. Best bet is to give it a try.

So I managed to get it working (at least in few of my tests) with wait-until plugin:

cy.waitUntil(() =>
      cy.get('.someSelector')
        .as('someAlias')
        .wait(10) // for some reason this is needed, otherwise next line returns `true` even if click() fails due to detached element in the next step
        .then($el => Cypress.dom.isAttached($el)),
    { timeout: 1000, interval: 10 })

      .get('@someAlias')
      .click()

@TomaszG A plugin, a wait, an additional check in a loop, an additional selector. All I can say is.... yikes. Cypress is kinda shitting the bed here.

So I managed to get it working (at least in few of my tests) with wait-until plugin:

cy.waitUntil(() =>
      cy.get('.someSelector')
        .as('someAlias')
        .wait(1) // for some reason this is needed, otherwise next line returns `true` even if click() fails due to detached element in the next step
        .then($el => Cypress.dom.isAttached($el)),
    { timeout: 1000, interval: 10 })

      .get('@someAlias')
      .click()

Unfortunately, it does not work for me.
It works fine for the cy.get(...) chain but fails for the cy.get(...).find(...) chain because Cypress cannot find the parent element.

I solved my issue using the JQuery.click():

cy
  .get('[data-cy="user_row"]')
  .find('[data-cy="user_row_cell"]')
  .should('be.visible')
  .then((e) => {
    Cypress.$(e).click();
  })

It does not work for all cases but it works fine for my case. Maybe it will be useful to someone.

All solutions here are at best hacky workarounds.
The Cypress spirit is to interact with UIs like an user would do.
This issue is indeed very technical and obviously an user is not supposed to know if an element is render one or multiple times, so as the test writer.

IMO, This re-query should be done systematically, without option to enforce the Retry-ability core-concept of Cypress.

For now, my only not-100%-safe workaround is a cy.wait(xxx) before any risky selector. It goes against core-concepts of this project.

AngularJS and Angular users can use https://angular.io/api/core/Testability

We have something like this in our legacy AngularJS tests. I don't know if React has an equivilent api.

Cypress.Commands.add('waitUntilAngularStable', () => {
    const getAngular = () => cy.window().its('angular', {log: false});
    getAngular().then((angular) => {
        return new Cypress.Promise(resolve => {
            angular.getTestability('body').whenStable(() => {
                resolve();
            });
        });
    });
});

Used like

cy.get('.back-btn').click();
cy.waitUntilAngularStable();
cy.get('.container').should('not.be.visible');

Nothing works for me. Even chains fail.
Снимок экрана 2020-06-10 в 16 46 29

One viable alternative is to use Cypress.$ instead of cy.get. It's something we've used to great affect in our team. The side effect is that it bypasses a lot of the checks that come with using the official selection method.

Copy-paste the following to your ./cypress/support/commands.js file.

Cypress.Commands.add("get$", (selector) => {
  return cy.wrap(Cypress.$(selector)).should("have.length.gte", 1);
});

And now you should be able to click the button using the cy.get$ command:

cy.get$("#btnId").click();

This works because cy.wrap automatically retries when the .should assertion passes. If you wanted to explicity wait until the DOM element is attached, you can try the solution here: https://github.com/cypress-io/cypress/issues/5743#issuecomment-650421731

All solutions here are at best hacky workarounds.
The Cypress spirit is to interact with UIs like an user would do.
This issue is indeed very technical and obviously an user is not supposed to know if an element is render one or multiple times, so as the test writer.

IMO, This re-query should be done systematically, without option to enforce the Retry-ability core-concept of Cypress.

For now, my only not-100%-safe workaround is a cy.wait(xxx) before any risky selector. It goes against core-concepts of this project.

The time of a machine's interactions with elements on a site differ significantly than of a regular user's interactions. By a lot, in fact! That is why such things happen.

I am currently having the same issue with a <ul> list element. This particular list element updates its contents (<li> children) by the time a search box (text input element) receives input and basically acts as a search result suggestions list for the user.

As the user is typing into the searchbox, the suggestions refresh rather quickly on the server, probably on every character input, but does not manage to update in real-time for the user as well, which is why it takes a bit of time for the suggestions to refresh after the user is done typing. A user would typically wait a bit before the different suggestions calm down and stay in place, then click on the right one to advance.

This is why cy.wait(n); should not be ignored! Let's put the machine (test) into action now.

As the machine is done typing, the suggestions list might not have updated for the cilent (machine) instantly, however, the particular suggestion that meets the criteria is found at the bottom of the list, but would appear at the top right after the suggestions list finally updates for the client. The machine should now wait a bit until it is assumed that the list has been updated for the client and ready for its elements to be interacted with.
Otherwise, the machine would select the suggestion found at the bottom and by the time the suggestion has been "moved" to the top, the machine will try and interact with it (or click on it), but the element is not there anymore. It has been moved, but in reality, its current instance has been removed from the DOM and a new instance has appeared on the top of the list as the list has been fully updated for the client.

Sorry for my bad english, but I'm hoping I covered some benefits of cy.wait(n); :)
It is still not-100% safe, but is the safest approach if your assumptions are relatively correct.

@Uberwire that is not a valid use of Cy.wait. Cy.get has a built in wait so if your css selector is correct it will wait till it becomes the first element in the list.

I was not sure that my Cy ran synchronously, so I deleted my previous comment and rebuilt my tests. I separated actions and asserts to different describe/it sections and now it must work synchronously. But I get the error from time to time.
Also I tried force click, it adds more problems, than solves.

@Uberwire that is not a valid use of Cy.wait. Cy.get has a built in wait so if your css selector is correct it will wait till it becomes the first element in the list.

According to my context, cy.get gets called once on the object before it gets detached from the DOM, which terminates its "waiting". So, the point is to prevent getting the object until we assure that it will stay in place for an actual user to interact with it.

@Uberwire in your example, you should be able to wait for the element and position within the container since it should be deterministic that once you've entered X characters should it appear at the top of the list.

@Uberwire in your example, you should be able to wait for the element and position within the container since it should be deterministic that once you've entered X characters should it appear at the top of the list.

Safer and more practical approach in that case. Unless, the element an actual user is looking for gets found at the bottom of the list while another element that meets the same criteria gets found at the top of the list before the list fully refreshes, getting caught in the get method before the actual element we want.

The user can stop typing mid-way after seeing the desired result at the bottom of the list and interact, but that is not what is being tested in my case.

cy.contains('elementToRequery') seems to work for us, since we rely a lot on loading components asynchronously. 🥳 this doesn't work however in combination with cy.get() (as in cy.get.contains('elementToRequery')

more on this behaviour here

I have the same problem and I have been days without progress, any solution?

 cy.get('[data-cy=acceptButton]').click()

it's not clicking

Same problem even if using the waitUntil library:

cy.get('[data-cy=acceptButton]').click({ force: true })
cy.waitUntil(  () => cy.get('[data-cy=acceptButton]')  ).click()
cy.waitUntil(  () => cy.get('[data-cy=acceptButton]')  ).click({ force: true })

element is DETACHED from the DOM (not always, but randomly)

How can we trust the cypress test if it doesn't work as expected?

cy.get('[data-cy=acceptOfferButton]').as('acceptOfferButton')
cy.get('@acceptOfferButton').should('be.visible')
cy.get('@acceptOfferButton').click({ force: true })

all commands pass successfully except last, sometimes is clicking but most of the time not.

How can we work with react component if the framework re-rendendered asynchronously?

Thanks @marmite22 for your solution. It helped me to resolve the
"CypressError: Timed out retrying: cy.click() failed because this element is detached from the DOM." error message for Angular2+.

Here is the solution I came with :

export const waitUntilAngularStable = () =>
  cy
    .window()
    .invoke("getAllAngularRootElements")
    .then(ngRootElements => {
      cy.window()
        .invoke("getAngularTestability", ngRootElements[0])
        .then(
          testability =>
            new Cypress.Promise(resolve => {
              testability.whenStable(() => {
                resolve();
              });
            })
        );
    });

Cypress.Commands.add("waitUntilAngularStable", waitUntilAngularStable);

Inside your test :
cy.waitUntilAngularStable();

@cm0s This is an interesting idea. How do you know that the click will take place when waitUntilStable is still true? Is it possible to chain the check immediately before the click?

@bcole-dbg, what currently do is juste that :

    // before waitUntilAngularStable, do stuff that my rerender some angular elements and make it "unstable"
    cy.waitUntilAngularStable();
    // DOM elements have finished rerendering we can trigger our click event
    cy.get(".my-button").click();

In my understanding, the click event will not be triggered before the promise in the waitUntilAngularStable() is resolved, so we should be good. But perhaps there is something I don't understand, on how Angular cycles work with Cypress. For now, it seems to work with my use case. I will be happy to hear if others try this and can improve it.

Used command solution posted by wintonpc with success!
https://github.com/cypress-io/cypress/issues/5743#issuecomment-650421731

Any word on this? I was using a test spec and my new way of selecting users in a table worked great, but once i put it to use in my spec proper things started to detach. My guess is it's moving from one IT to the next and when it hits the new IT the item detaches.

This is really annoying, when a test becomes flakey when using cy.contains('td', 'Automated User').siblings().eq(0).children().eq(0).click({force: true}); as the test gets detached by the second eq. If i use force:true the click may or may not actually check the checkbox, even though it "passes". It's clearly not clicking the checkbox.

Is there any way to add this kind of re-query ability before clicking through a custom command right now? Would help to remove a lot of flake from our tests.

I'm suffering the same fate when testing React code... Intermittent failures. I added xhr waits which alleviated the problem somewhat, but still I occasionally see the error. This is really quite a big issue, it'd be great to have an update on whether you have intentions for fixing it.

Guys, how do you test your code until this issue is done? This solution only helps in some cases. 😢

This is making our CI pipeline completely unstable after almost 2 years. We are using a custom command :

Cypress.Commands.add('getAttached', (selector, { contains } = {}) => {
  const getElement = typeof selector === 'function' ? selector : ($d) => $d.find(selector);
  let $el = null;
  return cy
    .document()
    .should(($d) => {
      $el = getElement(Cypress.$($d));
      /* eslint-disable no-unused-expressions */
      expect(Cypress.dom.isDetached($el)).to.be.false;
      if (contains) {
        expect($el).to.contain(contains);
      }
    })
    .then(() => cy.wrap($el));
});

But this is obviously not perfect - as its possible that the element can become "detached" again in the period between the initial assertion and actually making the click

We have resorted to skipping tests, desperate to have this functionality implemented!

I have described a situation when the element gets detached during the test and how to solve it in this particular case in https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/

I would love to see more reproducible examples of people running into this issue.

@bahmutov here's a very simple Next.js example that I ran into, where I:

  1. Used cy.visit() to visit the homepage
  2. Used cy.get(selector).click() to get a header link and click on it

For step 2, I needed to use { force: true } to get non-flaky tests without the "detached from DOM" error.

https://github.com/upleveled/next-js-example-may-2020/blob/d4225fed4ba25be4bb067190ced610f62e999ee6/cypress/integration/navigation.spec.js#L8-L10

I don't recall having this issue with integration / end to end tests before.

I suppose there are other methods for waiting for a specific element to be visible, but I guess from Cypress' "automatically retries" philosophy, it should just work out of the box?

Maybe I'm doing something wrong, but I would suggest expanding the "pit of success" here if possible...

I have described a situation when the element gets detached during the test and how to solve it in this particular case in https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/

I would love to see more reproducible examples of people running into this issue.

Thank you for the article @bahmutov but it describes a very simple application architecture.

You say:

We want the test to always wait for the application to finish its action before proceeding.

This makes sense in theory, but it is not always simple to "wait" for the application to finish its action. In the case of a complex web application there are many possible sources of a re-render. In your blog you solve the problem for when we have a simple request -> response -> render flow.

But what about an app that updates many times during this series, e.g to return a loading state.

In our application we have 100s of requests firing in the background, each of these requests updates the state of some component, and causes it to re-render.

So lets take it from a user perspective - wait for the DOM to look as you'd expect

cy.contains('Async data - Thing i'm trying to click')
   .click()

In your blog, you mention this as the solution, ie. waiting until you can "see" the thing you are trying to click.

But in an application with 10s or 100s of requests continuing in the background, Cypress will still view the element as detached, even though the contains passed.

As for a reproduction, you can try a react application with apollo-client. Use their useLazyQuery hook to make 5 or 6 requests at various points in the tree, it will throw Cypress very much out of whack.

If we could wait for an idle network this would alleviate a lot of our flakeyness

But the ideal solution from our perspective is to make Cypress retry if something is detached. From a user perspective I don't see the use in this error - if we can see something, we expect to be able to interact with it.

@dan-cooke is this because the application is hydrating? Can you make a public repo showing this? Seems something similar to https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ or https://www.cypress.io/blog/2018/02/05/when-can-the-test-start/

@bahmutov wow.. that first article is very useful. I had not thought about using cypress-pipe to retry a click.

Our application is hydrating yes, this is very possibly the reason.

Thank you for that! I will have a play around and get back to you - if I have time to create a reproduction repo I will!

I would love to see specific reproducible examples that are flaky that I can run to explain how to make sure the test runner and the app do not "race" against each other. For now I have described various situations in these blog posts

@bahmutov After some trial and error we've gotten to a tentatively stable place in our CI using mostly pipe, so thanks for that tip! Our project is very much like @dan-cooke 's, lots of hydrating, loading states and API requests -- we managed to avoid using cy.wait in most places except one, but perhaps with some more digging we could do away with it.

@bahmutov did you see the example above here? https://github.com/cypress-io/cypress/issues/7306#issuecomment-668060445

I suppose I would suggest that the behavior of the pipe solution be the default when using normal Cypress chaining + commands (eg. my example of cy.visit() and then cy.get('[data-cy="header-link"]').click()).

Maybe this means adding retries up until the timeout for the cy.get in Cypress core (if an element is found to be detached). I guess by identifying a few common patterns that cause this and then adding retries, this would probably effectively resolve the issue without getting developers to write custom solutions for their problems.

I don't know the details - maybe what I'm suggesting is technically complex, but I think expanding the pit of success and reducing the amount of people possibly running into the detached errors is probably worth it. The "it just works" behavior reduces the barrier for people to write tests.

Most people will not find the blog posts (many will also not even search for elegant solutions like this), and over the lifetime of the open source project it will create a lot of effort by everyone to deal with this issue.

@bahmutov After some trial and error we've gotten to a tentatively stable place in our CI using mostly pipe, so thanks for that tip! Our project is very much like @dan-cooke 's, lots of hydrating, loading states and API requests -- we managed to avoid using cy.wait in most places except one, but perhaps with some more digging we could do away with it.

Can you show any of your tests please?

@karlhorky I forked your example to play with it locally. I know the source of flake, and it is due to the navigation happening during or after your clicks. The test only clicks around and really checks for the wrong thing there :) You must alternate your commands with assertions to make sure you are

  1. finding the right thing and not accidental text
  2. are on the expected page before clicking to navigate to the next page

See https://docs.cypress.io/guides/core-concepts/retry-ability.html#Alternate-commands-and-assertions for general advice. In your case

// BAD
context('Navigation', () => {
  it('can navigate around the website', () => {
    cy.visit('http://localhost:3000');

    cy.get('[data-cy="header-link-about"]').click();
    cy.get('main:contains("About")');

    cy.get('[data-cy="header-link-users"]').click();
    cy.get('main h1:contains("Users")');
  });
});
// GOOD
// alternate commands and assertions
// to make sure the test runner does not
// run ahead of the application
context('Navigation', () => {
  it('can navigate around the website', () => {
    cy.visit('http://localhost:3000');

    cy.get('[data-cy="header-link-about"]').should('be.visible')
      .click();
    cy.location('pathname').should('match', /\/about$/)
    cy.contains('main h1', 'About').should('be.visible')

    cy.get('[data-cy="header-link-users"]').click();
    cy.location('pathname').should('match', /\/users$/)
    cy.contains('main h1', 'Users').should('be.visible')
  });
});

I will write a blog post or two about this, follow Cypress on twitter and watch https://www.cypress.io/blog/ for postings

@bahmutov After some trial and error we've gotten to a tentatively stable place in our CI using mostly pipe, so thanks for that tip! Our project is very much like @dan-cooke 's, lots of hydrating, loading states and API requests -- we managed to avoid using cy.wait in most places except one, but perhaps with some more digging we could do away with it.

Can you show any of your tests please?

Unfortunately it's a private repo :/

Ok thank you for the response @bahmutov.

Regarding my other point about "pit of success", do you see any of these things as a potential deficiency that could be fixed in Cypress itself?

Ideally, users write tests how they expect them to work (probably based on the Cypress docs and also previous integration testing experience) and Cypress just works - the "detached" error doesn't happen. But it seems like complexities of how applications are built today lead to this happening a lot.

If the Cypress team doesn't believe that any of these classes of issues are something to be fixed in Cypress itself (which seems like it would be the best case), maybe the "pit of success" could be achieved another way.

Some ideas:

  1. Improving heuristics around error messaging within Cypress to point devs in the right direction of writing tests how Cypress wants
  2. Adding to / improving / reorganizing the docs so that these types of problems are surfaced early and patterns shown to deal with them (this seems like it happens commonly)
  3. Creating a "linter" or similar for Cypress tests to check for bad patterns (could call it "baas" - Bahmutov As A Service 😅)

We definitely believe that there should be a better strategy to handle re-querying DOM elements that are found 'detached' from the DOM. This is why this issue exists and is still open - it is work we intend to do in the future.

Until that work is prioritized, however, we want to offer strategies to assist in testing applications where this error in encountered, such as in https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/

Teaching best practices for writing predictable tests is an ongoing challenge that we're always working on in several ways such as in-product guidance, docs, blogs, webinars, etc.

We do have an eslint plugin specifically for Cypress btw. This is open source. https://github.com/cypress-io/eslint-plugin-cypress

@jennifer-shehane good to hear, thanks!

Didn't know about the ESLint plugin, cool! 💯

I have described the solution to the flake problem in comment https://github.com/cypress-io/cypress/issues/7306#issuecomment-668060445 in this blog post https://www.cypress.io/blog/2020/08/17/when-can-the-test-navigate/

bad:
cy.get('[data-cy="header-link-about"]').click();
good:
cy.get('[data-cy="header-link-about"]').should('be.visible')
.click();

Would it be possible to have a config option for .click() chained onto a selector with .should('be.visible') so that the user wouldn't have to find these cases. Would this slow down test or change the test significantly? We're basically getting an error when an element is not visible and if the solution is to add an assertion of .should is it really very different or to make it a silent assertion?

In the case of anything with a chained .click() we're basically saying that it should be visible -- we already get a hard error when the element is not interactive so wouldn't having a way to add .should('be.visible') make this work? Asking for a friend before they write a babel plugin that does this automatically.

Upon upgrading to Angular 10 from Angular 8, most of our tests started failing with this detached from the DOM error. Really a bummer. I've since started using the waitUntil package and it seems to work but it doesn't feel good lol.

Rather executing tests cypress starts next test execution and the cypress command for type and submit keeps showing the progress in the running state over the left panel of the window.

from package.json:
"devDependencies": {
"cypress": "^5.2.0"
}

and tried the retry configuration in cypress.json:

"retries": {
"runMode": 2,
"openMode": 3
}

The error I can see is -
"CypressError
cy.type() failed because this element is detached from the DOM."

There's one additional issue I observed was:

Cypress tests were reloading the same URL all over again especially in the last written test of spec file.
This is usual with every test that is present at the very end of the spec file.
Any idea what can I do to stop this reloading activity?

Hi @bahmutov , I been following your post: https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ to solve the detached issue found in the project. (i checked if the option is visible since there is not search filter in the test case)
the code:

  1. cy.get('.mat-option').then((opts) => {
    const selectedOpt = opts[0];
    cy.contains('.mat-option', selectedOpt.innerText).should('be.visible').click()
  cy.get('[aria-label="account-list"]').should('have.value',selectedOpt.innerText)
});
  1. cy.get('.mat-option').then((opts) => {
    const selectedOpt = opts[0];

    cy.wrap(Cypress.$(selectedOpt)).should('be.visible').click();

    cy.get('[aria-label="account-list"]').should('have.value',selectedOpt.innerText)
    });

in the cypress test, it found the option visible and the option displays as expected.
However, the cypress still complain on the the option click statement.
(Timed out retrying: cy.click() failed because this element is detached from the DOM.)
thanks
cypress complain visible element detached
cypress complain visible element detached-using cy contain

We definitely believe that there should be a better strategy to handle re-querying DOM elements that are found 'detached' from the DOM. This is why this issue exists and is still open - it is work we intend to do in the future.

Until that work is prioritized, however, we want to offer strategies to assist in testing applications where this error in encountered, such as in https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/

Teaching best practices for writing predictable tests is an ongoing challenge that we're always working on in several ways such as in-product guidance, docs, blogs, webinars, etc.

We do have an eslint plugin specifically for Cypress btw. This is open source. https://github.com/cypress-io/eslint-plugin-cypress

I'm not sure "writing appropriate tests" is correct in each case as I have this problem with this simple thing.

cy.get('input[name="extension"]', { timeout: 60000 }).type('lobby{enter}')

about 50% of the time it has disconnected from DOM by the time it types.

Could you provide reproducible example showing the code where typing fails so often?

Sent from my iPhone

On Oct 5, 2020, at 17:43, Jason Gallivan notifications@github.com wrote:


We definitely believe that there should be a better strategy to handle re-querying DOM elements that are found 'detached' from the DOM. This is why this issue exists and is still open - it is work we intend to do in the future.

Until that work is prioritized, however, we want to offer strategies to assist in testing applications where this error in encountered, such as in https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/

Teaching best practices for writing predictable tests is an ongoing challenge that we're always working on in several ways such as in-product guidance, docs, blogs, webinars, etc.

We do have an eslint plugin specifically for Cypress btw. This is open source. https://github.com/cypress-io/eslint-plugin-cypress

I'm not sure "writing appropriate tests" is correct in each case as I have this problem with this simple thing.

cy.get('input[name="extension"]', { timeout: 60000 }).type('lobby{enter}')
about 50% of the time it has disconnected from DOM by the type it types.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

.click({force: true}) works for me but I don't know why! :smile:

@bahmutov turns out putting in cy.wait(5000) fixes it. seems like the DOM needs a few seconds to settle after each page load

This is not a solution. And always waiting 5 seconds for every single click in e2e tests are really bad idea.

How long I need wait to finish integration tests? A week?

If the application is loading for up to 5 seconds and does not set any observable effects where the test “knows” when it is ready to process the input - what can we do?

Sent from my iPhone

On Oct 7, 2020, at 12:06, Adam Stachowicz notifications@github.com wrote:


This is not a solution. And always waiting 5 seconds for every single click in e2e tests are really bad idea.

How long I need wait to finish integration tests? A week?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

This is not a solution. And always waiting 5 seconds for every single click in e2e tests are really bad idea.

How long I need wait to finish integration tests? A week?

I absolutely agree with you, and it is a pity that such a critical issue has been in an "unresolved" state for almost half a year.
As I said before, I solve the problem with jQuery click() instead of Cypress click() (in my case, the problem was related with the vuetify table component headers), but I just can't imagine how other people deal with it when the described way does not help.

The only solution that comes to mind is to fork Cypress and patch click() specifically for your purposes. Of course, this is not the best idea, but in my opinion the only guaranteed solution to the problem.

You could also just overwrite cy.click command with your logic

Sent from my iPhone

On Oct 7, 2020, at 12:32, Sergey Shaldanov notifications@github.com wrote:


This is not a solution. And always waiting 5 seconds for every single click in e2e tests are really bad idea.

How long I need wait to finish integration tests? A week?

I absolutely agree with you, and it is a pity that such a critical issue has been in an "unresolved" state for almost half a year.
As I said before, I solve the problem with jQuery click() instead of Cypress click() (in my case, the problem was related with the vuetify table component headers), but I just can't imagine how other people deal with it when the described way does not help.

The only solution that comes to mind is to fork Cypress and patch click() specifically for your purposes. Of course, this is not the best idea, but in my opinion the only guaranteed solution to the problem.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

Agreed with you @serge-shaldanov
My team was planning to migrate our existing e2e tests to cypress couple months ago but this is blocker issue for me to decide whether I should use cypress or not.
If the issue is not fixed, we do not want to waste time on unstable tests (documents/blogs post with simple demo don't help at all)

I'v agree with you! Same problem for me. All my e2e tests are instable. I want a solution too.
cy.wait is a bad solution.

For me I wait xhr request, and I resolve issue. But I don't know before cy.click the request xhr launch. And I'm not going to ask myself every time I click what the request is, how many there are, if in the future we add one...Etc....

A solution, for my case, may be to make the click when the environment is considered stable (waitUntilallrequest?).
but this may not be true for everyone.

(sorry for my bad english, i hope you understand me :))

I have an application with pages that are re-rendered regularly, without user interaction to show any external modification to the current user.
The "detached" problem happens a lot.
If i use command chains, it happens every single time.
If do a get() for every type(), clear(), or click(), it happens once every few test runs
I can't use wait because the application is never done loading.
@jennifer-shehane 's article does not tackle my problem. I can't wait for the application to be done with its processing since it's never done : https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/
Right now the only think I can think of is asking the devs if they can implement a way to stop the real-time refreshs or maybe write somewhere when it was last updated.
This feature would be very appreciated !

Can you please give us such example of constantly self-updating page and the test you are trying to run against it?

Sent from my iPhone

On Oct 13, 2020, at 16:39, Daryl PIFFRE notifications@github.com wrote:


I have an application with pages that are re-rendered regularly, without user interaction to show any external modification to the current user.
The "detached" problem happens a lot.
If i use command chains, it happens every single time.
If do a get() for every type(), clear(), or click(), it happens once every few test runs
I can't use wait because the application is never done loading.
@jennifer-shehane 's article does not tackle my problem. I can't wait for the application to be done with its processing since it's never done : https://www.cypress.io/blog/2020/07/22/do-not-get-too-detached/
Right now the only think I can think of is asking the devs if they can implement a way to stop the real-time refreshs or maybe write somewhere when it was last updated.
This feature would be very appreciated !


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

@bahmutov I have same issue with Create New button in scrollable list click. Wait doesn't help as Create New button already rendered, but on the next operation cy.contains("Create New") Cypress finds element and makes additional scroll and then click. Scroll makes element re-rendered and de-attached and next click() operation will fail

cy.wait(1000)
cy.contains('Create New').click()

Log:
<button type="button" test_id="$.tag.member-tag-create" class="btn btn-secondary btn-sm" style="margin: 8px 8px 8px 0px;">Create New</button>

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

The previous command that ran was:

cy.contains()

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

I am facing the same issue over here with React components.
It is sad, it is annoying me a lot.
I am gonna try some of the workarounds you guys wrote over here.

This is frustrating. In old timey Selenium I would do like

def retry(your_method, max_attempts=3): 
   for attempts in range(max_attempts):
        try:
          return your_method()
        except DetachedException as e:
            logger.warning(e)
    raise SomeException

retry(method_that_looks_up_and_clicks)

I was kind of hoping something like that would be baked into Cypress.

What about adding a new option or/and cli flag called "allowDetached" or "retryQuery" something similar? to allow component detached milliseconds ago be clicked anyways?

@jennifer-shehane: are you sure #9032 is a duplicate of this? This here sounds more like a fundamental problem with Cypress, whereas what I reported there was a regression, that is dependent on the browser version used. It only happens in Chrome builds >83, and it still does NOT happen in current Firefox.

We simply installed Chrome 80 on all our machines, which solved this particular problem for us. I doubt that would be the solution here.

@marc-guenther I think either way, the solution laid out in this issue would address the problem. But, the Chrome component is interesting and I can bring it up with our team.

Maybe I do not understand the original problem, but it still sounds quite different to me.

From what I understand of this ticket, the problem is when someone holds on to an element, after it has become detached:

cy.get('my-button').click().should('be.visible')

Here, the button is clicked, causing the button to become detached (because the page reloads, or the app rerenders the page), and the problem then is, that I keep holding on to that button for the assertion.

Whereas what I describe in #9032 is very different:

cy.get('my-select').select('some option') // which submits the page
cy.get('my-select').should('be.visible')

Here I do not hold on to the original element, but search for it again. This is perfectly fine behaviour, which I do all over the time in Cypress, I perform an action, which submits the page (the submitting action is always the last in a cy... chain), and then the next command is a fresh cy... command.

Like this:

cy.get('my-button').click()
cy.get('my-button').should('be.visible')

This one works as expected, as did the previous example for quite a while, since somehow Chrome update broke it.

I was under the impression that this is standard Cypress behaviour, after an action submits a page, that the next command does NOT accidentally target the old page, but waits for the next page to load? I assume that in my example, the second line get's triggered too early, and finds the element from the old page, before the submit() on the <select> option is fired?

I should maybe add, that we have a very old-school app, everything is rendered on the server, nothing asynchronous, no re-rendering, DOM manipulation, or anything, just plain old html. An autosubmitting <select> is already the height of interactive Javascript magic for our developers. So almost all of our tests look like these boring sequences from above.

Maybe that's why I don't understand any of the fancy examples given here. There are so many people who get hit by this, can someone provide an easy example which exposes this problem?

EDIT: I agree that this issue will probably fix mine as well, I just think the causes are different.

@marc-guenther Why is your issue only happening in Chrome 81+? Likely Chrome changed the way their page load fires so that it now happens asynchronously. Someone would have to dive into the Chrome commits to verify that completely, but that's likely the case in this issue.

What seems to be happening:

cy.get('my-button').click()
cy.get('my-button')
// PAGE LOAD FIRES
.should('be.visible') // we are asserting on an element that was removed

This is why adding an arbitrary wait in between helps your issue, it gives just enough time for the page load to fire at a different time.

cy.get('my-button').click()
cy.wait(0)
// PAGE LOAD FIRES
cy.get('my-button')
.should('be.visible') // passes

Why is this issue so difficult to just fix?

This original example actually highlights the difficulty with addressing this issue.

<form>
    <select onchange="document.forms[0].submit()">
        <option value="1">First</option>
    </select>
    <input />
</form>
it('this will fail but should not', () => {
    cy.visit('index.html')
    cy.get("select").select("First")
    cy.get("input").type("Hallo")
})

If we implement the 're-query elements that are detached logic', here are a few scenarios of how that may play out:

Input element was rerendered just as it was before

  • Page load event fires after typing the first H character of .type(), so that our entire page has now rerendered.
  • Cypress re-querys for an input element, the single input element still exists on the page.
  • Cypress has to have maintained the state of which characters were already typed to know to type 'a' next.
  • Page load event fires after typing 'a' character of .type(), so that the input we were typing into has now rerendered on the page.
  • Repeat until all characters are typed.

New input elements rendered

  • Page load event fires after typing the first H character of .type(), so that our entire page has now rerendered.
  • Cypress re-querys for an input element. This time, multiple input elements exists on the page. Say our onchange event code actually adds new inputs, which match our original selector.
  • Cypress has no idea which input element we're supposed to continue typing into, since there are 2 completely new elements on the page that match the original cy.get() selector. Cypress would have to error as it does today 'Element is detached from the DOM'.

Input element removed from the DOM completely

  • Page load event fires after typing the first H character of .type(), so that our entire page has now rerendered.
  • Cypress re-querys for an input element. This time no input element is found. Say in our onchange event our code said to remove the input altogether.
  • Cypress has no idea where to continue the typing of the rest of the characters and would have to error as it does today 'Element is detached from the DOM'.

This is worrisome. To quote myself:

I was under the impression that this is standard Cypress behaviour, after an action submits a page, that the next command does NOT accidentally target the old page, but waits for the next page to load?

From your comment above, I conclude that this is NOT true. Which, imho is a huge issue. This was one of the main selling points why I chose Cypress over _Selenium_, that I do NOT have to worry about these kinds of timing issues. Actually, Cypress itself is advertising exactly that on its web site: Cypress is not flaky

It's not really clear from my last post, but this:

cy.get('my-button').click()
cy.get('my-button').should('be.visible')

still works perfectly fine in current Chrome, where as this:

cy.get('my-select').select('some option') // which submits the page
cy.get('my-select').should('be.visible')

doesn't. If now Chrome team tomorrow decides to change page loading of the first case as well, that would mean most of our tests are broken.

As much as I would hate to loose the graphical testrunner, but we are still in the beginning of our E2E testing journey, and given problems like this, maybe it makes sense to bite the bullet and re-evaluate _Testcafe_. They of course advertise exactly the same thing: _"Create stable tests (and no manual timeouts): TestCafe automatically waits for page loads and XHRs before the test starts and after each action."_

image
the error as following:

Timed out retrying: cy.click() failed because this element is detached from the DOM.
<span class="mat-option-text">(UTC-10...</span>
Cypress requires elements be attached in the DOM to interact with them.
The previous command that ran was:
  > cy.should()
This DOM element likely became detached somewhere between the previous and current command.

the testcode is as the following statement:
cy.get('mat-option').contains(selectedOpt.innerText).should('exist').click();
I just wondering how can we make sure the element is not detached if the should exist not help

@FrankyXie I tend to get better luck using Cypress.dom methods:

cy.get('.mat-option')
    .contains(selectedOpt.innerText)
    .then($el => Cypress.dom.isAttached($el))
    .click()

I don't know if this solves the issues with page loads, but it seemed to have helped when I added assertions between page transitions:

Cypress.Commands.add('awaitPageLoad', () => {
    cy.window().then((win) => {
        win.pageReady = true;
    });
    cy.window().should('have.property', 'pageReady', true);
});

@FrankyXie I tend to get better luck using Cypress.dom methods:

cy.get('.mat-option')
    .contains(selectedOpt.innerText)
    .then($el => Cypress.dom.isAttached($el))
    .click()

Thanks a lot.

This weird solution worked for us.

cy.get('.fc-custom-theme__event')
        .contains('Hello')
        .then(([e]) => e.click())

>

Thanks a lot.

No problem. I made a small mistake - "should" is better to use than "then", so the test retries.

cy.get('.mat-option')
    .contains(selectedOpt.innerText)
    .should($el => Cypress.dom.isAttached($el))
    .click()

Thank's i try it tomorrow!!!!!

Could someone please provide a reproducible example? All I see here are "fixes" for stuff that never happens to me... It might it easier to know what we are actually talking about.

Could someone please provide a reproducible example? All I see here are "fixes" for stuff that never happens to me... It might it easier to know what we are actually talking about.

In my case, ( I thought i had shared this earlier), it takes a minute for the dom to settle. so when cypress runs, it grabs the element it's looking for, and before it can "click" it, it is removed from the dom - because that element is re-rendered.

by waiting 1 second, it gives it time to settle and it works fine (before querying for the element). so, it's not something that i can tell you how to reproduce because it's react that is re-rendering the element.

However, just knowing why it is happening, should be enough to know what is happening. As far as a solution goes, I'm not sure what would be best. perhaps, requery if it has been removed?

Could someone please provide a reproducible example? All I see here are "fixes" for stuff that never happens to me... It might it easier to know what we are actually talking about.

cy.get('my-select').select('some option') // which submits the page
cy.get('my-select').should('be.visible')

Just confirming, in your example, selecting 'some option' causes the page to re-render? If so, adding a window attribute during page render might help to assert page refresh? Sorry if I am misunderstanding your issue.

cy.get('my-select').select('some option')
cy.window().then((win) => {
    win.pageReady = true;
});
cy.window().should('have.property', 'pageReady', true);
cy.get('my-select').should('be.visible')

To assert if an individual element renders, I attach .should($el => Cypress.dom.isAttached($el)) to the element before performing any actions on it. I've mainly used it on click/type, not as much with select, but it should be similar to my previous example.

One of our engineers, @bahmutov, wrote a blog post describing the behavior and workarounds for the example where the element rerenders upon form submit. It may be a worthwhile read for others in this thread https://www.cypress.io/blog/2020/11/17/when-can-the-test-submit-a-form/

So I managed to get it working (at least in few of my tests) with wait-until plugin:

cy.waitUntil(() =>
      cy.get('.someSelector')
        .as('someAlias')
        .wait(10) // for some reason this is needed, otherwise next line returns `true` even if click() fails due to detached element in the next step
        .then($el => Cypress.dom.isAttached($el)),
    { timeout: 1000, interval: 10 })

      .get('@someAlias')
      .click()

thank u , your solution really helped!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rbung picture rbung  ·  3Comments

jennifer-shehane picture jennifer-shehane  ·  3Comments

carloscheddar picture carloscheddar  ·  3Comments

simonhaenisch picture simonhaenisch  ·  3Comments

Francismb picture Francismb  ·  3Comments