Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cypress.io: contains() not waiting for element

We are writing UI tests with cypress, which is usually quite simple to use. But again and again I stumble over a tedious waiting problem.

The scenario is pretty simple. The user clicks on the search button. Then he selects one of the elements with a certain text. Here's the code:

cy.get('#search-button').click();
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();

The third click event fails, because already cy.contains('Test item 1') doesn't wait for the page and the element to be rendered. From what I can see in the test steps, it simply clicks in the middle of the page, which does essentially nothing. So all subsequent steps fail of course.

However if I add a wait() between the calls like this:

cy.get('#search-button').click();
cy.wait(2000);
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();    

The page is rendered correctly, Test item 1 appears, is clicked and all subsequent steps succeed.

According the best practices the wait() call should not be necessary and therefore should be avoided. What am I doing wrong here?

like image 574
Ash Avatar asked Nov 22 '18 09:11

Ash


People also ask

How do you wait for an element to appear in Cypress?

As Cypress internally retries commands, we don't need to add any wait clause to ensure the element is visible before verifying it. Make sure you use timeouts sparingly. Most of the time you will be fine with using the default timeout.

How do you avoid wait in Cypress?

Use timeout per command Sometimes, you simply want to wait until a certain element appears, but everything else on the page is pretty fast. For these cases, you can use the options object and change timeout for a certain command.

What is Cy wait () in Cypress?

cy. wait() can time out waiting for the request to go out. cy. wait() can time out waiting for the response to return.

Does Cy get wait?

cy. get() can time out waiting for the element(s) to exist in the DOM . cy. get() can time out waiting for assertions you've added to pass.


2 Answers

tl;dr

Give a bigger timeout for contains:

cy.get('#search-button').click();
cy.contains('Test item 1', { timeout: 4000 }).click();
cy.get('#cheapest-offer-button').click();  

Explanation

As many Cypress commands, contains have a second argument which accepts an option object. You can pass the amount of milliseconds the command should wait in the timeout key like this:

.contains('Stuff', { timeout: 5000 }) // Timeout after 5 secs

This way the command will behave like if you added a wait before it, but if the command was successful it won't wait out all the time like wait does.

The official Cypress docs on timeouts explains this technique: how it works, how it should be done and how it affects chained assertions.

If the reason why you can't click the item is visibility then you can try .click({ force: true }), although this should be a last resort since it might hide actual bugs.

like image 195
totymedli Avatar answered Oct 21 '22 13:10

totymedli


It looks like this is common issue https://github.com/cypress-io/cypress/issues/695.

Solution is to force cypress to wait for all async operations like in frameworks based on Selenium webdriver. It is much more quickly than cy.wait() Do implement method:

function waitForBrowser() { 
   cy.window().then(win => {
      return new Cypress.Promise(resolve => win['requestIdleCallback'](resolve));
   });
}

And just use it like this:

cy.get('#search-button').click();
waitForBrowser();
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();

If you use Angular, better to use waitForAngular instead of waitForBrowser

function waitForAngular() {
  return cy.window().then(win => {
      return new Cypress.Promise((resolve, reject) => {
        let testabilities = win['getAllAngularTestabilities']();
        if (!testabilities) {
            return reject(new Error('No testabilities. Check Angular API'));
        }
        let count = testabilities.length;
        testabilities.forEach(testability => testability.whenStable(() => {
            count--;
            if (count !== 0) return;
            resolve();
        }));
      });
  });
}
like image 32
EvgenyV Avatar answered Oct 21 '22 12:10

EvgenyV