How to create a synchronous for loop? [closed]
Asked Answered
S

1

0

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://mcmap.net/q/454845/-how-to-use-a-while-loop-in-cypress-the-control-of-is-not-entering-the-loop-when-running-this-spec-file-the-way-i-am-polling-the-task-is-correct

https://mcmap.net/q/449700/-cypress-io-writing-a-for-loop-closed

Somewise answered 5/6, 2024 at 21:43 Comment(5)
A lot of the detail is obscured by your custom code that we can only guess at what it's doing. I suppose you are asking "how do I convert a sequence of .then() into a loop".Ehrenburg
What are the actions exactly? Are there other methods of AssertActions.assertActionRequest? i.e could you show roughly what do stuff is for #2 & #3 pleaseEhrenburg
@AngusYaam - thanks. Yes, I want to convert a sequence of .then() into a for loop. Do stuff for #2 & #3 is mostly the same as that for #1. 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}. Please let me know if this helps or I need to explain it better.Somewise
Ok, thanks I think I see the pattern now.Ehrenburg
I can't post code, but piggy-backing off Aladin - the array to loop would be something like ['for action 1', 'for action 2', 'for action 3']. Then I'd put the common code in a function and pass each action key in as a parameter (which is all simple forEach() code). I agree with comments about making assertions to "regulate" the flow, that seems like the right way to make the loop behave synchronously.Ehrenburg
V
1

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.

Vannie answered 5/6, 2024 at 22:26 Comment(3)
It looks like your actions array would be an array of functions to run. Is that correct? Why can't we iterate over an of actionSettings instead? actionsettings is an object like {age: 23, gender: male}Somewise
Yes, sorry I misunderstood the question.Vannie
I have to say, the answers in the linked questions are mostly suggesting alternate ways to perform the loop, when really the focus should be on controlling the code within the loop - making sure each iteration is complete before the next starts.Vannie

© 2022 - 2025 — McMap. All rights reserved.