Collection Pre-Script should only be run once

Thatā€™s an image of a line mentioning that the functionality that the user was mentioning would be a new feature request.

It doesnā€™t say anything about one being raised or anything in progress. :grinning_face_with_smiling_eyes:

If you would like that to be a new feature request, I would recommend raising it on our Github Issue Tracker. :trophy:

Did anyone ever open an issue for this feature request? I think I have a good use case for something like this but I was unable to find an issue on github that looked related. My use case would be running an auth request and setting a variable with the token value only once before all requests in a collection instead once before each request. The workaround mentioned above would certainly work but would be better if there was a built in solution.

why canā€™t you just ad it in the pre-request first script and use the variable in rest of the scripts ?

Iā€™m not sure I understand your question completely. Technically, I can write a script to make the auth call and set the variable. That part is not the issue. The issue comes with the pre-request script running once before each/every request in the collection. If I have 50 requests I want to run, I can re-use the same token for each as long as itā€™s not expired. There is no need to run the auth request 50 times. What I would like to do, and if I read the thread correctly, the idea is to run the auth request once and only once, set the variable, and then use the variable in each request. From what I gather, this is not how pre-request scripts work. The text at the top of the page even says ā€œThis script will execute before every request in this collectionā€.

Am I misunderstanding how this works?

@amilbeck-imc THis behaviour happens when you use a collection-level pre-request script. so if you want to run it only once then add it as the pre-request of the first request.

Else add a if condition in collection level


if(pm.info.requestName==="first request name" ){
   // do this
}

so this will run only for first request

2 Likes

Got it. Thanks! Appreciate the help!

1 Like

I found this question when searching for a way to have a script run once at the beginning of a Run. To have something run just once I checked the value of pm.info.iteration which gives the index of the current run (so it starts at zero: 0, 1, 2 etc). You can also use pm.info.iteractionCount which will give you the total number of iterations, if that helps in any other way.

Hereā€™s an example of the code I used:

if (pm.info.iteration == 0) {
        // Put code you want run just once at the beginning here
    }

and you could use this for code at the end of a Run:

if (pm.info.iteration == pm.info.iterationCount - 1) {
        // Put code you want run just once at the end here
    }

@joshuamcvey1111 , when you say use pm.info.iteration or pm.info.iterationCount, does that mean this would only work when running the whole collection or when making the collection be data driven?

I just tried both of your suggestions and they did not work. Mind you I only ran a single folder and not the whole collection.

I was actually shocked to realize (after having spent days trying to run a ā€œrun once and get me the auth tokenā€ that this script would be executed for EACH request in the collection.

And of course, now I see the fine print.

So this is more of a beforeEach hook and not a before (or beforeAll) hook.

Iā€™ll keep reading their docs but the suggested solutions above feel hackish. But if they work, and there arenā€™t other alternativesā€¦might as well.

I think the name is a bit misleading. At least call it ā€œCollection Pre-Per-Request Scriptā€. Thereā€™s no ambiguity there; not any need for fine print.

@filoso

If you are putting an auth script in the collection pre-request scripts (which I also do), then I would recommend that you also store the token expiry date and have an IF statement checking the date.

This way the auth only re-runs if the date threshold has been hit.

The following is an example of this (but please store in environment variables, not collection).

pm.test("Check for collectionVariables", function () {
    let vars = ['clientId', 'clientSecret', 'tenantId', 'username', 'password', 'scope'];
    vars.forEach(function (item, index, array) {
        console.log(item, index);
        pm.expect(pm.collectionVariables.get(item), item + " variable not set").to.not.be.undefined;
        pm.expect(pm.collectionVariables.get(item), item + " variable not set").to.not.be.empty; 
    });

    if (!pm.collectionVariables.get("bearerToken") || Date.now() > new Date(pm.collectionVariables.get("bearerTokenExpiresOn") * 1000)) {
        pm.sendRequest({
            url: 'https://login.microsoftonline.com/' + pm.collectionVariables.get("tenantId") + '/oauth2/v2.0/token',
            method: 'POST',
            header: 'Content-Type: application/x-www-form-urlencoded',
            body: {
                mode: 'urlencoded',
                urlencoded: [
                    { key: "client_id", value: pm.collectionVariables.get("clientId"), disabled: false },
                    { key: "scope", value: pm.collectionVariables.get("scope"), disabled: false },
                    { key: "username", value: pm.collectionVariables.get("username"), disabled: false },
                    { key: "password", value: pm.collectionVariables.get("password"), disabled: false },                    
                    { key: "client_secret", value: pm.collectionVariables.get("clientSecret"), disabled: false },
                    { key: "grant_type", value: "password", disabled: false },
                ]
            }
        }, function (err, res) {
            if (err) {
                console.log(err);
            } else {
                pm.test("Status code is 200", () => {
                    pm.expect(res).to.have.status(200);
                });
                let resJson = res.json();
                pm.collectionVariables.set("bearerTokenExpiresOn", resJson.expires_in);
                pm.collectionVariables.set("bearerToken", resJson.id_token);
            }
        });
    }
});

@michaelderekjones Thank you very much for that. I will take a closer look at it later. I do like the idea of leveraging the expiration date of the token as a gate check.

In general, it does surprise me how often we have to resort to custom code yet we donā€™t make the leap to just use libraries/packages like axios/got and/or jest.

I do understand why youā€™d want to put all of the variables at ENV level but I also wonder why you have it there at the COLLECTION level?

Again, thank you sir.

Itā€™s just some old code that I used. I havenā€™t got around to correcting it. :face_with_open_eyes_and_hand_over_mouth:

Ha! Another case of ā€œdo as I say, not as I doā€. But yeah, Iā€™m with you. It makes total sense to store all of these variables/tokens at the environment level since Iā€™m sure they are different (or they should be) for different environments.

Thanks again, Mike!

Ran into a bit of a glitch when trying to implement your approach but I wonder if itā€™s just particular to my set up.

The value for your bearerTokenExpiresOn key, is it coming back to you in units of seconds? And you are multiplying it by 1000 to convert it to milliseconds right? And then converting it to a Date.

For me, the value comes back as 3599, which is essentially an hourā€™s time. But when I convert it to to a Date, so 3599000, ends up being some Date in 1969, taking into account the Timezone and Daylight Savings time, etc. So Date.now() NEVER ends up being greater (later than) the expiration date/time of the auth token.

Did you have to work around that?

You are correct. I can remember fixing this. I need to find the code where I corrected this and Iā€™ll repost the example. (Iā€™ve just had a quick look though and canā€™t find it).

If I remember rightly, where I set the bearerTokenExpiresOn variable, I did the calculation there and saved the variable as a proper date\time stamp.

It would be something like.

pm.environment.set("bearerTokenExpiresOn", Date.now() + resJson.expires_in * 1000);

Which would change the evaluation to something similar to the followingā€¦

if (Date.now() > pm.environment.get("bearerTokenExpiresOn") {

}

I need to re-write and test this, as I think I might have posted that code a few times in the last six months or so.

Found it and apologies for copy\pasting old code.

pm.test("Check for Environment Variables", function () {
    let vars = ['clientId', 'clientSecret', 'tenantId', 'username', 'password', 'scope'];
    vars.forEach(function (item, index, array) {
        // console.log(item, index);
        pm.expect(pm.environment.get(item), item + " variable not set").to.not.be.undefined;
        pm.expect(pm.environment.get(item), item + " variable not set").to.not.be.empty; 
    });

    if (!pm.environment.get("bearerToken") || Date.now() > pm.environment.get("bearerTokenExpiresOn")) {
        pm.sendRequest({
            url: 'https://login.microsoftonline.com/' + pm.environment.get("tenantId") + '/oauth2/v2.0/token',
            method: 'POST',
            header: 'Content-Type: application/x-www-form-urlencoded',
            body: {
                mode: 'urlencoded',
                urlencoded: [
                    { key: "client_id", value: pm.environment.get("clientId"), disabled: false },
                    { key: "scope", value: pm.environment.get("scope"), disabled: false },
                    { key: "username", value: pm.environment.get("username"), disabled: false },
                    { key: "password", value: pm.environment.get("password"), disabled: false },                    
                    { key: "client_secret", value: pm.environment.get("clientSecret"), disabled: false },
                    { key: "grant_type", value: "password", disabled: false },
                ]
            }
        }, function (err, res) {
            if (err) {
                console.log(err);
            } else {
                pm.test("Status code is 200", () => {
                    pm.expect(res).to.have.status(200);
                });
                let resJson = res.json();
                pm.environment.set("bearerTokenExpiresOn", Date.now() + resJson.expires_in * 1000);
                // console.log(pm.environment.get("bearerTokenExpiresOn"));
                pm.environment.set("bearerToken", resJson.id_token);
            }
        });
    }
});
1 Like

No apologies necessary @michaelderekjones. Iā€™ll take a look at your new code again but I think Iā€™ve also arrived already at a similar solution. Thatā€™s to say, Iā€™ve managed to get it working for my setup. But of course Iā€™ll compare and ā€œborrowā€.

I think the crux is hand entering an initial starting value and thereafter having the automation take care of keeping it up to date. It seems to be working so far but itā€™s only been a few minutes. Gotta wait for it to expire and then rerun.

I got tripped up when exporting the collection and the ā€œenvironmentā€ *.json files and the Initial vs. Current values. All those nooks and crannies!

I do notice that you are doing this from via a Test Script right? Whereas I am doing it via the Pre-Request Script for the whole collection.

I wonder if there are any advantages to the way you are doing it? Other than being able to assert and sue the pm.test object.

This is in a collection pre-request script. You can have tests in pre-request scripts and can use them to control whether the code following the assertion runs or not. If the tests fail, then it will stop executing any more code. For example, there is no point in trying to set the bearerToken if the sendRequest returned a non 200 status code. If any of the required environment variables are missing, then it wonā€™t trigger the sendRequest().

Iā€™ve also cleaned it up a bit and put the IF statement first, and moved some of the code around so its a bit cleaner. Iā€™ve put in some recommended console logs (and them remarked them out). Iā€™ve also amended the test case names, so its obvious that the tests were run from the pre-request scripts.

let currentDateTime = Date.now();
let tokenExpiry = pm.environment.get("bearerTokenExpiresOn")
// console.log("currentDateTime: " + currentDateTime);
// console.log("tokenExpiry: " + tokenExpiry);
if (!pm.environment.get("bearerToken") || currentDateTime > tokenExpiry) {
    pm.test("Pre-request check for Environment Variables", function () {
        let vars = ['clientId', 'clientSecret', 'tenantId', 'username', 'password', 'scope'];
        vars.forEach(function (item) {
            // console.log(item);
            pm.expect(pm.environment.get(item), item + " variable not set").to.not.be.undefined;
            pm.expect(pm.environment.get(item), item + " variable not set").to.not.be.empty;
        });
        pm.sendRequest({
            url: 'https://login.microsoftonline.com/' + pm.environment.get("tenantId") + '/oauth2/v2.0/token',
            method: 'POST',
            header: 'Content-Type: application/x-www-form-urlencoded',
            body: {
                mode: 'urlencoded',
                urlencoded: [
                    { key: "client_id", value: pm.environment.get("clientId"), disabled: false },
                    { key: "scope", value: pm.environment.get("scope"), disabled: false },
                    { key: "username", value: pm.environment.get("username"), disabled: false },
                    { key: "password", value: pm.environment.get("password"), disabled: false },
                    { key: "client_secret", value: pm.environment.get("clientSecret"), disabled: false },
                    { key: "grant_type", value: "password", disabled: false },
                ]
            }
        }, function (err, res) {
            if (err) {
                console.log(err);
            } else {
                pm.test("Pre-request Microsoft login Status code is 200", () => {
                    pm.expect(res).to.have.status(200);
                    let resJson = res.json();
                    // console.log(resJson);
                    pm.environment.set("bearerToken", resJson.id_token);
                    pm.environment.set("bearerTokenExpiresOn", Date.now() + resJson.expires_in * 1000);
                    // console.log("bearerTokenExpiresOn: " + pm.environment.get("bearerTokenExpiresOn"));
                });
            }
        });
    });
};

As always, you should get this to work, and then get your tests to fail to ensure you are not getting a false positive. For example, remove the value for scope from the environment variable (or invalidate the password). You should be able to trigger a failure with both tests.

pm.test can be used in the pre-request script

That I did not know.

Agree w/ you on all the other comments. I thought it odd that the call to get the token had 0 tests/assertion at all.