PaulPonomarev
PaulPonomarev

Reputation: 373

Cypress loop execution order

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

Answers (2)

Fody
Fody

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.


How to run the same test again and again to confirm it is flake-free

See Burning Tests with cypress-grep for a non-code way to repeat a test.

Upvotes: 4

agoff
agoff

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

Related Questions