cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.07k stars 3.19k forks source link

cy.click() failed because element is effective width and height of 0x0, even though it is not. #695

Closed peterklijn closed 4 years ago

peterklijn commented 7 years ago

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: screen shot 2017-09-29 at 11 44 37

Item highlighted by Cypress: screen shot 2017-09-29 at 11 33 11

Chrome inspect of the element: screen shot 2017-09-29 at 11 34 22

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

reno1979 commented 5 years ago

Same here, we create a modal with a form and 2 out of 10 times Cypress can not find the required elements

seth-wat commented 5 years ago

I'm also having this problem. I just came here to say I hope to see it prioritized. Cypress is great, and many thanks to everyones hard work. However, things like this are critical to sustainable test architecture.

kuceb commented 5 years ago

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

image

let me know if this helps, and open any issues you find in that repo. Thanks!

Saibamen commented 5 years ago

@Bkucera - retrying tests is not a solution!!

kuceb commented 5 years ago

@Saibamen correct, it isn't a solution. But it's relevant to this issue, and hopefully it helps you out.

indrolie commented 5 years ago

I have the same issue here.

.invoke('width').should('be.gt', 0) .should(be.visible) cy.wait(300)

Tried all above, still failed. The plugin also didn't help my case: https://github.com/Bkucera/cypress-plugin-retries

kuceb commented 5 years ago

@indrolie can you provide more context or a reproducible example of the failure? Are you using a framework, what does your cypress code look like?

dineshk-qa commented 5 years ago

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:

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

egucciar commented 5 years ago

I think the thread already found the problem (not retrying the whole chain / holding onto elements which are supposed to have been detached)

The most horribly robust is solution is to make your wait long enough for things to settle. I mean a hard wait that's much longer then 500 to give it a good time to settle, before you attempt to get your element. This means cypress will not get the original item that was then detached, it won't be cached, and it and can pass.

Hopefully one day the chain retryability / memory leak issue will get resolved. Otherwise if a long enough hard wait doesn't fix the issue for you guys, perhaps a reproducible example can be supplied other sources of error could be identified.

PS: my hard wait was probably at least a second, but you might need 2 or 3. I've only encountered this twice as we use primarily custom shadow commands at my work and don't use the cypress get as much.

egucciar commented 5 years ago

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

mxrguspxrt commented 5 years ago

Element visible, works well, test fails randomly.

chilikasha commented 5 years ago

Added assertion for width and wait(100) as suggested workaround. Worked locally, but fails when running in CI: either get same error about "effective width and height of 0x0..." or proceeds next to click(), but the click itself doesn't work (in my case dropdown should appear)

Dogyunjeong commented 5 years ago

I had this problem as well.

How I fix is that don't use cypress selector playground. Insteand, using chrome dev tool and copy selector of the element.

If use this copied selector in cypress test, It works fine. e.g

cy.get('#react-root > div > div > div > div.theme__my__cyeme > div.app_content > div:nth-child(2) > div > div > section > div > div.my_layout > div > div > button').click()

Some element emits same error, even though use chrome dev tools.

niedarek commented 5 years ago

I had a similar problem, but in my case i found out that in onclick function i set condition: if (event.originalEvent instanceof MouseEvent) which caused cypress click() was not successful.

bpkennedy commented 5 years ago

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.

Dogyunjeong commented 5 years ago

I had this problem quite a lot. I use cypress for react project. Those are what I had

Above cases, .invoke('width').should('be.greaterThan', 0); might solve prolems, But some tries will be failed which is worse than not working.

The solution will be that to make sure render is fully finished which I couldn't find

Below are what I tried

should('be.visible') solved many problems for me. It prevents to get element during re-render after Check width

It seems cypress will not re-select element whenget() is pedning,

It would be wonderful, if cypress re-select element when click() or type() has these problems before timed out e.g get -> if click failed due to those problem -> get agian -> try click -> if failed, get -> .....

shengslogar commented 5 years ago

To pile on here, I'm also having this issue on a specific page. I tried asserting the width, which was returned correctly, but click (even with the force flag) fails to work.

There's a good chance I wouldn't be able to make an MVP of this since this works across the rest of an app I'm working on. There are no pending requests and it seems like Cypress is picking up on a partial detached element (might be some app JS didn't fully finish running, haven't dug through everything yet 🤯).

The only "solution" so far is an arbitrary timeout after each cy.visit which leads me to believe the DOM is just not ready fast enough. (v3.2.0)

image

maxime1992 commented 5 years ago

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 with data- and cy.get but no luck either.

DennisLoska commented 5 years ago

@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.

maxime1992 commented 5 years ago

@DennisLoska thanks, I tried what you said and indeed it's enough (for now).

Thanks for unlocking this situation until a proper fix comes out :)

bsell93 commented 5 years ago

I tried doing a force click twice and that worked for me.

cy.get('.checkbox').click({ force: true });
cy.get('.checkbox').click({ force: true });

Edit: A more robust and reliable solution would be something like

cy.get('.checkbox').check({ force: true });
cy.get('.checkbox').then(
    (el) => !el.checked && cy.get('.checkbox').check({ force: true })
);
unfadding commented 5 years ago

I tried doing a force click twice and that worked for me.

cy.get('.checkbox').click({ force: true });
cy.get('.checkbox').click({ force: true });

Worked for me. Thanks so much!

patsrick1943 commented 5 years ago

I have a similar problem using vue.js. A modal dialog opens after a button click, but cypress never sees the dialog or any of its contents. I tried going all the way back to the enclosing div, but cypress did not see it, the form, or any of the controls. image image

I even added a hard wait before trying to click. Here's the amended code:

    cy.get('#tabCollectContent > div > #toolbarCollectSites > .btn-group > #cmdInitiateSurvey').click()
      .wait(1000)
       cy.get('div#modalNormal.modal.hid.fade-in').click()
      cy.get('div#modalNormal.modal.hid.fade-in > form#surveyInit').click()
      cy.get('#surveyInit > div').click()
      cy.get('#surveyInit > div > div.span9').click()
      cy.get('#surveyInit > div > div.span9 > div.hidden.panel.panel1').click()
maxime1992 commented 5 years ago

@bsell93 that seems really risky.

If you click twice, and if for some reason the first one is working at some point, the second click will do the opposite of what you want (on your checkbox example)

bsell93 commented 5 years ago

@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.

EvgeniiVoskoboinik commented 5 years ago

Perhaps this solution will help you https://stackoverflow.com/a/56451395/2846252

dschuessler commented 5 years ago

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.

I explicitly assert that the element is visible, yet the test fails claiming afterwards that this very element is not visible.

Bildschirmfoto 2019-06-13 um 15 17 43

alegout commented 5 years ago

Got this issue with select2 This element '<li.select2-results__option>' is not visible because it has an effective width and height of: '0 x 0' pixels. I tested with cy.get('#query-btn').invoke('width').should('be.gt', 0) but not working 🤷‍♂

Also, when inspecting the element my element has width and height > 0

It works with force: true

ziyailkemerogul commented 5 years ago

I'm getting the same error:

cy.click() failed because this element is not visible: although it's visible.

I also tried with force: true but still getting the same error. I saw lots of entries and opened issues related to this topic, (or cy.type() failed because this element is detached from the DOM. this) and couldn't figure out the reason.

Is this directly related to React or it's something Cypress doesn't support currently?

AnalyzePlatypus commented 5 years ago

Same error with Vue.js.

cy.get("#EMAIL").invoke('width').should('be.greaterThan', 0) // -> true
cy.get("#EMAIL").type("Bob");  // -> "...element is not visible"

Cypress 3.3.1 Vue 2.6

clakech commented 5 years ago

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 :

cy.get(`.sample-selector`)
  .should('be.visible')
  .click();
This element [...] is not visible because it has an effective width and height of: '0 x 0' pixels.
jbpallingayan commented 5 years ago

experiencing this on our test.

drFabio commented 5 years ago

I have hte same on a table cell and doing the below does not solve it. The innerwidth test passes the other do not. There is no change or remount between those 2 iteractions

  cy.get('@cell')
    .invoke('innerWidth')
    .should('be.greaterThan', 0);
  cy.get('@cell').click();

The force click "works" but then the onClick is not fired for some reason. The cy.get().wait() seems to improve the success rate but not always solve it

justinpinili commented 5 years ago

out team is also experiencing this issue/bug.

we've tried adding additional browser waits, which does alleviate some of the issues locally, however in CI it doesn't make much of a difference.

fo-fo commented 5 years ago

Would it be possible to get a better error message than has an effective width and height of: '0 x 0' when click() is attempted on an element that has been removed from the DOM tree?

fo-fo commented 5 years ago

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 the get() and the click()?

ghost commented 5 years ago

We still see this issue when using Cypress 3.4.1.

Our test (in pseudo code) is as easy as:

describe(description, () => {
  it(expectation, () => {
    cy.server();
    cy.route(
      method,
      url1,
      response, // this is a fixture
    ).as(alias);

    cy.visit(url2);
    cy.wait(@alias);

    cy.get('[test-id="<test id name>"]').should('be.visible'); // this assert is OK
    cy.get('[test-id="<test id name>"]').click(); // this line fails
  });
});
ghost commented 5 years ago

Are there any Cypress flags that I might enable in order to produce a richer log which might give us more input on why

click()

fails?

@brian-mann / @jennifer-shehane

flurosatbrad commented 5 years ago

Not sure if my problem is the same as the above posts as I am starting web development in general but I too am getting an error when trying to click on a selected element.

cy.get('.leaflet-draw-draw-circle').then((el)=>{
                 console.log(Cypress.dom.isDetached(el))
      }).then((el)=>{
                 cy.get(el).click();
      });

My apologies for my horrible formatting.

My target is visible on the page. From reading the above thread, I am first checking if the element is detached, which yields a 'false' in my console, then use the same element to click.
I still get the effective width and height fo 0 x 0 error.

Have I something different going on here?

kuceb commented 5 years ago

@flurosatbrad that appears to be the same issue. One of the issues here is during the actionability check before commands, we scroll the element. In some cases this can cause certain frameworks to re-render/detach dom nodes. The issue being the error message does not check for detached state, so you get the wrong error message, specific issue here #4946

flurosatbrad commented 5 years ago

@Bkucera Thanks for the clarification. Atleast I can try a work around if this is my issue.

saas2813 commented 5 years ago

I got the same error-message, but since our tests have had a lot of timing issues I added a check that some other control, that is conditionally rendered (added to the DOM) at the same time, was visible before the input command and I have not seen the problem since. (I have not run the test-case very much since but thought it might give you ideas.) I also get the impression that Cypress checks that the page have finished loading, but have no way to check that it is done rendering. Sprinkling my test-code with checks for element visibility seems to have made it stable. Before this there was a distinct correlation between computer load and failing tests.

kkkrist commented 5 years ago

This code-smells like hell, but calling cy.wait(0) prior to cy.get('…') helps in my case. Want more code smell? Then use const 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 that cy.get grabs the element(s) from the last render?!

hdavidzhu commented 5 years ago

@saas2813 @kkkrist At my company, we patched click to execute after a window.requestIdleCallback, which helps significantly. We don't use React, and Brian's earlier point makes sense:

There are no events in JS that will help you do this, it's completely application specific.

When we run tests against our static pages, we don't need to do this patch since the client doesn't custom refresh.

Perhaps there could be adapters/libraries for Cypress to work w/ the most popular frameworks, or more tutorials on how to properly detect render complete? This definitely seems to be a very common gotcha.


Or maybe simply, to enable retries when Cypress tries to act on a stale DOM element?

testerez commented 5 years ago

We had a similar issue on a React app. It was caused by a coding mistake.

Something like:

const MyComponent = () => {
  const MyInnerComponent = () => <div/>;
  return <MyInnerComponent/>;
}

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:

Cypress knows and understands everything that happens in your application synchronously.[...] It's impossible for Cypress to miss elements when it fires events

hdavidzhu commented 5 years ago

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.

@testerez Reading https://github.com/cypress-io/cypress/pull/4945, I think that'll fix this issue in the next release!

kuceb commented 5 years ago

thanks @hdavidzhu, you are correct. Should have thrown a comment on here.

davetapley commented 5 years ago

This is especially depressing 😞

test
Nandish47 commented 5 years ago

Any update on this issue? It's still reproducible in Cypress v3.6.1

Rodyb commented 5 years ago

I am experiencing exactly the same in 3.6.1