cypress.io: contains() not waiting for element
Asked Answered
R

5

18

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?

Richelle answered 22/11, 2018 at 9:54 Comment(3)
It's a bit hard to envision what is going on, can you show the DOM? (or describe the elements and what you know to be happening behind the clicks). Often you can replace a cy.wait() with a cy.contains(selector, newContent) where newContent is a piece of text that indicates a fetch has completed.Studdard
cy.contains('Test item 1') is finding something, despite it not yet being visible. cypress will throw an exception and fail the test if it can't find the element. Take a look in the command output on the left to see what that call is returning. You may need to change how you're locating that element to make it more specific, or maybe adding a .should("be.visible") will get it working. Posting a screen capture of the command log and your HTML will help us figure it out easier.Isolt
I believe it also could be used like cy.get(selector).contains('Expected text')Arctogaea
M
9

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.

Mum answered 27/6, 2019 at 1:23 Comment(0)
C
3

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();
        }));
      });
  });
}
Codon answered 4/6, 2019 at 21:9 Comment(2)
I did try using this approach, but I got no good result. Sorry !!Funnelform
Works :) too bad we have to manually do this... For me it worked well in Cypress 3.8.2 now in 4.4.0 I had to wait for Angular.Hound
B
1

Another alternative to those posted above is to use something like

cy.contains('Test item 1').should('exist').click()

or cy.contains('Test item 1').should('be.visible').click()

I am not sure why this works but it does, it doesn't require setting a specific timeout.

Alternatively no one else has suggested using

cy.server()
cy.route()

and

cy.wait('@routeAlias')

But that requires more information than in the OP

Bunch answered 5/8, 2020 at 23:41 Comment(0)
W
0

I had the same problem when trying to reach a span with certain value. I fixed it by providing more specific path to the span instead of getting all span elements in the page.

cy.get('span').contains('Round ID') //not working
cy.get('.details span').contains('Round ID') //worked

So you can try to be more specific when getting elements of the type which you want to target. Another option is to wait()...

Wrote answered 5/11, 2019 at 8:5 Comment(0)
P
0

You might be already aware that Cypress commands are Asynchronous. So if some command depends with another action then its better to write it in subroutine of its dependent action.

cy.get('#search-button').click().then(() => {
    cy.contains('Test item 1').should('be.visible').click();
    cy.get('#cheapest-offer-button').should('be.visible').click();
});
Pubes answered 6/8, 2020 at 6:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.