\nCan it be that Cypress has its own loop execution logic? Is there a way to make it work more predictably?
\nAnd in total I've noticed that since version 10.0.0 Cypress won't wait where it did before, like waiting for page to load where now I have to add timeout configs.
Cypress code is executed asynchronously. It will execute at a different time than your synchronous (read: regular JS) code. The expect
from Chai is a synchronous command, so it is executed sequentially after updating your selected
variable.
To avoid sync/async issues, you could include your synchronous JS code in the Cypress chain.
\nlet last_selected; // removing from the iterations to avoid any sync/async issues\nfor (let i = 0; i < 4; i++) {\n cy.wrap(i).then(() => {\n selected.push(i);\n last_selected = selected[selected.length - 1];\n })... // rest of your Cypress commands.\n}\n
\n","author":{"@type":"Person","name":"agoff"},"upvoteCount":2}}}Reputation: 373
I've used the idea described here to run the test multiple times with different input values. But I've met a problem that Cypress manages loop pretty strange. To test the problem I've created a minimized application:
$(document).ready(() => {
$('#submit').on('click', () => {
$('#result').val($('#result').val() + $('#select').val() + '\n');
});
});
select, button, textarea{
font-size: 1.2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select id="select">
<option value="" disabled="" selected="">Choose</option>
<option value="first">First</option>
<option value="second">Second</option>
<option value="third">Third</option>
<option value="fourth">Fourth</option>
</select>
<button id="submit">Add</button>
<br>
<textarea id="result" rows="20" cols="50"></textarea>
The test I run using Cypress:
describe('Test', () => {
it('should add entries to textarea', () => {
cy.visit('http://localhost/cypress-fail/index.html');
cy.get('#select', { timeout: 10000 }).should('be.visible');
let selected = [];
for (let i = 0; i < 4; i++) {
selected.push(i + 1);
let last_selected = selected[selected.length - 1];
cy.get('#select').children('option').then($options => { console.log(($options[last_selected])); cy.wrap($options[last_selected]).invoke('attr','value').then(value => cy.get('#select').select(value))});
cy.get('#submit').click().then(()=> console.log("submitted"));
cy.wrap(selected).should('have.length', i + 1);
//expect(selected).to.have.length(i+1);
cy.get('#result').invoke('val').then(text => {
let list = text.split('\n').filter(a => a);
cy.wrap(list).should('have.length', i + 1);
})
}
})
})
As the result of the test I get assert error:
assert: expected [ 1, 2, 3, 4, 5 ] to have a length of 1 but got 5
However if I use the "expect" line and try Chai-style this test passes, but it checks the array each loop first and then loops again to add selected entries into textarea.
Can it be that Cypress has its own loop execution logic? Is there a way to make it work more predictably?
And in total I've noticed that since version 10.0.0 Cypress won't wait where it did before, like waiting for page to load where now I have to add timeout configs.
Upvotes: 2
Views: 1251
Reputation: 32138
You can make iteration work for simple scenarios, but it's easy to lose track of what executes on the Cypress queue and what does not.
If you switch to recursion it's easier to control when the code moves from one step to the next.
Here the code is inside a recursive function, pretty much unchanged from the original.
it('retries test with recursion', () => {
let selected = [];
const runTest = (i = 0) => {
if (i===4) return; // exit condition
selected.push(i + 1);
let last_selected = selected[selected.length - 1];
cy.get('#select')
.children('option')
.then($options => {
console.log(($options[last_selected]));
cy.wrap($options[last_selected]).invoke('attr','value')
.then(value => cy.get('#select').select(value))
});
cy.get('#submit').click().then(()=> console.log("submitted"));
cy.wrap(selected).should('have.length', i + 1);
cy.get('#result').invoke('val').then(text => {
let list = text.split('\n').filter(a => a);
cy.wrap(list).should('have.length', i + 1);
})
cy.then(() => { // after above block is complete
runTest(++i) // next iteration
})
}
runTest()
});
The step to next iteration is inside cy.then()
which guarantees it will only happen after all preceding queue commands are complete.
The "exit" condition is at the top - if (i===4) return
.
In this case we only want to stop iterating, so we just return. Another possibility is to conditionally throw an error and fail the test, for example if you were searching for a particular <option>
value but never found it.
See Burning Tests with cypress-grep for a non-code way to repeat a test.
Upvotes: 4
Reputation: 7163
Cypress code is executed asynchronously. It will execute at a different time than your synchronous (read: regular JS) code. The expect
from Chai is a synchronous command, so it is executed sequentially after updating your selected
variable.
To avoid sync/async issues, you could include your synchronous JS code in the Cypress chain.
let last_selected; // removing from the iterations to avoid any sync/async issues
for (let i = 0; i < 4; i++) {
cy.wrap(i).then(() => {
selected.push(i);
last_selected = selected[selected.length - 1];
})... // rest of your Cypress commands.
}
Upvotes: 2