Reputation: 3583
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?
Upvotes: 18
Views: 20531
Reputation: 196
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();
});
Upvotes: 0
Reputation: 31
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
Upvotes: 1
Reputation: 625
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()...
Upvotes: 0
Reputation: 31162
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();
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.
Upvotes: 11
Reputation: 1256
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();
}));
});
});
}
Upvotes: 3