cypressinterceptcypress-intercept

cy.intercept() in a loop till response of API call has expected value


I'm trying to intercept API call which is autorefreshed each 10s by using cy.intercept() in a loop. The loop should break when the status is set to "completed". Unfortunately cy.intercept() is called only once, I tried to debug it and I can see in the console that the function is called only once and then breaks even though the status is not set to "completed". What am I doing wrong?

export function stubBackendResponse(id: string): void {
  let iterations = 0;
  const maxIterations = 10;
  const intervalMs = 30000;

  function checkStatusAndLoop(): void {
    cy.intercept({
      method: "GET",
      url: `/api/${id}`,
    }).as("data");

    cy.wait("@data")
      .its("response.body")
      .should((data) => {
        const status = data[0].status;

        if (status !== "completed" && iterations < maxIterations) {
          iterations++;
          setTimeout(checkStatusAndLoop, intervalMs);
        } else if (
          status !== "completed" &&
          iterations >= maxIterations
        ) {
          throw new Error("Max iterations reached. Status is not 'completed'.");
        }
      });
  }

  checkStatusAndLoop();
}

Solution

  • The problem is probably trying to recurse within the callback of the .should() assertion.

    Generally should callbacks will only fire assertions (expect, assert) not alter the flow of the code.

    You could try it this way, with the recursion called within a normal function. No need for setTimeout() if you issue a new cy.wait("@data") as your recursive step.

    let iterations = 0;
    const maxIterations = 10;
    
    function checkStatus(interception => {
    
      if (iterations >= maxIterations) {
        throw new Error("Max iterations reached. Status is not 'completed'.")
      }
    
      const status = interception.response.body[0].status
      if (status !== "completed") {
        iterations++;
        cy.wait("@data").then(checkStatus)
      }
    })
    
    
    cy.intercept({method: "GET", url: `/api/${id}`}).as('data')  
    
    // visit the page to trigger the polling 
    
    cy.wait("@data").then(checkStatus)
    

    Another option is dynamic aliasing although I don't know if the alias can be assigned on the response phase (example is on request phase).

    let iterations = 0;
    const maxIterations = 10;
    
    cy.intercept({method: "GET", url: `/api/${id}`}, (req) => {
    
      iterations++
    
      if (iterations >= maxIterations) {
        throw new Error("Max iterations reached. Status is not 'completed'.")
      }
    
      req.continue(response => {
        const status = response.body[0].status;
        if (status === "completed") {
          req.alias = 'data'
        }
      })
    })
    
    cy.wait("@data")  // won't be set until response is correct,
                      // but this may be a chicken and egg failure