How can I break the for loop within the then block?

79 Views Asked by At

I'm new to Cypress and JavaScript. I want to create a loop that executes a request with different values until I get a certain response.

let continue_loop = 1;

loginLoop: for (let i = 1; i < 100; i++) {
  cy.request({
    // url: Cypress.env("web_api")+"/1.0/user/login",
    url: Cypress.env("api").staging + "/1.0/user/login",
    method: "POST",
    failOnStatusCode: false,
    body: {
      email: "email" + i + "@email.com",
      password: "Helloworld10",
    },
  }).then((response) => {
    if (response.status == 403) {
      continue_loop = 2;
    }
  });

  if (continue_loop == 2) {
    break loginLoop;
  }
}

It seems that continue_loop only gets the value 2 within the then block, because after it always returns to 1 so the loop never breaks. I also tried to use Cypress aliases but it's the same result. How can I achieve this?

2

There are 2 best solutions below

0
Mike 'Pomax' Kamermans On

Cypress, annoyingly, does not use promises but "something that has a .then function" instead. Of course, we can just turn those into real promises by wrapping them and then awaiting those:

const MAX_FAIL_COUNT = ...;

for (let failures = 0, i = 1; failures < MAX_FAIL_COUNT && i < 100; i++) {
  // Await the request with a simple wrapper
  const response = await new Promise(resolve => cy.request(
    ...
  ).then(resolve));

  // And don't use == unless your statement *only* works with type coercion
  if (response.status === 403) {
    failures++;
  }
}
0
TesterDick On

Cypress aliases are provided to bridge the gap between synchronous code like for-loops and asynchronous commands like cy.request().

You would need to

  • bring the code checking break condition inside the loop,
  • invert the logic so that you only continue if continue_loop is true

This reproducible example shows how to do equivalent logic to async/await in a Cypress test.

let continue_loop = true

// set up the alias to control the loop
cy.wrap(continue_loop, {log:false}).as('continue')   

// staring at 95 to keep the example short
// #101 will be a failing request
// loops 102 to 200 will be skipped

for (let i = 95; i < 200; i++) {
  cy.get('@continue', {log:false}).then(continue_loop => {
    if (continue_loop) {
      cy.request({
        url: `https://jsonplaceholder.typicode.com/posts/${i}`,
        failOnStatusCode: false,
      }).then((response) => {

        // checking for any failed status here
        if (response.status >= 400) {
          continue_loop = false
          cy.wrap(continue_loop, {log:false}).as('continue')  // update the alias
        }
      })
    }
  })
}

enter image description here


With a Promise wrapper

Using a Promise wrapper works for this simple command, but could fail for more complex command chains.

Cypress issues a warning in the console.

for (let i = 95; i < 200; i++) {
  const response = await new Promise(resolve => {
    cy.request({
      url: `https://jsonplaceholder.typicode.com/posts/${i}`,
      failOnStatusCode: false,
    }).then(resolve)
  })
  if (response.status >= 400) {
    break 
  }
}

The warning thrown in the console

enter image description here


Request polling

This example in the docs is the simplest IMO.

This pattern uses recursion rather than a for-loop.

function req(i) {
  if (i > 200) return    // highest number to try

  cy.request({
    url: `https://jsonplaceholder.typicode.com/posts/${i}`,
    failOnStatusCode: false,
  })
  .then((response) => {
    if (response.status >= 400) {
      return
    }
    req(++i)
  })
}

req(95)  // pass in the starting number (I use 95 to keep the example short)