Search code examples
typescriptcypress

How to create a synchronous for loop?


I want to click a series of elements in a web page and check if each of those actions produces the correct API request URL. For that, I made an array of settings for each element and iterated over them, clicked the respective element and asserted the results. The problem is that the assertion for the last element always expects the setting for the first element. I see that this happens because cypress runs async. So, I ended up removing the iteration and just repeated all the action-assertions as shown below.

Is there an easy way to make a synchronous for loop instead of the way I have done it below ? Basically, I want to convert a sequence of .then() into a for loop. The "action" is the act of clicking an element on the page based on the actionSettings object. That object can look something like {age: 23, gender: male}. NOTE that I do not want to use the cypress recurse plugin here.

//code to intercept api call aliased as "actionApiCall".

cy.log("Do 1 of 5 actions on a given page")
  .then(() => {
    const actionSettings = getActionSettings("for action 1");
    myPageObject
      // clickElement(...) returns a Cypress.Chainable<null> with return cy.wrap(null);
      .clickElement(actionSettings)
      .wait("@actionApiCall")
      .then((intercept) => {
        // This assertion checks that the query params of the request URL are per the settings in actionSettings.
        AssertActions.assertActionRequest(intercept.request, actionSettings);
      });
}).log("Do 2 of 5 actions on a given page")
  .then(() => {//do stuff})
.log("Do 3 of 5 actions on a given page")
  .then(() => {//do stuff})

..... ETC

PS - Cypress .each will not work here because I don't want to iterate over cypress elements. I want to iterate over an array of "actionSettings" i.e. not cypress elements. Also, here are the solutions that did not work for me.

How can I run a Cypress Command within a synchronous loop?

https://stackoverflow.com/a/72512125/6648326

https://stackoverflow.com/a/70687033/6648326


Solution

  • I guess you just want to wrap each step in a .then() to ensure the Cypress queue is processing in the same order as your above sequence.

    Don't think in terms of asynchronous/synchronous, instead think of the command queue that Cypress uses as a pipeline of commands. When you loop, over some array, using .then() makes sure the commands are in the right sequence.

    If the action causes some page change, you would need to add some should() assertion after each that confirms the action is complete. Something like:

    const actionsArray = [action1, action2, action3]
    actionsArray.forEach(action => {
      cy.then(() => {
        action() // perform action in sequence
          .should(assert action is complete)
      })
    })
    
    // or
    cy.wrap(actions).each(action => {
      cy.then(() => {
        action() // perform action in sequence
          .should(assert action is complete)
      })
    })
    

    is logically equivalent to

    action1()
      .should(assert something that indicates action1 is complete)
      .then(() => {
        action2().should(assert action is complete)
      })
      .then(() => {
        action3().should(assert action is complete)
      })
    

    There are other issues to consider, like what if the actions depend on some data that gets updated at different steps.