Postman Tests and Scripts: Assertions, Chaining and Newman CLI
Clicking Send and reading the response is manual testing. The moment you add a script that checks the status, the body and the headers on its own, your Postman collection becomes an automated test suite. Add Newman and it runs every night in CI without anyone opening Postman. This lesson covers the pm scripting API, the assertions you’ll use daily, JSON Schema validation, chaining, data-driven runs and Newman with HTML reports, all on the live practice API.
pm.test('name', () => { pm.expect(...)... }) using the Chai assertion library. Pre-request scripts prepare data before sending. Values are passed between requests with pm.environment.set() / pm.collectionVariables.set(). The Collection Runner runs a collection with CSV/JSON data, and Newman (newman run collection.json -e env.json) runs it from the command line or a CI pipeline with CLI, JUnit and HTML reports.Pre-request vs Post-response Scripts
| Pre-request script | Post-response script (tests) | |
|---|---|---|
| Runs | Before the request is sent | After the response arrives |
| Typical use | Generate data, timestamps, signatures; fetch a token | Assertions; save values for later requests |
| Levels | Collection → folder → request (all run, in that order) | Request → folder → collection |
CryptoJS.HmacSHA256 before every call, so testers never calculate signatures by hand.The Assertions You’ll Use Every Day
// Status
pm.test('status is 200', () => pm.response.to.have.status(200));
pm.test('status is 2xx', () => pm.response.to.be.success);
pm.test('status is one of 200/201', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));
// Response time
pm.test('responds under 1000 ms', () => pm.expect(pm.response.responseTime).to.be.below(1000));
// Headers
pm.test('JSON content type', () => {
pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
});
pm.test('has ETag', () => pm.response.to.have.header('ETag'));
// Body values
const json = pm.response.json();
pm.test('user is Asha Verma', () => {
pm.expect(json.data.name).to.eql('Asha Verma');
pm.expect(json.data.age).to.be.a('number').and.to.be.above(17);
pm.expect(json.data.role).to.be.oneOf(['admin', 'editor', 'viewer']);
pm.expect(json.data.email).to.match(/^[^@\s]+@[^@\s]+$/);
});
// Arrays
pm.test('every product is a book', () => {
json.data.forEach(p => pm.expect(p.category).to.eql('books'));
});
// Negative / error format
pm.test('error has code and message', () => {
pm.expect(json.error).to.have.all.keys('status', 'code', 'message');
});
Try the last pattern on a 404 response:
https://sqatools.in/wp-json/sqa-api/v1/users/999curl -i \
"https://sqatools.in/wp-json/sqa-api/v1/users/999"to.equal checks strict identity (fine for strings and numbers); to.eql compares objects and arrays deeply. Use eql for {...} and [...].JSON Schema Validation in Postman
Postman includes a JSON Schema validator. Validate the whole structure in one assertion:
const userSchema = {
type: 'object',
required: ['id', 'name', 'email', 'role', 'age', 'active'],
properties: {
id: { type: 'integer', minimum: 1 },
name: { type: 'string' },
email: { type: 'string' },
role: { enum: ['admin', 'editor', 'viewer'] },
age: { type: 'integer', minimum: 18 },
active: { type: 'boolean' },
},
};
// Built-in assertion: wrap the user schema because the API returns { "data": user }
pm.test('user matches schema', () => {
pm.response.to.have.jsonSchema({ type: 'object', required: ['data'], properties: { data: userSchema } });
});
// Same check with the bundled Ajv library (shows detailed errors):
pm.test('user matches schema (Ajv)', () => {
const Ajv = require('ajv');
const ajv = new Ajv({ allErrors: true });
pm.expect(ajv.validate(userSchema, pm.response.json().data), JSON.stringify(ajv.errors)).to.be.true;
});
Tip: the practice API publishes its real contract at GET /schema/user. Fetch it once, store it in a collection variable, and validate every user response against it.
Chaining Requests: A Full CRUD Flow
Create a folder “CRUD flow” with five requests and these scripts:
| # | Request | Post-response script |
|---|---|---|
| 1 | POST {{baseUrl}}/objects | pm.test('201', () => pm.response.to.have.status(201));pm.collectionVariables.set('objId', pm.response.json().id);pm.collectionVariables.set('etag', pm.response.headers.get('ETag')); |
| 2 | GET {{baseUrl}}/objects/{{objId}} | Assert 200 and name |
| 3 | PATCH {{baseUrl}}/objects/{{objId}} with header If-Match: {{etag}} | Assert 200 and version === 2 |
| 4 | DELETE {{baseUrl}}/objects/{{objId}} | Assert 204 |
| 5 | GET {{baseUrl}}/objects/{{objId}} | Assert 404 |
Controlling the flow
// Skip the rest of the run if login failed
if (pm.response.code !== 200) {
pm.execution.setNextRequest(null); // older versions: postman.setNextRequest(null)
}
// Jump to a specific request by name
pm.execution.setNextRequest('Cleanup - delete object');
Pre-request Scripts: Get a Token Automatically
Put this at collection level so every request has a valid token, fetched only when the old one is missing or expired:
const expiresAt = pm.collectionVariables.get('token_expires_at');
if (!expiresAt || Date.now() > Number(expiresAt) - 30000) {
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/auth/login',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: { mode: 'raw', raw: JSON.stringify({
username: pm.environment.get('username'),
password: pm.environment.get('password'),
}) },
}, (err, res) => {
const body = res.json();
pm.collectionVariables.set('access_token', body.access_token);
pm.collectionVariables.set('token_expires_at', Date.now() + body.expires_in * 1000);
});
}
Then set the collection’s Authorization to Bearer Token = {{access_token}}.
Data-Driven Testing with the Collection Runner
Run one request many times with different inputs from a CSV or JSON file. Example age-boundaries.csv for POST /validate/user:
name,email,age,expectedStatus
Asha,asha@example.com,17,422
Asha,asha@example.com,18,200
Asha,asha@example.com,100,200
Asha,asha@example.com,101,422
Request body uses data variables:
{ "name": "{{name}}", "email": "{{email}}", "age": {{age}} }
Test script reads the expected value from the same row:
pm.test(`age ${pm.iterationData.get('age')} -> ${pm.iterationData.get('expectedStatus')}`, () => {
pm.response.to.have.status(Number(pm.iterationData.get('expectedStatus')));
});
In the Runner, select the request or folder, choose the data file, check the preview and click Run. Each CSV row is one iteration.
https://sqatools.in/wp-json/sqa-api/v1/validate/usercurl -i \
-X POST \
-H "Content-Type: application/json" \
-d '{"name":"Asha","email":"asha@example.com","age":17}' \
"https://sqatools.in/wp-json/sqa-api/v1/validate/user"Newman: Run Postman Collections from the Command Line
Newman is Postman’s command-line runner. It’s how collections run in Jenkins, GitHub Actions, GitLab CI or Azure DevOps.
# 1. Install (needs Node.js)
npm install -g newman newman-reporter-htmlextra
# 2. Export from Postman: collection (v2.1) and environment as JSON files
# 3. Run
newman run sqa-practice.postman_collection.json \
-e sqa-practice.postman_environment.json \
-d age-boundaries.csv \
-r cli,junit,htmlextra \
--reporter-junit-export results/junit.xml \
--reporter-htmlextra-export results/report.html
# Useful options
# --folder "CRUD flow" run one folder only
# -n 3 run 3 iterations
# --delay-request 200 wait 200 ms between requests
# --bail stop on first failure
# --env-var "password=$QA_PASSWORD" inject secrets from CI
Newman exits with code 1 when any test fails, which fails the CI build: exactly what you want.
Newman in GitHub Actions
name: api-tests
on: [push, workflow_dispatch]
jobs:
newman:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g newman newman-reporter-htmlextra
- run: |
newman run postman/sqa-practice.postman_collection.json \
-e postman/sqa-practice.postman_environment.json \
--env-var "password=${{ secrets.QA_PASSWORD }}" \
-r cli,htmlextra --reporter-htmlextra-export report.html
- uses: actions/upload-artifact@v4
if: always()
with: { name: newman-report, path: report.html }
Organising a Maintainable Postman Test Suite
- Folders per resource, plus “Smoke”, “Negative” and “Flows”.
- Reusable checks at collection level, e.g. a post-response script asserting every response is JSON and under 2 seconds.
- Names that state the expectation: “PATCH object with stale If-Match – 412”.
- No hardcoded ids or URLs; everything via variables.
- Each flow creates and cleans up its own data.
- Collections stored in Git (exported JSON) so changes are reviewed.
Reusable code across requests can be saved as a function in a collection variable or, in recent Postman versions, as package library scripts.
Postman vs Code-Based Automation
| Postman + Newman | Playwright (TypeScript) | |
|---|---|---|
| Start-up speed | Very fast, low code | Needs project setup |
| Version control and review | JSON exports (hard to diff) | Normal code reviews |
| Complex logic, reuse | Limited | Full language, fixtures, helpers |
| UI + API in one framework | No | Yes |
| Best for | Exploration, smaller suites, mixed-skill teams | Large long-lived regression suites |
Many teams use both: Postman for exploration and contract smoke tests, Playwright for the main regression suite (Module 6).
Interview Questions: Postman Scripts and Newman
What is the difference between pre-request and post-response scripts?
Pre-request scripts run before a request is sent to prepare data, headers, signatures or tokens. Post-response scripts run after the response arrives to make assertions and store values for later requests.
How do you write an assertion in Postman?
Use pm.test with a name and a function containing pm.expect or pm.response.to assertions, for example pm.test(‘status 200’, () => pm.response.to.have.status(200)).
Which assertion library does Postman use?
Chai’s BDD style (expect), exposed as pm.expect, plus Postman’s own pm.response.to helpers.
How do you validate JSON Schema in Postman?
Use pm.response.to.have.jsonSchema(schema) in recent versions, or the bundled Ajv library: create a validator, validate the parsed response and assert the result is true, printing ajv.errors on failure.
What is Newman?
The command-line collection runner for Postman. It runs exported collections with environments and data files, produces CLI, JUnit and HTML reports and returns a non-zero exit code on failures, which makes it suitable for CI/CD pipelines.
How do you do data-driven testing in Postman?
Use {{variables}} in the request, then run it in the Collection Runner or Newman with a CSV or JSON data file. Each row is an iteration, accessible in scripts through pm.iterationData.get().
How do you control the order of requests in a run?
Requests run in collection order by default; pm.execution.setNextRequest(‘name’) jumps to a specific request and setNextRequest(null) stops the run.
How do you send a request from inside a script?
Use pm.sendRequest with a URL or request object and a callback. It’s commonly used in pre-request scripts to fetch auth tokens.
How do you keep secrets out of shared Postman collections?
Store them as secret-type environment variables or in Postman Vault, keep real values only in “current value”, and inject them in CI with newman –env-var from the CI secret store.
How does Newman report failures to CI?
It exits with code 1 when any assertion fails or a request errors, causing the CI step to fail. JUnit XML reports can be published by the CI tool and HTML reports uploaded as build artifacts.