cypress
cypress copied to clipboard
Re-query elements that are found 'detached' from the DOM
Current status
This is currently in progress. See https://github.com/cypress-io/cypress/issues/7306#issuecomment-1182059194, https://github.com/cypress-io/cypress/issues/7306#issuecomment-1182427284 and https://github.com/cypress-io/cypress/issues/7306#issuecomment-1188245931 for a brief description of the plan and updates on current status.
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

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
- https://github.com/cypress-io/cypress/issues/6215
- https://github.com/cypress-io/cypress/issues/5743
- https://github.com/cypress-io/cypress/issues/4946
- https://github.com/cypress-io/cypress/issues/4414
- https://github.com/cypress-io/cypress/issues/3670
- https://github.com/cypress-io/cypress/issues/9032
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.
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.