-1

My axios request polling created for Cypress is not actually polling the request. Could someone please advise how can we fix the problem ?

Note: For some reason I would like to do this with axios only!

In the subscriptionChange.spec.js file..

helperFunctions
                .checkSubscriptionId(apiEndpointUrl, tokenDetails)
                .then((subscriptionId) => {
                  console.log("Sub id data::" + subscriptionId);

                  if (subscriptionId != null) {
                    cy.request({
                      method: "POST",
                      url:
                        apiEndpointUrl +
                        `/someurl/v1/subscription/${subscriptionId}/change`,
                      headers: {
                        Authorization: "Bearer " + tokenDetails,
                      },
                      body: {
                        userId: userId,
                        planCode: "plus_annual",
                      },
                    }).then((resp) => {
                      console.log("Change Subscription response ::" + resp);
                      const planName = resp.body.subscriptionChange.plan.name;
                      const planCode = resp.body.subscriptionChange.plan.code;
                      expect(resp.status).to.eq(200);
                      expect(planCode).to.contain("plus_annual");
                      expect(planName).to.contain("Plus Annual");
                    });
                  }
                });

helperFunctions.js

class helperFunctions {
  checkSubscriptionId = async (apiEndpointUrl, tokenDetails, retryNo = 0) => {
    const options = {
      headers: {
        Authorization: "Bearer " + tokenDetails,
      },
    };

    return await axios
      .get(apiEndpointUrl + "/someurl/v1/user/info", options)
      .then((res) => {
        console.log("RESPONSE ==== : ", res);
        const subscriptionId =
          res.data.user.subscription.gateway_subscription_id;

        if (retryNo >= NUM_RETRIES) {
          throw new Error(
            `Subscription not found after ${NUM_RETRIES} retries`
          );
        }
        // If it is found, return
        if (subscriptionId || retryNo >= NUM_RETRIES) {
          return;
        } 
         return new Cypress.Promise((resolve) => {
          setTimeout(() => {
            console.log("Retrying ::" + retryNo);
            resolve(
            this.checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo + 1)
            );
          }, 2000);
        });
      })
      .catch((err) => {
        console.log("ERROR: ====", err);
      });
  };
}
soccerway
  • 10,371
  • 19
  • 67
  • 132

4 Answers4

4

Is it this line?

this.checkSubscriptionId(tokenDetails, retryNo + 1)

The parameters don't match up, maybe use

this.checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo + 1)

To fix the delay, add the recursive call in a callback

// try again after delay
this.delay(retryNo, () => {
  this.checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo + 1);
});
          
...

// delay function..
delay(retryNo, callback) {
  setTimeout(() => {
    console.log("Retrying ::" + retryNo);
    callback();
  }, 2000);
}
user16695029
  • 3,365
  • 5
  • 21
4

This question is very similar Looping API calls with Axios and Promises.

There are two notes that are missing in your code, return on delay and adding a promise around setTimout()

return delay(2000).then(() => {
  return this.checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo + 1)
})

...

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
Moustapha
  • 143
  • 6
2

Using the sample given by Cypress Request Polling as basis, substituting axios for cy.request():

  • you don't need async/await since you are using .then()

  • checking the retries at the top of the recursive call to simplify

  • moving setTimeout into the function for clarity

  • adding return subscriptionId so you get the value outside

  • adding return on the recursive call so that the value found deeper is also returned

checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo = 0) {

  if (retryNo >= NUM_RETRIES) {
    throw new Error(`Subscription not found after ${NUM_RETRIES} retries`);
  }

  const options = {
    headers: { Authorization: "Bearer " + tokenDetails }
  }

  return axios.get(apiEndpointUrl + "/someurl/v1/user/info", options)
    .then((res) => {
      console.log("RESPONSE ==== : ", res)

      const subscriptionId = res.data.user.subscription.gateway_subscription_id;

      if (subscriptionId) {
        // break out of the recursive loop
        return subscriptionId;                                    
      }

      return new Cypress.Promise((resolve) => {
        setTimeout(() => {
          console.log("Retrying ::" + retryNo);
          resolve(this.checkSubscriptionId(apiEndpointUrl, tokenDetails, retryNo + 1))
        }, 2000)
      })
    })
}

As proof of concept, this simple test works for setTimeout()

cy.then(() => {
  return new Cypress.Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(42)
    }, 1000)
  })
})
.should('eq', 42)
  • Thank you very much. In the second retry I can see `subscriptionId ` is available, but then cypress failed to process the last request based on above id. – soccerway Mar 08 '23 at 00:00
  • Can you please clarify what is happening? I can't get the picture. – Rachel.C.Carey Mar 08 '23 at 00:05
  • Sure, once the `subscriptionId ` is found and it should return back to the test to process the cancel request.. that processing request is not happening ! – soccerway Mar 08 '23 at 00:08
  • Ok, not sure about the cancelling but I think `setTimeout()` is blocking the return statement inside it's callback. Let me try something. – Rachel.C.Carey Mar 08 '23 at 00:13
  • Thanks, I have added the cancel cy.request() part to the question... – soccerway Mar 08 '23 at 00:15
  • The problem is that `setTimeout()` isn't letting the inner `return` value pass outside. You can try wrapping with a Promise and use the `resolve` parameter, but I'm not sure how it behaves inside the axios then function. – Rachel.C.Carey Mar 08 '23 at 00:22
  • I have tried many ways, I am getting the `subscriptionid` inside the test after second retry, but cypress still not executing the cy.request to cancel it...Assume that, we cant' mix test with axios request and cy.request calls.. not sure – soccerway Mar 08 '23 at 01:00
  • Wonderful help by the way, – soccerway Mar 08 '23 at 01:01
  • *assume we cant' mix test with axios request and cy.request* I don't think so, they are independent calls and axios finishes before cy.request starts. Check this line `const subscriptionId = data.user.subscription.gateway_subscription_id;` it seems to be repeating the code already done in the helper function (assuming you returned the subscriptionId as per above). – Rachel.C.Carey Mar 08 '23 at 01:23
  • I tried that already, as per the latest code, i have already got the `subscriptionId ` returned by the checkSubscriptionId function...but still final cy.request is not processing, just ignoring that one in the spec file .... – soccerway Mar 08 '23 at 01:26
  • It sounds like you misunderstood - `data` should already be `subscriptionId` because you already extracted it from `res.data.user.subscription.gateway_subscription_id` in the helper, so there would be no need to try to extract it again. – Rachel.C.Carey Mar 08 '23 at 01:54
  • Yes, exactly, I was trying to say the same thing. Thank you ! – soccerway Mar 08 '23 at 02:17
  • If you've fixed that error, please change it in your code. Otherwise another person will answer to point out the same mistake. – Rachel.C.Carey Mar 08 '23 at 21:24
  • Thanks for making that change. Can I ask if you can perform a "dummy" cancel with `cy.request` just by hard-coding the subscriptionId (and commenting out axios) - if that still fails then the problem is in the `cy.request()` itself - i.e test that part in isolation. – Rachel.C.Carey Mar 08 '23 at 22:00
  • When I use `cy.request()` in the helperFunctions.js instead of axios, it is working fine. So should i need to hard code the value and see it again ? – soccerway Mar 08 '23 at 22:08
  • Ok, that's similar to what I suggest and proves the point - the cancelling cy.request has the right parameters. Is there any difference in `subscriptionId` when axios is used? – Rachel.C.Carey Mar 08 '23 at 22:12
  • No, the `subscriptionId` coming as same while using axios request.. But cypress is not considering executing the cy.request with cancel end point. – soccerway Mar 08 '23 at 22:15
  • I have tried adding `if (subscriptionId != null) { ...put the cancel req here }` , also adding the cancel request in new Promise ...nothing works – soccerway Mar 08 '23 at 22:17
  • You say `cypress is not considering executing the cy.request` but how do you know that it's not executing? Is the Cypress log showing anything for the cy.request? I would suspect it's the server that is ignoring the cy.request POST. Can you see what happens on the server? – Rachel.C.Carey Mar 08 '23 at 22:23
0

You're not waiting for delay() to actually delay anything; all it does is schedule a console.log() for later.

I'd highly recommend the axios-retry library for this

import axios, { AxiosError } from "axios";
import axiosRetry from "axios-retry";

const axiosInstance = axios.create();

// Add an interceptor to fail responses without subscription ID
axiosInstance.interceptors.response.use(
  (res) => {
    const subscriptionId = res.data.user?.subscription?.gateway_subscription_id;

    if (!subscriptionId) {
      const err = new AxiosError(
        "Pre-condition failed",
        "412",
        res.config,
        res.request,
        res
      );
      err.status = 412;
      return Promise.reject(err);
    }
    return res;
  },
  null,
  {
    synchronous: true,
  }
);

axiosRetry(axiosInstance, {
  retries: NUM_RETRIES,
  shouldResetTimeout: true,
  retryDelay: () => 2000,
  retryCondition: (err) => err.status === 412,
});
checkSubscriptionId = async (baseURL, token) =>
  (
    await axiosInstance.get("/someurl/v1/user/info", {
      baseURL,
      headers: { authorization: `Bearer ${token}` },
    })
  ).data;
Phil
  • 157,677
  • 23
  • 242
  • 245