Sharing tips and tricks with others in the Postman community

Iā€™ve found in my short time working for Postman, that everyone I talk to uses the application in slightly different ways.

We all have our little tips and tricks that we use locally that have helped us with the tasks that we have been doing. These might be small things like creating a small helper function or much more bigger things.

I was inspired by @tmccannā€™s post this week about a particular way heā€™s created a process to solve a problem he was having, in his context.

This was new knowledge to me and Iā€™m sure it was new to others too but by sharing this information, itā€™s allowed me to know a little bit more about the different use cases that our users have.

I want to start a thread that people can contribute to and share these new and different ways that you have Postman configured to suit your own context. By sharing something, big or small, you are passing on this awesome new information to other people in the wider Postman community.

Iā€™m going to kick this off with something simple that solved a certain problem for me and hopefully, this encourages people to do the same.

I had a need to clear out my environment variables after a Collection run to start again in a known state for each run - I knew that I could use .unset() to clear each variable I had created but the code turned ugly really quickly when you have multiple repeated lines.

I was also aware of .clear() to go nuclear and wipe out the lot but I wanted to keep a few reusable variables like the baseURL for my requests.

I needed something that kind of sat in the middle of those two, thatā€™s when I created this little cleanup function.

function cleanup() {
    const clean = _.keys(pm.environment.toObject())
    _.each(clean, (arrItem) => {
        if (!arrItem.startsWith("some_prefix")) {
            pm.environment.unset(arrItem)
        }
    })
}
cleanup() 

This is just a simple piece of JS code that clears out all the variables, apart from the ones that start with a certain prefix. This was just a basic naming convention that I created to add some order to my environment files.

This function was placed in Tests tab of the last request in the Collection, it would grab all of the environment variables and if the name didnā€™t start with the prefix, it would clear it out of my environment file. Simple but very effective for me at the time.

What are the things that you have been using locally that you think others might find interesting and valuable?

14 Likes

Well, I donā€™t want @danny-dainton to have all the fun around here so here is a simple contribution from me. :smiley:

To go along the lines with my post earlier this week. I do a lot of CI/CD work, so that means I rely on Newman a lot.
For anyone that runs something similar, they know that you can console out everything or subsets of information when needed. However this usually means you have the add the console.log statements to the Pre-Request Scripts or the Tests tabs.

When you need to do debugging you can always add/remove these as needed but it is not good practice to leave those in there once you are done debugging. This is especially true if you are working on larger teams and not just a one person show.

In my case, I want to know when things go wrong but I donā€™t want to have to own everything and want to allow my user base to be able to troubleshoot without pulling me as I would become a bottle neck in the process.

What I did was I put this snippet of code in Tests tab of the collection:

var debug = pm.globals.get("debug");

// Check if the response body is JSON format.
try {
response = JSON.parse(responseBody);
} catch (exception) {
response = responseBody;
}

// Check debug logging is enabled, also that the response type is JSON and its not empty.
if (debug == 1) {
console.log(response);
}

What I am doing is setting a variable that I pass in as a parameter in my Jenkins job, this is a Global Variable that can be then passed into Newman on the command line.
My command would then look something like this:

newman run collectionName.json \
        --environment "environmentName.json" \
        --global-var debug=1 \
        --verbose \
        --disable-unicode \
        --color on \
        --delay-request 100

So what this is doing here is allowing me to pass that parameter as a Global var and run my collection.
First it will decide if the response is JSON or not. In my case it will either be JSON format or HTML but I am just doing a catch all here if its not JSON, you can improve on that as needed.

Once I know the response body format, I then check if we have debugging enabled or not as per mu parameter from my Jenkins job. It will be 0 for OFF and 1 for ON.

On each call in my collection it will then print out the response body if it is ON.
The outcome here is that when I look at the console logs in Jenkins, I see the responses for my calls and tell if there is a data issue that is causing things to fail.

As mentioned, this allow me to be hands off when there are issues and others can debug because the data is there in front of them. No need to pull me in to re-run and tell them where things are going wrong.

As always if you have questions or feedback, feel free to reach out :slight_smile:

8 Likes

Loosely Detecting Headers

Whilst testing a collection of APIs recently I found myself in a situation whereby I needed to detect if the response was JSON - pretty straight forward really in the grand schema of things. Postman has the pm.response.headers collection and the has method. However, thatā€™s quite strict and uses exact matching. Some of the responses I was dealing with where inconsistent in their Content-Type values. e.g.

Content-Type: application/json
Content-Type: application/json;charset=utf-8

At the time of writing there was no .contains() method, so I had to improvise and came up with the following:

const isJsonResponse = (pm.response.headers.filter((header) => header.value.indexOf('application/json') !== -1).length > 0);

if (isJsonResponse) { 
	... do stuff ... 
}

A JavaScript dev I know, improved upon the snippet using object destructing, making it:

const isJsonResponse = pm.response.headers.filter(({headerValue}) => headerValue.indexOf('application/json') > -1).length;

Itā€™s probably not going to change your life, but itā€™s something to keep in the tool box.

Oh, and one more thing!

The reason I needed a JSON detection snippet was because I was validating the JSON Schema of the API response. If youā€™re lazy like me, I can totally recommend a free tool for generating the JSON Schema from existing JSON over at Quicktype IO. Paste your JSON on the left hands side, and choose JSON Schema from the languages drop-down, thus:

https://app.quicktype.io?share=GxkZCPp7lZ1g3NpWhS6j.

4 Likes

Extracting values from a JSON response

I notice lots of questions from the community which all have a similar flavour - Users are having trouble extracting data from a JSON response body when the value is within an array.

These are normally JavaScript related questions, more than specific Postman questions but I want to use this thread to offer more support around this problem.

For these examples Iā€™ll be using the https://randomuser.me API, this returns lots of random user data, in a JSON format. This is perfect to help with explaining ways to extract data.

This is an example of the response body returned from the randomuser API:

{
    "results": [
        {
            "gender": "female",
            "name": {
                "title": "mrs",
                "first": "tilla",
                "last": "sƦvareid"
            },
            "location": {
                "street": "mogens thorsens gate 3652",
                "city": "alta",
                "state": "nordland",
                "postcode": "1152",
                "coordinates": {
                    "latitude": "49.9350",
                    "longitude": "113.7576"
                },
                "timezone": {
                    "offset": "-6:00",
                    "description": "Central Time (US & Canada), Mexico City"
                }
            },
            "email": "tilla.sƦvareid@example.com",
            "login": {
                "uuid": "ed63d908-5b6f-4cf5-9abe-0be5107000ed",
                "username": "tinybird773",
                "password": "planet",
                "salt": "Tx7V7veO",
                "md5": "c601d954f8144194fd1f2df8e4d8e05e",
                "sha1": "205292c0af997e9c95ae8bd1247a8962a2240eac",
                "sha256": "ddcf7358dda748e1c61c8ea09434e5d3fd7b5aedf90a922c44423fb48815ff09"
            },
            "dob": {
                "date": "1991-12-12T09:06:33Z",
                "age": 27
            },
            "registered": {
                "date": "2007-10-10T12:43:45Z",
                "age": 11
            },
            "phone": "24000987",
            "cell": "94325402",
            "id": {
                "name": "FN",
                "value": "12129107373"
            },
            "picture": {
                "large": "https://randomuser.me/api/portraits/women/8.jpg",
                "medium": "https://randomuser.me/api/portraits/med/women/8.jpg",
                "thumbnail": "https://randomuser.me/api/portraits/thumb/women/8.jpg"
            },
            "nat": "NO"
        }
    ],
    "info": {
        "seed": "dab43d72e88a90df",
        "results": 1,
        "page": 1,
        "version": "1.2"
    }
} 

Iā€™m going to do some basic global variable setting using the JSON response to demonstrate how to extract the values. You could also use this method to set the environment variables too.

Generally, users run into problems when the response body contains an array and this is when people tend to reach out for support because the code that would have been used the get the data from a single object, no longer works.

let jsonData = pm.response.json()

pm.globals.set('gender', jsonData.results.gender)

As you can see from the image, itā€™s created the global variable called gender but itā€™s failed to extract the value from the response.

As the results property type is an array with a list of objects within it, we need to specify which object we would like to use.

This response only has a single item and to get the data from within this object we reference it using [0]. Arrays are zero indexed so they start from 0 rather than 1 - This is an important thing to remember when working with JS arrays.

let jsonData = pm.response.json()

pm.globals.set('gender', jsonData.results[0].gender)

This time because we have added [0] to results, it has set the gender variable correctly. What thatā€™s basically doing is saying that we want to get the value from the "gender" key, in the first object of the array.


Hardcoding the array index number is going to be ok if you know that you only have a single object but if you have multiple objects, you would need to iterate through those objects to get each of the values.

This time weā€™re going to add some JavaScript code to loop through the results array and get all the values from the "first" key, within the "name" object. Donā€™t worry, this will all make sense with a few examples.

Within the Postman application, there are a few built-in modules that we can use to help us out. For this example, Iā€™m going to use the _.each() function from the Lodash module.

Using this function we can loop through the results array and grab each of the values we require, without the need to hardcode the index number.

let jsonData = pm.response.json()

_.each(jsonData.results, (result) => {
    pm.globals.set('firstName', result.name.first)
})

You can see in the image that we have extracted the "first" value and set this as the firstName global variable.

Thereā€™s a slight problem with this approach if there is more than 1 object. As we loop through the array, the variable is set but as there is more than 1 that value is overwritten with the last one. Not an ideal situation but we can add a couple of lines to our code to collect each of the first names, in the array.

This time we have created an empty array and assigned this to the firstNames variable. Next, within the loop, we are collecting each of the first names and using the .push() method to add these to the firstNames array.

Finally, when the loop has finished, we set a new firstNames global variable and use JSON.stringify() to save the array values as a string.

let jsonData   = pm.response.json()
let firstNames = []

_.each(jsonData.results, (result) => {
    firstNames.push(result.name.first)
})

pm.globals.set('firstNames', JSON.stringify(firstNames)) 

Now we can see that 5 different names have been set in the firstNames variable.


Just as a quick example - Iā€™ll add a Test using a loop so we can see how this can be used to check for a certain value.

The API has a number of filters, one of which is to return a specific gender, weā€™re also returning 100 results and that would take a while to check each one manually so we can create a Test to do this for us in super quick time.

GET https://randomuser.me/api/?results=100&gender=female

let jsonData = pm.response.json()

pm.test('The `female` filter returns the correct gender', () => {
    _.each(jsonData.results, (result) => {
        pm.expect(result.gender).to.equal('female')
    })
})


So this was a basic walkthrough of how to extract data from a response body which has an array of objects. There are a number of different ways that we could do this using some of the different Lodash functions but for now, Iā€™ve just kept it simple.

If this doesnā€™t make sense or you need to achieve something different, please reach out to me. :slight_smile:

14 Likes

Sending an array in a request using a data file in runner

I came across multiple questions around using an array in the part of a request body etc. using a data file in the collection runner, so hereā€™s how you can get it working.

The solution is to extract the array out of the data variable, then since local variables have a higher scope than the data variable so you can stringify the array and store it in a local variable.
You can read more about the different types of variables and their scopes here.

You can also make use of a different type of variable type like the environment or global but then in that case youā€™ll have to give your variable a different name in the request body (if youā€™re confused here, donā€™t worry youā€™ll figure it out after reading further).

For eg:.
My data file is a JSON file and it has the following content in it:

[
  {
    "productLine": [
      {
        "id": "13jj443",
        "name": "postman"
      },
      {
        "id": "9039dds",
        "name": "newman"
      }
    ]
  },
  {
    "productLine": [
      {
        "id": "8349dfkl",
        "name": "postman-api"
      },
      {
        "id": "4590yuqp",
        "name": "api-network"
      }
    ]
  }
]

And I need to send the productLine array of each iteration as a part of my request body to the server.

Steps:

  1. In my Pre-request script, I need to get the data from the iteration data of the run.
    Code:

    let productLine = pm.iterationData.get('productLine');
    
  2. I need to stringify the productLine array and store it in a local variable (since local variables have higher precedence over data file variables so they will be used in the request-sending flow) in the script.
    Code:

    pm.variables.set('productLine', JSON.stringify(productLine));
    
  3. In my request body, I need to make sure I donā€™t have any quotes (") around my productLine variable that I am using:
    For eg:

    {
      "productLine": {{productLine}}
    }
    
  4. That would be all, fire it away!

8 Likes

HI @tmccann, me too, I have debug logging all over the place.
Found this nifty javascript ā€˜short circuitingā€™ trick to shorten it to one line

If you make your debug variable truthy, eg.

const debug = pm.globals.has("debug") && (pm.globals.get("debug") == 1);

so that debug will be either true or false
then simply use

debug && console.log('.. debug info ...');

Iā€™ve found short circuiting really handy for this, but it can also reduce the unhandled exceptions (which can become a real nuisance in unattended execution)
eg, if your example normally returned a json response like

{
"result":{"success":true,"message":"the request succeeded"},
"value":"some json object"
}

then if you want to log the message with

debug && console.log(response.result.message);

would work fine as long as there is a message, but throws an exception if result wasnā€™t there.
Instead you could

debug && response.result && console.log(response.result.message);

which only tries to print message if there was a result.

(this relies on the concept of truthiness , check it out :grinning: )

5 Likes

@VincentD123 Thanks for the reply and welcome to the community.

That link to short circuiting is awesome.
This is the first time I have seen this and itā€™s something I am going to read into more as I can see how it would be useful.

2 Likes

Just a quick note on @jameshdā€™s Loosely Detecting Headers which I needed to do a week or so ago. Had a look at the Postman doc Headerlist object and down the page found the get(key) method.

pm.response.headers.get(ā€˜Content-Typeā€™).includes(ā€˜application/jsonā€™)

this matches both your cases

However, it assumes the key is'Content-Type' :thinking: not 100% sure if the key parameter is case-sensitive, or if the server is obliged to use case accurate 'Content-Type'?

This approach is kind of bad. if you want afterwards to iterate inside the array (which of course we want to do that, it is the whole point of having arrays), then you canā€™t use this because you saved the array as a string, not as an array. fo that you need to transoform that string back to an array. for example:
var fisrtNames = pm.globals.get(ā€œfisrtNamesā€); //this is my string extracted from a JSON array response on another request
var jsonData = JSON.parse(fisrtNames); // this is how I call back that string and transform it back to an array so I can actually use it

I have an array on the json path called standardized with two values from csv. I am unable figure on how to do GET based on the following json path below. Thanks

jsonData[0][ā€œextrasā€][ā€œstandardized_disaggregateā€]
jsonData[1][ā€œextrasā€][ā€œstandardized_disaggregateā€]

Hey @pete,

Not really sure if this is a question for me or not :smile:

Are you having an issue accessing part of your response? What you be about to add an example response body so we can see what those references relate too?

Iā€™m sure we can sort this out quickly :trophy:

Thanks, itā€™s a simple and great solution

1 Like

Small example from my daily Postman Usage.
I had to create a smoke test scenario executed via newman on Azure pipelines. We added it to the CI pipelines to be executed after we release the new app version. As failures are quite often in a rapid development environment, I had added a basic tripwire solution for any request that didnā€™t match the expected status code.

The Tests directory of the collection

pm.test("Response code is "+pm.variables.get("StatusCode")+" | "+pm.info.requestName, function () {
    pm.expect(parseInt(pm.response.code)).to.equal(parseInt(pm.variables.get("StatusCode")));
});

if(pm.response.status === "Created"){
    console.log("Id: " + pm.response.json().id);
}
if(pm.response.code >= 400 && parseInt(pm.response.code) != parseInt(pm.variables.get("StatusCode"))){
    console.log("response: "+pm.response.text());
    console.log("request: "+ request.data);
}

And before each test add simple one liner pre-resuest:

pm.collectionVariables.set("StatusCode", 200 ); // or 201, 204 and validation checks 410, 403 etc 

Benefits

  • I can expand my Smoke Test collection rapidly with new requests in such simple way.
  • Each created object has been logged in with a created ID that will be used in the next request paths like get by id or put.
  • If the Status code is unexpected, I had data about what were sent and what was received from server
  • Each test name is signed with a Request name to distinguish tests in the Azure report page.

But donā€™t forget to add robust assertions later :stuck_out_tongue_winking_eye:

Edited to show code, I thought it was going to add a nested response

@singhsivcan, first of all, AMAZING WORK!

This looks exactly like what I want to ask.

So letā€™s say that you want to store the value in ā€œidā€ when product name = ā€œnewmanā€ as a variable called ā€œnewNewmanIdā€ and the productLine response could be first in the array or later (not always in the same order).

How can I accomplish that? Thank you very much for any help!

cc: @danny-dainton, @tmccann

[
  {
    "productLine": [
      {
        "id": "13jj443",
        "name": "postman"
      },
      {
        "id": "9039dds",
        "name": "newman"
      }
    ]
  },
  {
    "productLine": [
      {
        "id": "8349dfkl",
        "name": "postman-api"
      },
      {
        "id": "4590yuqp",
        "name": "api-network"
      }
    ]
  }
]

My basic tip would be to use the Folder level scripts for anything that needs to run on multiple calls. And by using multiple levels of folders, common script code can be organized to apply to the right calls.

In my early days of writing calls I wound up quickly have many copies of the same code on multiple calls, and then ran around syncing them up when I had a change.

I also ran in to cases where I had common code used on similar API calls, that I would then use in 2 different folders (via copies of the calls) that each made up a flow that was run in Runner. To solve a specific problem I had, I would set a variable in the folder, and then use that string as part of my variable names so things were stored apart from each other (ie env variable names became dependent on the folder that was running them).

// setup
let   Profile = 		pm.environment.get("Profile");  // AWS or Azure, set at folder level Pre-Test
let   ServersActive = 	Profile + '_ServersActive' ; // profile name (pre-pended) env variable name
// use
let   Servers = pm.environment.get(ServersActive);

I also used this ā€œProfileā€ variable method to build the string for an API endpoint, so something like this:

let urlstring ;
switch(pm.environment.get("Profile")) {
     case "aws":
        urlstring = "/api/v1/aws-instances";          break;
     case "az":
        urlstring = "/api/v1/az-instances";           break;
}
pm.environment.set("instanceUrlString", urlstring);

Then in postman the call would reference {{instanceUrlString}}/somecall

Hereā€™s another basic example of folder level code that can be run repeatedly if you are generating names for things, like server names, or device names, it can be very helpful to have unique names used across all environments (prod, dev, qa), so device nameblah-17 will only exist once if the number part is incremented and never re-used. For this I made a global counter, and then increment it at the folder level for the calls that could create a given resourceā€¦ (I also coded in the env type (prod/dev/qa) to the name as well so names would be unique and show which code base generated them.)

build__number = parseInt(globals.builds__numbers);
if (build__number > -1) {  //it's a number, so increment it
build__number += 1;
} else {                  //else set it to 1
build__number = 1
}
pm.globals.set("builds__numbers",build__number);

I also have tried to not enter in any postman environment variable data by hand. So I try to have calls that are ā€œenv setupā€ calls, which can actually call this dummy endpoint (https://reqres.in/api/users/1) so they succeed, and in the pre-req tab, I seed the env variables with the needed data.

This makes it much easier to share the code with someone else, and can make it easier to go from one target API environment (development) to a new one like (qa) and setup the needed config data thatā€™s stored in the postman environment(s). It also allows for changing the data used when an API changes, or when environments have different requirements.

To tie this in to Dannyā€™s original post what I would do instead is whack all the env data and then rerun my setup-env call as part of the iteration. But I do like Dannyā€™s solution too. :slight_smile:

Hi, is it possible to do the same thing but instead of using each iteration in the request body to use all the data of all the iterations and send in one request? Iā€™ve been trying everything out in the community but still havenā€™t found a way. The variables keep resetting : Iā€™ve tried push as well and that is also reset. Same for persist. Thanks for any help on this.

How to reset all varaibles to initial value after script

Add this to first requestā€™s pre-request script:

 pm.environment.set("resetValue",pm.collectionVariables.toJSON().values)

This wiil store all initial value to resetValue variable

And in the last requestā€™s test script use

pm.environment.get("resetValue").forEach((a)=>pm.collectionVariables.set(a.key,a.value))

Now we are resetting all collectionVariables to the initially saved value

1 Like