Closed peterklijn closed 4 years ago
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 :-P
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.
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:
cy.route('/api/order/retailer-orders**').as('getRetailerOrders');
cy.route('api/connection/connections').as('getConnections');
cy.route('/api/connection/relations').as('getRelations');
cy.route('/api/order/basket').as('getBasket');
cy.route('/api/order/retailer-orders/*').as('getRetailerOrder');
cy.route('/api/brand/brands/*').as('getBrand');
// .. some other checks ..
cy
.wait('@getRetailerOrders')
.wait('@getConnections')
.wait('@getRelations')
.wait('@getBasket')
.wait('@getRetailerOrder')
.wait('@getBrand');
cy.get('a[href="/styles"]').click(); // <--- This is where it sometimes fails.
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.
Could any of those XHR's affect the re-rendering of the <a>
?
Not to my knowledge, the <a>
is in the header op the page. The XHR's are for content.
Interestingly, if I use ....click({ force: true });
it sometimes fails because the click doesn't trigger a page change. We use react-router for our navigation, so I have a gut feeling it might have to do with that react-router component not being completely initialised as the page is still kind-of loading / waiting on async calls.
I 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.
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
or 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.
I have the same issue when I am trying to type into an input field, the behaviour is rather flaky with Cypress, 5 out of 10 times the steps works.
I am changing the language of Installation in Joomla, which changes the fields based on the language selected by users. Before I begin Typing into the field, I ensure the field is visible, and then try to type, but I get the same error which says element is not visible because it has an effective width and height of: '0 x 0' pixels.
Although in the screenshot it is clearly visible.
I am just trying to understand if we have a way of saying, hey Cypress do not proceed with the test until you have xyz element visible and you can interact with it.
I have the same issue. We are using angular. Some times the click registers and the other times does not.
@puneet0191 and @chathuraa did you read my comments above in this thread? I believe both of your situations are caused by the element becoming detached as Cypress begins to type. Can you verify nothing on your end is re-rendering the element.
@puneet0191 in your case you are highlighting the element in dev tools which is not necessarily the element that cypress found because it could have been replaced by being rerendered.
@brian-mann, thanks for the quick response. I believe the page render several times. Unfortunately reducing rerender is all the way at the bottom of our backlog. Sorry for my ignorance, is there a work around?
@chathuraa I worked around it by making sure the re-renders are done before executing the assertions which in my case is when all backend calls are done. See this comment
In my case, I was trying to click on a link (after waiting for the API result) that was being mounted and unmounted when API results came in. Switching it to a React pure component and making sure it wasn't unnecessarily unmounted fixed the problem. FWIW, I'm also using React Router 4 and this was trying to click on a <Link>
element. Thank you Cypress team for such an awesome dev experience
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 👍
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.
@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:
// these assertions below are the same
cy.get('#query-btn').invoke('width').should('be.gt', 0)
cy.get('#query-btn').invoke('width').should('be.greaterThan', 0)
You could basically assert against any property of an element, like innerWidth
and outerWidth
also.
See Assertions
Thanks, @jennifer-shehane! Exactly what I was looking for.
@jennifer-shehane this is exactly what we needed as well. we are going to implement a custom command to handle these assertions.
Cheers!
@jennifer-shehane thank you for the suggestion! Can you clarify, why cy.get('#query-btn').invoke('width').should('be.gt', 0)
won't fail, but cy.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's cy.get('#query-btn').click().parent()
and after click()
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...
Same problem here:
Out of around 12 forms elements, Cypress is unable to "type" in 2 of them. All of them have the same visibility. However, the other 10 are without an issue. {force:true}
does not work, either, no typing is done.
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 :
cy.get('#candidatsList > li:nth-child(1) > span').should("contain", "Créer RDV");
cy.get('#candidatsList > li:nth-child(1) > span').invoke('width').should('be.gt', 0);
cy.get('#candidatsList > li:nth-child(1) > span').click();
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 agree with @vjau , no matter what we are doing to guard the click, the click fails though the element is clearly visible.
@Bkucera and I have been reworking the visibility algorithms and are also in the process of adding native events. These bugs will be fixed. I don't have a timeline yet, but as we fix the visibility stuff we will release patches.
At the moment you can try setting the input's value programmatically and then manually trigger the change
events (or other stuff) so that your application binds to them correctly.
cy.get('input').invoke('val, 'asdf').trigger('change', { force: true }) // something like this
Just take programmatic shortcuts which will avoid the checking layers
Didn't work for me. I tried to programmatically make them visible, but to no avail.
On Thu, Jun 14, 2018 at 7:10 PM, Brian Mann notifications@github.com wrote:
@Bkucera https://github.com/Bkucera and I have been reworking the visibility algorithms and are also in the process of adding native events. These bugs will be fixed. I don't have a timeline yet, but as we fix the visibility stuff we will release patches.
At the moment you can try setting the input's value programmatically and then manually trigger the change events (or other stuff) so that your application binds to them correctly.
cy.get('input').invoke('val, 'asdf').trigger('change', { force: true }) // something like this
Just take programmatic shortcuts which will avoid the checking layers
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/cypress-io/cypress/issues/695#issuecomment-397369806, or mute the thread https://github.com/notifications/unsubscribe-auth/AIJ3zabjO0hR7dK3-Gt0IW_TF7QjUkyRks5t8pj3gaJpZM4Poclb .
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.
Try cy.get(...).click({ force: true })
Tried that to no avail.
On Thu, Jun 21, 2018, 22:21 Brian Mann notifications@github.com wrote:
Try cy.get(...).click({ force: true })
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/cypress-io/cypress/issues/695#issuecomment-399230133, or mute the thread https://github.com/notifications/unsubscribe-auth/AIJ3zUeHjuniqd4kB8tbN5mpAaJCCzOpks5t_AA5gaJpZM4Poclb .
yeah if it force clicks it works some of the time - I think the issue is it's still clicking before the element loads. I'm noticing this behavior with a dropdown element that loads a list (but doesn't need to fire off an xhr request to do so)
Cypress can't click an element before it loads. It waits until the element exists. It's possible that your actual click
handler isn't wired up even though its visible and available.
I would simply add an assertion about the element (or something else) that can guard Cypress from proceeding until the element is ready to receive the click event. It may be something about its dimensions, or content on the page, or waiting for an XHR, etc.
@brian-mann with the scrolling issue i reported earlier, AFAIK there is no way to know when the scroll has ended. The element exists but the browser is not able to click it while scrolling.
but doesn't the .should('be.visible').invoke('width').should('be.greaterThan', 0)
passing before a click command ensure that the element is visible? Or am I missing an assertion?
cy.get(selectors.addAdminTestEdit).should('be.visible').click();
cy.get(selectors.addAdminTestEditRemoveAdmin).should('be.visible').invoke('width').should('be.greaterThan', 0)
cy.get(selectors.addAdminTestEditRemoveAdmin).should('be.visible').invoke('height').should('be.greaterThan', 0)
cy.wait(300)
cy.get(selectors.addAdminTestEditRemoveAdmin).click();
will pass always - but if I take out the cy.wait(300)
the click()
will fail about half the time
I have same problem with AngularJS website. I need to include cy.wait()
or the click()
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
Had same issue when the click failure in 10% chance.
cy.get('#query-btn').invoke('width').should('be.gt', 0)
Didn`t helped
{ force: true }
and cy.wait(150);
Worked ( kind of )
But its definitely workaround not a solution for me.
But the app which i was testing was not build on react and i was referencing ID locators. Maybe that where it comes from.
Happens to us too, nothing works except cy.wait(100)
.
@brian-mann : Do you have a repro environment where you can see this bug reliably?
I believe it does have to do with XHRs coming in late. In my case React is reflowing a bunch of elements to the DOM that already existed.
I'm not familiar with Cypress internals but perhaps attaching attaching a selector to the click event: cy.click(
We have the same (or similar) issue. Tests run fine on a desktop using chrome or electron but headless fails randomly (platform independent). cy.click()
in particular. While I'm on the bandwagon for Cypress, this seems to be a bit shaky, especially for complex SPAs. Or maybe we're doing it wrong - but I honestly can't see a way to assure a 100% hit rate here. I would love to read how to prevent things like that from happening.
@OmriAharon
Happens to us to, nothing works except
cy.wait(100)
.
Same here - nothing worked other than this workaround. Also using react.
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.
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.
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.
@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.
@Bkucera thank you for your quick reply. I've edited my comment after realizing I was all at sea yesterday. We seem to agree that retrying the whole chain would have made the test pass. But now I realize that without this failure I would never have noticed this caching thing :) Which, in my case, was not intended.
Trying to think about a way to avoid this in the future, maybe Cypress could enrich the error message with something like right before this failure, xhr request [url] completed
? But I understand it's already visible in the Live version, and might be too much.
@ArnaudPel
Would retrying the whole chain, or maybe just going up the chain until finding an element still attached to the DOM
Cypress does currently do this. The problem happens when Cypress finds an element, but it's not the one you expected, so the element never gets detached from the dom.
Still failing in 3.1.4
Multiple wait() doesn't work for me (less crashes but still randomly...)
This same with cy.select()
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:
const setupPageAddressListFirstSuggestion = '[data-cypress-test-reference="address-suggestion-first-item"]'
cy.get(setupPageAddressListFirstSuggestion).should('exist').and('not.be.disabled');
cy.get(setupPageAddressListFirstSuggestion).invoke('width').should('be.greaterThan', 0);
cy.get(setupPageAddressListFirstSuggestion).invoke('height').should('be.greaterThan', 0);
cy.get(setupPageAddressListFirstSuggestion).click();
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.
@jennifer-shehane I don't see any commits in develop branch for solving this issue. Anyone working to fix this?
Hi, I have the same issues. using should("be.visible") also fails because of this problem. It would be good if one can make a clear statement whether or not this will be fixed. Otherwise we also do not know if we should handle this in our code or not.
No work has been done specifically on this issue.
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.
We are experiencing same issue, are there any updates on this?
Similar issue with our project in Vue, hoping for an update soon.
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).Is this a Feature or Bug?
Bug?
Current behavior:
cy.click() failed because element is not visible