Validation diagnostics¶
Every finding from the scenario validator carries a stable diagnostic code
(HTTPCHAINxxx), a severity, a human-readable message and (where meaningful) a
location, so tooling can filter, sort and route diagnostics deterministically.
You meet these codes in three places:
pytest-httpchain validateoutput (and its--format jsonpayload);- pytest collection: error-severity findings fail collection, warnings are
emitted as
ScenarioValidationWarningin the form[HTTPCHAINxxx] ...; - CI gates:
validate --strictexits non-zero on warnings too.
Codes are append-only: a code's meaning never changes, and retired checks do not free their numbers for reuse.
Code reference¶
| Code | Severity | Meaning |
|---|---|---|
HTTPCHAIN000 |
error | Schema validation failed (Pydantic Scenario model) |
HTTPCHAIN001 |
error | Duplicate stage names |
HTTPCHAIN002 |
error | Fixture and variable share the same name |
HTTPCHAIN003 |
warning | Variable referenced but never defined/saved/fixture (typo), or saved only by earlier stages with a skip_if or a skip, skipif or xfail mark of their own, which leave it undefined when they skip (read it with get()) |
HTTPCHAIN004 |
warning | Variable referenced before it is saved or defined — saved by a later stage or by a later step of the same stage's response, or defined by a later substitution step (ordering / data-flow) |
HTTPCHAIN005 |
warning | Stage has no verify step (no response validation) |
HTTPCHAIN006 |
warning | Verify step asserts nothing (no-op) |
HTTPCHAIN007 |
error | Body or header matcher contains/not_contains list the same substring, or a verify.jmespath matcher's contains and not_contains are the same JSON value |
HTTPCHAIN008 |
error | Body, header or verify.jmespath matcher matches/not_matches list the same pattern |
HTTPCHAIN009 |
warning | Saved variable is shadowed by a scenario-level fixture |
HTTPCHAIN010 |
error | File not found |
HTTPCHAIN011 |
error | Path is not a file |
HTTPCHAIN012 |
error | $ref resolution failed |
HTTPCHAIN013 |
warning | File extension is neither .json nor .jsonc |
HTTPCHAIN014 |
error | Invalid JSON in the scenario or a file it includes, which the message then names: a syntax error (a /* comment never closed is one, reported at its opening; see Comments and trailing commas), a duplicate object key, bytes that are not UTF-8, or an integer too long to parse |
HTTPCHAIN015 |
error | Failed to parse JSON file (for example, nested too deeply to parse) |
HTTPCHAIN016 |
error | Fixture referenced in a scenario-level template |
HTTPCHAIN017 |
error | Scenario-level template references an undefined name |
HTTPCHAIN018 |
warning | Verify expression is not a template ({{ }}) — cannot evaluate to the required bool |
HTTPCHAIN019 |
error | Invalid pytest marker expression (scenario or stage marks) |
HTTPCHAIN020 |
warning | Referenced file does not exist (deep, opt-in): a file path (ssl, a binary body, a file a files or multipart body uploads), a body schema file, or a local file a body schema's $ref or $dynamicRef names |
HTTPCHAIN021 |
warning | A body schema cannot be used (deep): its file is not JSON, its JSON pointer leads nowhere, an $id on the pointer's way or in the schema cannot be read (not a string, or not a URI), the schema it selects is not valid, or a $ref or $dynamicRef it reaches does not resolve (not a string, remote, an absolute path, more ../ than httpchain_ref_parent_traversal_depth, outside the root, a pointer to nothing, a malformed $id) or points to an invalid schema |
HTTPCHAIN022 |
warning | User function cannot be imported (deep) |
HTTPCHAIN023 |
warning | Unexpected argument passed to a user function (deep) |
HTTPCHAIN024 |
warning | Missing required argument for a user function (deep) |
HTTPCHAIN025 |
info | Template parametrize values force collection-time resolution |
HTTPCHAIN026 |
warning | $ref path matches files under both lookup bases (ambiguous) |
HTTPCHAIN027 |
warning | User-defined name shadowed by the reserved response namespace |
HTTPCHAIN028 |
warning | Scenario directive ($include/$merge) inside an inline JSON Schema — not resolved there; a JSON Schema $ref is, to a local file too |
HTTPCHAIN029 |
warning | Template expression in a dict key — only values are substituted, so the key is sent literally (a verify.jmespath key is evaluated as written; one that is not valid JMESPath fails validation instead). Also raised for an escaped \{{ in a key, which does nothing there: the key keeps its backslash, and its braces need no escape |
HTTPCHAIN030 |
warning | Template expression in a functions substitution's kwargs — kwargs are passed to the function unrendered, so it arrives as literal text. Also raised for an escaped \{{ there, which arrives with its backslash |
HTTPCHAIN031 |
error | xdist_group marker in a stage's marks, naming a group the scenario does not declare — under --dist loadgroup it runs that stage apart from the rest of the scenario; put it in the scenario's marks (see pytest-xdist) |
HTTPCHAIN032 |
error | Stage name contains ::, pytest's node-id separator — the stage cannot be run by its node id, and --dist loadscope runs it apart from the rest of the scenario |
HTTPCHAIN033 |
error | Scenario's xdist_group name has a ] after its last @ — pytest-xdist ignores such a group, so --dist loadgroup does not keep the scenario's stages together |
HTTPCHAIN034 |
error | Stage request.url is relative (/users/1, or /users/{{ id }}), but the scenario's client sets no base_url to resolve it against (see Client configuration) |
HTTPCHAIN035 |
warning | A built-in function that is no use as a value (a time, encoding, URL or hashing helper, uuid4, env, rand or randint) used without calling it ({{ now }} for {{ now() }}, {{ env }}, or str(timestamp)) — the template gets the function itself, not its value, and one that renders to a function fails the stage (a parametrize value, collection; a scenario-level template, scenario initialization, or collection for the substitutions of a scenario whose parametrize values are templates, which resolves them there) |
HTTPCHAIN036 |
warning | A built-in's name the scenario defines too, used as a function where that definition is not in scope (in another stage, in a scenario-level template, in a step before the one defining it): called (timestamp()) where the scenario's is a fixture or function substitution, or handed to a function that takes one: as a key= (sorted(rows, key=len)), or to your own function or a method of your fixture's object (sign(timestamp), helper.ids(uuid4)) where the scenario's is a fixture or function substitution — the built-in runs in its place, silently. A save or variable handed to your function out of scope is a missing name (HTTPCHAIN003/004, 016/017): the function gets the built-in function in its value's place |
HTTPCHAIN037 |
warning | A template in a stage (not a parametrize value: see HTTPCHAIN038) that is no single expression the engine evaluates, as its text alone shows: a syntax error ({{ 1 + }}, or a dict literal whose } runs into the template's closing }}, as in {{ {'a': 1}}}), more than one statement ({{ a; b }}), an assignment ({{ user.active = True }} where == was meant, +=, :=) or another statement, a kind of expression the engine does not evaluate (a lambda, a set comprehension, * unpacking outside a list literal, yield, await), an attribute it does not read (doc._id, for which write doc['_id'], or '{}'.format(x)), or a call of anything but a name or an attribute (fns[0]()), wherever in the template it sits, a branch never taken included — the template fails the stage. This is its only finding: the names in it are not checked. A refusal that depends on a value (a module, a function the engine forbids) comes when the stage runs |
HTTPCHAIN038 |
error | The same in a template resolved before any stage runs, so that nothing runs: a scenario-level one (substitutions, auth, ssl, client), which fails scenario initialization and every stage with it (the scenario's collection, for substitutions a templated parametrize value resolves there), or a stage's parametrize value, which fails the scenario's collection |
HTTPCHAIN039 |
error | A directory given to validate holds no scenario file: none named test_<name>.<suffix>.json or .jsonc for the suffix searched by, outside the directories pytest skips — so a mistyped path, or a suffix that names no file, cannot pass the gate as an empty run |
HTTPCHAIN040 |
warning | A parallel stage's stats_as names a variable its own response saves too — the stats are saved after the response steps' saves and replace that one, which no later stage can read. When the stage runs, a name only a user_functions save returns, which validate cannot see, raises the same warning |
Deep (opt-in) checks¶
HTTPCHAIN020–HTTPCHAIN024 come from deep validation
(validate --deep), which imports your user modules and touches the
filesystem — so it is opt-in and never runs at pytest collection time. Deep
findings are always warnings; pair --deep with --strict to fail CI on
them.
Filtering collection warnings¶
At collection time, warning-severity findings are emitted as
pytest_httpchain.ScenarioValidationWarning (error-severity findings fail
collection outright, and info findings — HTTPCHAIN025 — never affect
validity, are exempt from --strict, and are not warned about at collection).
Standard warning filters apply — e.g. to silence one code project-wide: