Guide

The engine & CLI, end to end

Every engine capability, the eighteen-command CLI, and the plugin gallery — how each one works and the smallest override that turns it on. Everything here is opt-in and off by default.

The engine ships as collection data and runs where your requests run — two passes over one shared ctx, both driven by a single override block at the top of a request's Tests tab.

Two pipelines, one config. Auth, environment checks, random data and date variables run before the request goes out pre-request; assertions, snapshots, schema, security and plugins run after it comes back post-request. Both read the same merged config, so anything below can live in a per-request override or in the suite-wide hephaestus.defaults.

Assertions post-request

The assertions map is the workhorse: a field path maps to an object of operators, and every operator on that field must pass. Operators combine, so one entry can check existence, type and range at once. Two modifiers ride alongside them — soft logs a warning instead of failing, and when skips the check unless its expression is truthy. Setting softFail: true globally makes every functional failure soft.

eqstrict deep equality
nenot equal
gtgreater than
gtegreater than or equal
ltless than
lteless than or equal
typetypeof / array / null
minLenmin string / array length
maxLenmax string / array length
includesstring / array contains
matchesregex or substring
existsfield is present
absentfield must not be present
softwarn, do not fail
whenconditional skip expression
assertions: {
  "data.id":    { exists: true, type: "number" },
  "data.email": { matches: "@" },
  "data.score": { gte: 0, lte: 100 },
  "data.name":  { minLen: 1, maxLen: 100 },
  "data.role":  { includes: "admin" },
  "status":     { eq: "active",
                 when: "ctx.response.code === 200",
                 soft: true },
  "error":      { absent: true },
}

keysToFind, varsToSave, keysToCount

Three lighter helpers sit next to the map. keysToFind asserts that fields exist at the given paths — a bare string, or a { path, expect } object to also match a value (it honours soft and when too). varsToSave pulls a value out of the response and stores it in a Postman variable, keyed by alias, with scope of collection (default), environment or local. keysToCount asserts how many entries sit at a path — an array by default, or an object when you set type: "object".

keysToFind: [
  "data.id",
  { path: "data.token", expect: "abc" },
],

varsToSave: {
  token: { path: "data.token", scope: "collection" },
},

keysToCount: {
  items: { path: "data.items", expected: 3 },
}

Shape, order & headers post-request

Above the per-field map sit five declarative checks for structure and collections. Each one is a single object and emits its own Postman test.

assertShape · assertOrder · assertUnique

assertShape is a one-liner per field: a path maps to an expected type — "string", "number", "boolean", "object", "array", plus "absent" (must not exist) and "any" (must exist, any type). assertOrder confirms an array is sorted by a field in a direction, comparing as "number" or "string". assertUnique confirms elements are unique — by whole value, or by a nested field with by.

assertShape: {
  "data":       "object",
  "data.id":    "number",
  "data.items": "array",
  "error":      "absent",   // must not exist
  "meta":       "any",      // any type, present
},

assertOrder: {
  path: "data.items", by: "price",
  direction: "asc", type: "number",
},

assertUnique: { path: "data.items", by: "id" }

assertEach — every element against the same rules

Run the assertion vocabulary over each item of an array. Give it a path, optional minCount / maxCount for length, and a rules object using the same operators as the map above. Any rule can be marked soft to warn per item instead of failing.

assertEach: {
  path: "data.products",
  minCount: 1, maxCount: 200,
  rules: {
    id:     { type: "number", gt: 0 },
    name:   { type: "string", minLen: 1 },
    price:  { gte: 0 },
    active: { eq: true, soft: true },
  },
}

assertHeaders — one test per header

An array of entries, each keyed by name. Unless it is an absence check, every entry first asserts the header exists and is a non-empty string, then applies a matcher: expect as a substring or a predicate, equals for an exact value, or absent: true. equals wins when both are given; a throwing predicate counts as a failed check, not a broken run. The predicate form is a function, so it only works in a JS override, not in JSON defaults. Add label to name the test.

assertHeaders: [
  { name: "X-Request-Id" },
  { name: "Content-Type", expect: "json" },
  { name: "X-Api-Version", equals: "v2" },
  { name: "X-Powered-By", absent: true },
  { name: "X-RateLimit-Remaining",
    label: "rate limit left",
    expect: v => Number(v) > 0 },
]

Snapshots post-request

Snapshot regression pins a response against a saved baseline. On the first run the engine stores the body in collectionVariables["hephaestus.snapshots"], keyed by collection::request::status::format; later runs compare against it. It stays off unless enabled: true (or record: true). ignorePaths drops volatile fields before comparing; checkPaths narrows the comparison to just those sub-paths.

When an endpoint changes on purpose, set snapshotRecord: true (shorthand for snapshot.record), run once to overwrite the baseline, then drop the flag. Storage defaults to "collection-vars", the only backend that works offline; "postman-api" is unavailable offline, so the engine warns once and falls back automatically.

snapshot: {
  enabled: true,
  mode: "structural",   // shape only
  ignorePaths: ["data.updatedAt", "meta.ts"],
  checkPaths: ["data.status", "data.type"],
},

// force a fresh baseline for one run, then remove:
snapshotRecord: true

Schema, GraphQL & retries post-request

schema validates the parsed response against a full JSON Schema through a bundled tv4 (draft-04 / draft-07) — no network, no extra dependency. It is off unless enabled: true.

graphql checks the contract a 200 hides: GraphQL answers 200 even when the body carries an errors[] array, so status alone proves nothing. Passing true is shorthand for { noErrors: true }; you can also assert errorCount / errorContains for negative tests and shape the payload under data with dataShape.

retryOnStatus re-sends the request while its status is in the list, using setNextRequest — and it short-circuits the rest of the pipeline on intermediate attempts, so a retry never runs half a set of assertions. Turn on respectRetryAfter to obey the server's Retry-After header, waiting at most retryAfterCapMs so a hostile value can't stall the run.

schema: {
  enabled: true,
  schema: { type: "object", required: ["id"] },
},

graphql: { noErrors: true },   // 200 + errors[] fails

retryOnStatus: {
  statuses: [503, 429],
  maxRetries: 3,
  respectRetryAfter: true,   // obey Retry-After
  retryAfterCapMs: 5000,
}

Security & output post-request

securityAudit runs passive checks on the response and emits a separate test for each finding, so failures surface individually. It is opt-in — the default is { enabled: false, checkCors: true }. Enabled with no lists, it applies OWASP-style defaults: it requires HSTS, CSP, X-Frame-Options and X-Content-Type-Options; forbids server, x-powered-by and x-aspnet-version; and scans the body for stack traces and database errors. Version 4 added cookieFlags, checkJwt (rejects alg:none and expired tokens) and requireNoStore. Providing your own list replaces the matching built-in default.

Localization. locale selects the language of every user-facing string the engine emits — generated test names, logs, errors and status labels. "ru" is the default and is byte-identical to prior releases; "en" is the same engine in English. Assertion behaviour never changes, only the text. Any locale the catalog carries is now selectable, and npm run check:locales validates a contribution for completeness and parity.

Payload budget and config safety. maxBytes caps response size in bytes (values <= 0 disable it) — the size counterpart to maxResponseTime. strictMode turns the typo-guard strict: an unknown top-level override key normally warns with a "did you mean" suggestion, but under strictMode: true it fails the run so CI blocks on the typo. extraKeys is the escape hatch — list the names a third-party plugin reads off ctx.config and both the warning and the strict failure go away for exactly those keys.

securityAudit: {
  enabled: true,
  checkJwt: true,        // alg:none / expired
  cookieFlags: true,     // Secure, HttpOnly, SameSite
  requireNoStore: true,
},
maxBytes: 1048576,       // 1 MB response budget
locale: "en",            // "ru" default, or "en"
strictMode: true,        // unknown keys fail the run
extraKeys: ["budgetMs"],  // allowlist plugin keys

The CLI

Outside Postman lives a single zero-dependency binary. It never touches the engine — it reads what Newman wrote and turns each metric into a gate, exiting non-zero on the specific thing that regressed. Install it globally, then feed it a run report.

$ npm i -g hephaestus-postman-framework
summaryrun summary, p50/p95/p99 + --sla gate
comparediff two runs — regression gate
flakyassertions that flap across runs
coverageOpenAPI coverage, --min gate
benchengine overhead, --max-ms budget
reportself-contained HTML report
trendspass-rate / p95 sparklines
doctorpre-flight integrity & drift
mockreplay snapshots as a local API
watchre-run Newman on file changes
openapispec → collection (+ negatives)
docsAPI docs from a collection
junitNewman JSON → JUnit XML
migrateclassify migration state
sync-examplessnapshots → Postman examples
initconfig wizard; --demo offline demo
generatewizard → paste-able override
paneldev panel: trends, diff, editor

Plugins & the gallery post-request

Extensibility follows the engine-as-data model: a plugin is a single file you paste into a collection variable and register in the collection's Pre-request script — no build step, no engine fork. Plugins run once per request, second to last in the pipeline: after securityAudit, before the log summary. That position is what makes them useful — every assertion verdict is already in ctx._meta.results and ctx.config is still live. A throw inside a plugin becomes a failing test named after it, and the rest still run; if retryOnStatus fires, the pipeline stops before plugins and they sit out that iteration.

The gallery under gallery/plugins/ ships four working plugins plus a commented skeleton:

Gallery keys are read off ctx.config, so they live in your config where the engine has never heard of them — none are in KNOWN_KEYS. Each therefore warns by default and fails under strictMode, so declare them with extraKeys.

// 1. paste the file into a collection variable:
//    hephaestus.plugin.budget = <response-budget.js>

// 2. register in the collection Pre-request script:
pm.collectionVariables.set("hephaestus.plugins",
  JSON.stringify([
    { name: "response-budget",
      post: "hephaestus.plugin.budget" },
  ]));

// 3. its keys live in ctx.config — declare them
//    when strictMode is on:
const override = {
  extraKeys: ["budgetMs", "budgets"],
  budgetMs: 800,
};
Full key reference. Every option above, typed and described, lives on the config reference. Snapshots have their own viewer for inspecting baselines.