skip to content

A Postman sandbox script uses bare tests[...] and still passes — why isn't that proof it is current API?

level: middleimportance: must knowfreq 62%

answer

  1. The old names still answer you
  2. It complains once, not every time
  3. A passing run is not a verdict
  4. A pragma switches the layer off

basics

~20 s

Postman's sandbox keeps a legacy compatibility layer that warns once per identifier name and then answers normally, so a bare tests[...] assignment running successfully proves only that the shim exists, not that the spelling is current.

solid answer

~40 s

The sandbox installs a **legacy compatibility layer** — internally a `LEGACY_GLOBS` proxy — that keeps the older bare-global spelling alive: `tests[...]`, `responseCode`, `responseBody`, `postman.setEnvironmentVariable`, `postman.setNextRequest`, plus bundled library globals such as `tv4`, `xml2Json`, `CryptoJS` and `_`. Touching one of those names **warns once for that name** and then returns the real value, so the script keeps working and later reads are silent. A green run therefore proves only that the shim is present; it says nothing about whether the spelling is current. Read the console for the first-touch warning, or put `"use sandbox2";` at the top of the script to opt out of the layer entirely and let those identifiers fail outright. The current spellings all hang off one object: `pm.response.code`, `pm.response.text()`, `pm.environment.set`, `pm.execution.setNextRequest`.

code

javascript · 2 lines
javascript
var legacy = responseCode.code;
var current = pm.response.code;

go deeper

for a junior

Be ready to recognise the old spelling on sight: bare tests, responseCode, responseBody and a postman object, versus everything current hanging off pm. Knowing the pairing is enough at this level.

for a middle

Explain the mechanism: the compatibility layer warns once for an identifier name and then returns the real value, so the script never breaks. That is why a passing run carries no information about which spelling was used.

for a senior

Show how you would prove the negative rather than assume it. Name the console first-touch warning and its blind spot, and reach for the use sandbox2 pragma to convert a silent success into a hard failure.

for a principal

Own the wider point: any compatibility layer that warns instead of failing quietly converts a migration into an indefinite one. Decide where the enforcing switch lives so old spellings stop entering new work.

## The mechanism: warn once, then answer Postman's script sandbox is a JavaScript execution environment with a single current entry point: the `pm` object. Everything the current API offers a script — reading the reply, writing a variable, naming a test, steering the run — hangs off that one object. Beside it the sandbox installs a **legacy compatibility layer**, held internally as a `LEGACY_GLOBS` proxy, which keeps the older *bare global* spelling working. When a script touches one of those old names, the proxy does exactly two things, in this order: 1. it emits a **deprecation warning for that identifier name**, once; 2. it **returns the real value anyway**. It does not throw. It does not hand back `undefined`. It does not mark the result as degraded. The script runs to completion and the assertions evaluate. That single design decision is the whole trap, and it is the point of this topic: **an identifier that resolves through a compatibility shim is not evidence that it is current API.** ## What the layer covers The legacy surface is a fixed set of names, not an open-ended alias mechanism. Broadly it holds the old assertion object, the old `postman` command object, two bare reply readers, and a handful of bundled library globals. | legacy spelling | current spelling | |---|---| | `tests["name"] = value` | `pm.test(name, fn)` | | `responseCode` | `pm.response.code` | | `responseBody` | `pm.response.text()` | | `postman.setEnvironmentVariable(key, value)` | `pm.environment.set(key, value)` | | `postman.setNextRequest(name)` | `pm.execution.setNextRequest(name)` | | `tv4`, `xml2Json`, `CryptoJS`, `_` | bundled libraries, reached through the sandbox's module surface | Read that strictly as a **naming map**. It tells you what to write instead; it does not tell you what the right-hand call *does*. Each current name has its own behaviour and its own rules, owned by the surface it belongs to — the variable stores, the named-test recorder, the sequence control. The migration skill this topic is about is narrower and more mechanical: recognising the left column on sight and knowing the right column exists. ## Why a green run tells you nothing - The shim **answers**, by design, so behaviour is preserved. Nothing in the result distinguishes a value read through a legacy global from the same value read through `pm`. - The warning fires on the **first touch of a name**, not on every use. A name read forty times in one script produces one warning line, so the volume of warnings badly understates how much legacy code is actually there. - A warning is a warning. It does not fail an assertion and it does not change what the report records as passing. In a busy console it is easy to miss entirely. - Because the old spelling warns rather than fails, legacy scripts survive for years and get copied forward into new requests by people who reasonably assume that working code is current code. - Nothing in the collection file marks a script as legacy. The script is stored as source text; the sandbox decides at execution time which names it will answer. ## Turning the warning into a failure The sandbox hands the script itself a switch. The pragma `"use sandbox2";`, placed at the top of the script, opts that script out of the legacy layer entirely. With it in place the old names are simply not installed, so a bare `responseCode` is an undefined identifier and the script fails outright instead of quietly succeeding. That inverts the signal you get. Without the pragma, legacy usage shows up as a passing run plus a console line you may never read. With it, legacy usage shows up as a failing script that names the line. It is the difference between an advisory and a check. ## Reading a script you did not write 1. **Scan for the `pm` prefix.** Current script API is reached through `pm`. A bare identifier that is not a JavaScript builtin and not a variable the script declared is a candidate legacy global. 2. **Read the console output of one run.** First-touch warnings name the identifiers the layer answered. Treat the list as a floor, never a total. 3. **Add the pragma to prove the negative.** A script that still passes with `"use sandbox2";` at the top genuinely does not depend on the layer. 4. **Rewrite name by name** using the map above, rather than rewriting the logic, so the change stays reviewable. The honest summary for an interview: the old spelling still runs, the sandbox tells you so exactly once, and "it works" is the one piece of evidence that carries no information here.

  • How would you make the sandbox fail on a legacy identifier instead of warning about it?
    Put the pragma `"use sandbox2";` at the top of the script. That script is then run without the legacy compatibility layer installed, so a bare `responseCode` or `tests[...]` is an undefined identifier and the script errors instead of quietly resolving. It converts an advisory warning into a check you can actually rely on.
  • Why is one warning per name, rather than per use, awkward when auditing a long script?
    Because warning volume stops tracking legacy volume. A script that reads `responseBody` forty times emits one line, exactly like a script that reads it once. You can conclude that a name is used, never how much of the script depends on it, so counting warnings is not a migration metric.

It is a road sign that reads "this bridge is closed" while the bridge is still carrying traffic — the traffic proves nothing about the bridge's status.

saying these in an interview costs you the question

  • Says the old globals were removed and now throw
  • Treats a passing run as proof the spelling is current
  • Expects a warning on every use rather than the first
  • Calls the shim an app setting rather than sandbox behaviour
  • Claims pm and the legacy globals are two separate runtimes