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?
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.
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.
cy. wait() can time out waiting for the request to go out. cy. wait() can time out waiting for the response to return.
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.
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.
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();
}));
});
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With