cypress: cy.click() failed because element is effective width and height of 0x0, even though it is not.
Hi there,
I’m encountering some flaky tests due to an element not being considered visible by Cypress, I don’t understand what makes an element visible or not visible by Cypress and was hoping someone could clarify it for me.
Since version 0.20 tests randomly decide to fail with this message: This element '<a.sc-cqpYsc.cmkxre>' is not visible because it has an effective width and height of: '0 x 0' pixels.
.
However, when I inspect it is definitely has a size bigger then 0 by 0, as you can see in the screenshots.
Failing test due to click timeout:
Item highlighted by Cypress:
Chrome inspect of the element:
Now, I can “fix” this by adding { force: true }
to the click, but I would like to understand why this results in flaky behaviour (both headless and using the cypress UI).
- Operating System: locally: OSX 10.12.6 CI: Debian Stretch (Docker container node:6.11.3-stretch)
- Cypress Version: 0.20.0 & 0.20.1
- Browser Version: Version 61.0.3163.100 (Official Build) (64-bit) (locally)
Is this a Feature or Bug?
Bug?
Current behavior:
cy.click() failed because element is not visible
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 106
- Comments: 117 (30 by maintainers)
Commits related to this issue
- fix(errors): better error message when element is detached during actionability (address #695) — committed to cypress-io/cypress by kuceb 5 years ago
- fix(errors): better error message when element is detached duri… (#4945) * fix(errors): better error message when element is detached during actionability (address #695) * fix various failing test... — committed to cypress-io/cypress by kuceb 5 years ago
- fix(errors): better error message when element is detached duri… (#4945) * fix(errors): better error message when element is detached during actionability (address #695) * fix various failing test... — committed to grabartley/cypress by kuceb 5 years ago
I’ve tried all of the workarounds mentioned here:
.should("be.visible")
.invoke('width').should('be.greaterThan', 0)
cy.wait(500)
No matter what I try, it seems to fail about 20% of the time with the “effective width and height of 0x0” error.
My project is an AngularJS SPA and while the element being clicked is dynamic in the sense it is conditionally added to the DOM. when running in electron browser mode I can clearly see the element and it still fails!
Please prioritise this bug as it’s causing our CI process to regularly fail.
@peternye You should be able to write an assertion that checks on non-zero width where the tests will not move forward until it is greater than non-zero:
You could basically assert against any property of an element, like
innerWidth
andouterWidth
also.See Assertions
We are facing this issue too (a lot) and this is a basic requirement for a UI testing tool to avoid this kind of issue.
Seems like lot of people are facing the same issue too and can’t find a reliable solution to be confident with cypress tests.
What are the plan to troubleshoot and fix this ?
No, using wait or force:true are not acceptable solutions. We need that a .click() works fine when the should(‘be.visible’) is OK.
Here the code that fails randomly and that should not be possible to have this use case :
No work has been done specifically on this issue.
@jennifer-shehane thank you for the suggestion! Can you clarify, why
cy.get('#query-btn').invoke('width').should('be.gt', 0)
won’t fail, butcy.get('#query-btn').click()
fails? What I mean is that if Cypress is smart enough to do retries / reselects of the element when assertion doesn’t work, why cannot it do the same for the actions? Of course, if you had an action earlier in a chain, it should fail (no recovery if action was done on element and then it became detached, i.e. if it’scy.get('#query-btn').click().parent()
and afterclick()
element is detached), but if everything in the chain were just selectors, Cypress could’ve retried them all if by the time we’re doing first action element becomes detached. Or maybe I don’t understand how assertions work in Cypress…I explicitly assert that the element is visible, yet the test fails claiming afterwards that this very element is not visible.
At Airbnb we have a React app that uses the Progressive Hydration technique discussed in this presentation: https://www.youtube.com/watch?v=k-A2VfuUROg
As such, we have chunks of our DOM that become hydrated in idle callbacks after the page load event has fired. Due to the way that Cypress works, this has caused quite a bit of flake in our tests because elements often end up getting swapped out between the
.get()
and the next chained method (e.g. between.get('button')
and.click()
herecy.get('button').click();
).The naive approach to work around this is to add
cy.wait(n)
calls all over the place with arbitrary timeouts. This is not satisfying because if you pick a large timeout, you make your tests unnecessarily slow, and if you don’t pick a high enough timeout then you still end up with flake. As the product changes over time, these timeouts may need to be adjusted, and it is hard to know when to do that.To make this better, we’ve developed a custom Cypress command called
waitUntilSettled
. This will install a MutationObserver and recursively callrequestIdleCallback
on the page. After the idle callback resolves and the DOM has not yet been mutated, we proceed with the test. We are just starting to use this and it seems to be helping us, so I thought I would share the code here.https://gist.github.com/lencioni/7ba04e0f92558f49454c19c44cf3bc5c
We are currently only testing in browsers that support requestIdleCallback, so it is possible that when we roll this out to other browsers where we have to polyfill that, we will need to adjust this some–e.g. maybe we should wait for a couple of consecutive non-mutated DOM checks before moving forward.
I hope this helps anyone else running into these types of issues. In the future, it might be nice if Cypress was enhanced to do this style of waiting when visiting routes, so we could avoid dropping these
cy.waitUntilSettled()
calls in a bunch of places.Happens to us too, nothing works except
cy.wait(100)
.I have the same problem with a flaky behaviour. I know the element which i try to click is not removed from the dom, but there is an automatic scrolling of the viewport when the element is displayed. I get the 0x0 pixels error, but even with @jennifer-shehane workaround, the test runner is not able to click it.
With those tests :
First two “get” succeeds, but not the third one.
I don’t think that there is a “scroll end” event i could hook to, so i don’t know how i could guard this step without using cy.wait
I have the same issue. We are using angular. Some times the click registers and the other times does not.
This has also been a real issue for us. We’re using React and I’m still struggling to find the right combo of xhr waits for each page to get our tests reliable. But this thread has been helpful to understand the cause 👍
The root cause is the app you are testing is changing the dom underneath - so cypress gets an element but before it can click it, the app has removed it from the dom and re rendered. Sometimes this can be solved by asserting state before the click to ensure no more dom changes will occur and the app is stable. Most solutions here just add a tiny delay which make it more likely that your app under test has finished (the wait for visible may be lucky enough to catch the state and retry or it may miss it - depending on timing) IMO cypress should retry clicks if the dom element is removed, before it dispatches the event. I’ve solved this all of the time in our app by proxying every async event (all ASAP implementations, set timeout, promises) and then overriding get element to wait for all async events to finish before getting a element - it makes things very stable. The only problem is that some events like set timeout 200ms or 3000ms are things you should wait for and others you shouldn’t so we had to be careful on the boundaries). With the above, click is 100% stable on a very complex react app.
Hi everyone, we’re still working on better error recovery and messaging. We’re also working on test retry support, so even if a test fails 10% of the time, it will retry and not fail the whole suite.
In the meantime you can try out a plugin that enables retries: https://github.com/Bkucera/cypress-plugin-retries
let me know if this helps, and open any issues you find in that repo. Thanks!
Not to pile on here - but incase it helps, here is an interesting instance of this issue happening, where all suggested fixes above are not helping to work around the issue.
i am running these assertions:
here’s how cypress 3.1.4 processes these assertions:
I’ve also tried aliasing the element, then using the alias. same results.
(the item that i’m clicking is indeed generated/regenerated by multiple react renders - based on an api response, hence the difficulty - though its interesting how every assertion before the click, contradicts the final click assertion’s error message. :]).
For now, I’ve just stubbed out the harmony api calls, and have them constantly return the same data > which minimises the react re-rendering > which seems to stabilise the click event. It would be nice though, to not have to do this.
I would just like to say I’m experiencing this same problem, am able to verify the the existence of the element via height and width but getting the same “element not visible” when trying the click. I do believe it has to do with timing, if I hardcode a wait it passes fine. This is especially weird because the
.should('be.visible').invoke('width').should('be.greaterThan', 0)
and.should('be.visible').invoke('height').should('be.greaterThan', 0)
steps I run before the click command both pass while the click fails.After trying several approaches I’m getting good results with
We had a similar issue on a React app. It was caused by a coding mistake.
Something like:
The issue here is that
MyInnerComponent
is a new component at each render so react will remove the div element and re-create new one at each render.But still, I think Cypress could provide a better error message. The actual issue is that the element is detached from the DOM, talking about dimensions 0x0 is misleading.
Also, could we avoid this pitfall by checking the element state synchronously before performing the click? After all, this is the promise of Cypress:
I had this problem as well.
How I fix is that don’t use cypress
selector playground
. Insteand, using chrome dev tool and copyselector
of the element.If use this copied selector in cypress test, It works fine. e.g
Some element emits same error, even though use chrome dev tools.
Much of the algorithm used to determine visibility is documented here:
https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Actionability
Your issue is very clear which is helpful, thank you. I’m a bit confused when you say flaky though. Flake generally refers to situations where a test sometimes passes or sometimes fails.
It seems that you’re saying the test always fails which would not indicate a flaky test - you’ve just found a bug in our visibility algorithm 😛
While you’ve done an excellent job giving us a ton of great information, the problem is that there is more than just the styles of this
<a>
causing this problem.Is there any way you would be able to remove all of the excess HTML (or maybe even just do a File -> Save Page As) so you can export the entire DOM including the styles. From there we should be able to internally reproduce this and get it resolved.
Also if this is a public site (or even a private site we could log into) we’d be happy to take a look at this.
We’ve had a few users complain about this exact scenario - where Cypress considers an element hidden because its effective width and height is 0, but we’ve been unable to reproduce any situation to trigger that. We have several hundred tests around visibility (and they all pass) so we’re going to need help creating the scenario where this happens.
Any update on this issue? It’s still reproducible in Cypress v3.6.1
We still see this issue when using Cypress
3.4.1
.Our test (in pseudo code) is as easy as:
@maxime1992 So far this is the best way I could come up with to reduce flakyness. Basically every line of my test looks like this:
cy.get('#my-selector' ).should('be.visible').click(x,y, { force: true });
We use the assertion to make sure the element is visible so Cypress can actually click it. Also we define specific coordinates (you need to calculate the coordinates relative to the elements and parents position yourself there are helper functions for this) and finally we force the click without checking it again, because we asserted before, that it is visible. The coordinates are very helpful because if you click on an element Cypress defaults the clicks position to the center (which could be hidden by a another element). This reduces errors thrown by
cy.click()
and the tests actually work without too much flaky behavior for me, because every line is using that workaround, since I record most of my testing code with an extension.The only times I need to have wait statements is for my XHR’s. With this workaround I do not have the need for manual
cy.wait()
statements, but I would hope this issue will be solved soon. This workaround is by no means pretty, but at least you get stability until the issue is solved.Same issue while testing our Angular app. A better error message as suggested in #696 would have helped indeed, but debugging remained quite hard for me.
(Edited)
I had written a big comment about the whole day I spent experimenting around similar issues, but feel quite stupid now that I’ve finally realized (after getting away from the code) that it was a caching issue. I had a list of elements displayed first from the cache, that were getting re-rendered later when the xhr came back. So I can now confirm with 100% certainty that it was a bloody asynchronous rendering issue. Quite amazing that it was often happening at the exact same moment, but maybe Cypress was freeing the stack consistently at the same step. Dunno.
Suggestion of improvement
I suppose this issue means that Cypress doesn’t retry the whole commands chain when the click fails. Would retrying the whole chain, or maybe just going up the chain until finding an element still attached to the DOM, make such commands more stable ? Something like #1548 maybe.
Try
cy.get(...).click({ force: true })
Would it be possible to get a better error message than
has an effective width and height of: '0 x 0'
whenclick()
is attempted on an element that has been removed from the DOM tree?There were improvements as part of 3.5.0 release https://github.com/cypress-io/cypress/pull/4945 that should have improved this error message for many instances where one was asserting on a detached DOM element. Now users will see a message saying that the DOM element is attached.
If you’re still experiencing this in 3.6.1 - Please provide a fully reproducible example of the bug. I cannot stress this enough. If we cannot write a failing test in Cypress - then we cannot begin work on the issue.
This is especially depressing 😞
Having this issue too.
I’m using Angular and the element is definitely visible.
Test is flaky too on my side but even weirder: Locally with Chromium it seems to be fine whereas on CI with the headless browser it’s never passing (either never or very not often, not quite sure).
I need to click on that element as it’s an important path to my navigation and I’m really stuck here.
I was targeting it using
contains
and the text, I tried a proper selector withdata-
andcy.get
but no luck either.Another potentially better solution (and faster if you require too many waits) is a custom
get
command which mimics the native command but completely lets the element be garbage collected and retries the query selection against the latest Dom. I have a custom shadow get command which behaves this way and it is reliable despite me knowing there are definitely rerenders occurring at times. I only need to add very short hard waits for promises for async handles to be applied since they’re not always there by the time the click goes through, but I don’t need to add arbitrary large waits for the Dom rendering to settle.By making a custom command which retries the whole chain, it’s reasonable to expect that the issue would be resolved temporarily. Of course such a core change to the basic command will take time to resolve, so a better work around would be nice for most people. You can always use the overwrite or override command I think too, to simplify the transition. The get command is actually pretty basic once you understand it.
If work wasn’t so busy I’d gladly try to make this command but will not have time for a few days
I have same problem with AngularJS website. I need to include
cy.wait()
or theclick()
fails with:This element '<a>' is not visible because it has an effective width and height of: '0 x 0' pixels.
Running on Cypress 3.0.1
will pass always - but if I take out the
cy.wait(300)
theclick()
will fail about half the timeI believe the problem here is that it’s finding the
<a>
but then it’s getting detached / wiped from the DOM by the time the.click()
rolls around. If this is the case, there’s actually nothing wrong with the visibility algorithm (it’s just miscommunicating the root cause).I can manually recreate this situation and opened a new issue here: https://github.com/cypress-io/cypress/issues/696
There is something causing the
<a>
to be detached / rerendered.Likely what you’ll need to do is add more guards prior to interacting with the element.
{ force: true }
is failing because when the element is removed its event handlers are also removed, or the browser will no longer apply the default action to a detached element (such as to follow the link).So what ends up happening is that by forcing the click event to happen, Cypress issues it, but it’s being issued to a detached element, which does nothing.
@testerez Reading https://github.com/cypress-io/cypress/pull/4945, I think that’ll fix this issue in the next release!
Worked for me. Thanks so much!
We had similar issues. We discovered that an API call was basically reloading our component in the background - and it was happening so fast that the tests “usually” didn’t even notice. That produced the “flake” of it periodically failing. Once we understood that, we resolved the tests.
We are facing somewhat similar issue where cypress finds 2 input elements on page although after final rendering there’s only one such element. So as the first instance of element become detached, cypress throws the error as we try to type in that input box:
CypressError: cy.type() can only be called on a single element. Your subject contained 2 elements.
we have 2 solutions working for us:cy.get(selector).wait(500)
cy.get(selector).last()
I reconfirm that after final rendering is done, there’s only element on the page with above selector but may be cypress is not waiting enough for the page to render finally
Timeouts because of zero-width elements happens all the time to us; I’m guessing it’s because the element is only partially rendered. It would be great if there was a way to wait for an element of non-zero width; then I’d replace get() with a version that checked for both visibility and non-zero size. But I haven’t found a way to do this. You can do an assertion on the css width, but that’s not the same thing. Also {force: true} doesn’t work in this case. The only solution currently I’ve found is to add wait()s.
As per your suggestion - you will need to come up with some mechanism to ‘guard’ Cypress from executing its commands too soon until you know your application has completely initialized.
You could directly communicate with your react app and register an event listener, or you could search for some content on the page that you know becomes available. For instance your react app could add an attribute to the <body> or <html> to know its completely done.
You could also individually wait for all the XHR’s although react is going to process them asynchronously so there will still be a small gap even after waiting on them.
Once you establish that it will solve all of your flake. I would likely wrap this up into a custom command or just override
visit
so it doesn’t resolve until your app is fully loaded.There are no events in JS that will help you do this, it’s completely application specific.
Thanks for responding, I haven’t been clear about that, sorry. The test does indeed sometimes passes, and sometimes not. Locally it passes roughly 95% of the time, on our CI environment it passes roughly 10% of the time.
I’ve done some testing and I’m pretty certain it has nothing to do with the styling of the
a
tag, but with API calls happening while the.click()
is being triggered.Our app currently does a lot of api calls on page load (something we should improve upon 😃), and I managed to get the tests more stable by doing the following:
Now it’s a bit early to call the test “stable” but it at least a lot more stable.
About your other question, I’m going to try and isolate this issue by making a simpler app, but that will take some time.
Closing this issue and referring to https://github.com/cypress-io/cypress/issues/7306 since there have been no comments on this issue since 4.4.1 of Cypress was released, where we implemented a clearer error message in this case that used to show as the 0x0 error message:
If you’re experiencing a bug similar to this in Cypress, please open a new issue with a fully reproducible example that we can run. There may be a specific edge case with the issue that we need more detail to fix.
Are people still regularly seeing this error in the latest version of Cypress? There have been a lot fewer comments in here since January.
My theory is that people are now more regularly seeing the error:
which is a more accurate error message than the effective width and height one when the element is no longer in the DOM. We have an issue to address this here: https://github.com/cypress-io/cypress/issues/7306
This code-smells like hell, but calling
cy.wait(0)
prior tocy.get('…')
helps in my case. Want more code smell? Then useconst zero = 0; cy.wait(zero)
to keep the linter happy.I guess this is effectively the same as the good old
setTimeout(fn(), 0)
, waiting for the call stack to empty (e.g. until all re-renders ran) so thatcy.get
grabs the element(s) from the last render?!Another idea: should there be a kind of a “proxying” version of
get()
, which is able to re-evaluate the selector (with a timeout) if the previously found DOM element has been removed? It seems that DOM element aliases already do something like this, but it probably doesn’t solve the issue of the element disappearing between theget()
and theclick()
?Same error with Vue.js.
@maxime1992 you’re right. I’ve noticed inconsistencies with it. So a better solution would be to attempt to click it then check if it the checkbox is checked and if not attempt to click it a second time. Good catch.
We are experiencing same issue, are there any updates on this?
@ArnaudPel I fully agree, Cypress not retrying the entire command chain is a source of these types of confusions and bugs. We have been talking about fixing this for a while now, and will probably be next after retries or cross browser support.
@OmriAharon
Same here - nothing worked other than this workaround. Also using react.