Scripts & tests
Two ways to check a response: declarative assertions stored as data, and JavaScript that runs in a sandbox with a Postman-compatible pm.* surface. The app authors tests as scripts; the declarative form is still read and run by the engine.
Declarative assertions
An assertion is a row in the request file. There is no code to review, no runtime to trust, and the failure message writes itself:
[[tests]]
kind = "status"
op = "eq"
value = 200
[[tests]]
kind = "json"
path = "$.data.items"
op = "len"
value = 25
[[tests]]
kind = "header"
name = "content-type"
op = "contains"
value = "application/json"
[[tests]]
kind = "duration"
op = "lt"
value = 500
Four kinds exist, and these are all the operators each one takes:
| Kind | Fields | Operators |
|---|---|---|
status | value (number) | eq, ne, lt, gt |
json | path (JSONPath), value (optional) | eq, ne, exists, absent, contains, matches, lt, gt, len |
header | name, value (optional) | exists, absent, eq, contains, matches |
duration | value (milliseconds) | lt, gt |
Notes that save you a debugging session: path is a real JSONPath rooted at $; matches takes a regular expression; len asserts on the length of a string, array or object; header names are matched case-insensitively; and numeric comparison is numeric, so 1 and 1.0 compare equal. A json assertion against a body that is not JSON fails with that reason rather than pretending the path was missing.
Scripts
Each request can carry a pre-request script and a post-response script. They run in a QuickJS engine embedded in the Rust core — not in Node, not in the browser, not in a hidden window.
Order of execution for one request: pre-request script → variable resolution → send → declarative assertions and captures → post-response script.
The pm.* surface
This is the complete list. Anything not here does not exist:
pm.info—requestName,eventName,iteration,iterationCount.pm.environment,pm.variables,pm.globals,pm.collectionVariables— the same variable bag under four names, withget,set,unset,has,clear,toObjectandreplaceIn. Mándalo has one scope, not four; the aliases exist so pasted Postman scripts keep working.pm.request—method,url,body, all writable from a pre-request script, pluspm.request.headerswithadd,upsert,remove,has,get,all,eachandcount. Headers are{ key, value }objects. Edits made before the send are applied to the outgoing request.pm.response— only in a post-response script.code,status,responseTime,responseSize,text(),json(), andheaderswithget,hasandall.pm.response.to—to.have.status(200)orstatus("OK"),to.have.header(name),to.have.body(),to.have.jsonBody(),to.be.ok,to.be.success,to.be.clientError,to.be.serverError, andto.not.…for all of them.pm.test(name, fn)— runsfn, records a pass or a failure with the thrown message. A failing test never aborts the run.pm.expect(value)— a Chai-shaped assertion chain:equal/eql,a/an,above/below/least/most,include/contain,property,lengthOf,match,oneOf, thetrue/false/null/undefined/ok/emptyflags, andnotto negate.console.log,info,debug,warn,error— collected and shown with the response.
// post-response
pm.test("charge was created", function () {
pm.response.to.have.status(201);
const body = pm.response.json();
pm.expect(body.currency).to.equal("eur");
pm.expect(body.id).to.be.a("string");
});
pm.environment.set("chargeId", pm.response.json().id);
console.log("captured", pm.environment.get("chargeId"));
What we deliberately don't support
The sandbox is small on purpose, and every gap throws a message telling you what is missing and why rather than failing as undefined:
- No Node APIs and no modules.
require,module,exportsandprocessare absent. There is no module loader, so there is no supply chain to audit inside your API client. - No filesystem. A script cannot read or write files. Your collections cannot be modified by a script that came in with an import.
- No network from scripts.
fetch,XMLHttpRequest,WebSocketandpm.sendRequestall refuse. A request should send one request; if you need a chain, chain requests and use captures. - No timers and no async.
setTimeout,setInterval,setImmediateandqueueMicrotaskare absent — scripts are synchronous, so a run finishes deterministically. - No browser or host globals.
window,document,localStorage,DenoandBunare absent. - Bounded execution. Each script gets a memory ceiling and a wall-clock timeout (32 MB and 2 seconds by default). An infinite loop is interrupted; it does not hang the app.
- Not implemented yet, and they say so:
pm.execution,pm.cookies,pm.visualizer,pm.iterationData,pm.vaultandpm.setNextRequesteach throw an explanatory error when touched.
Postman scripts that only assert on the response and move variables around usually run unchanged. Scripts that fetch a second URL, use a cookie jar, or drive the collection runner need rewriting. The import brings scripts across verbatim and flags them so you can find out at review time rather than at run time.
Which one should you use
Use a declarative assertion whenever it expresses the check. It is data, so it reviews as data, it cannot loop forever, and the assertion name in the result is generated from the rule itself. Drop into a script when you need a computed expectation, a value derived from several fields, or a shape check that no single operator covers.