Assertions: .expect() and .check()¶
.expect() and .check() validate response properties using scopes --- named checks against parts of the response like status code, timing, body, and headers.
The only difference between them: .expect() failures are hard, .check() failures are soft (recorded, execution continues). A hard fail skips the call's .store() and .wait() and every later call; the same call's .check() and .assert() still run and are recorded. See Failure Semantics.
Complete Evaluation¶
Both methods evaluate all their scopes before any failure takes effect. If .expect() has three scopes and two fail, both failures are recorded together --- you see every problem at once, not just the first.
// If both status and timing fail, you see both failures in the result
.expect(
status: 200,
totalDelayMs: 500
)
Scope Forms¶
Every scope accepts three forms.
Shorthand --- value only¶
The operator is inferred from the scope name (see Default Operators):
For status, you can pass an array --- any match passes:
Value + operator¶
Override the default operator:
Full form --- with options¶
Includes the options block, which is a placeholder for extensions:
.expect(
status: {
value: 200,
op: "eq",
options: {
// Extension fields go here (e.g. notification templates)
}
}
)
All three forms produce the same core behaviour. The full form exists so extensions can attach metadata to individual scopes.
Available Scopes¶
| Scope | Type | Description |
|---|---|---|
status |
integer or integer[] | HTTP status code. Array means "any of these". |
body |
body match | Body content or schema validation. See Body Matching. |
headers |
object | Each key-value pair must match. Keys are case-insensitive. |
bodySize |
size string | Body size threshold (e.g. "50kb", "10m"). Also gates body capture: a body over the limit is not saved and the call records bodyNotCapturedReason: "bodyTooLarge". |
totalDelayMs |
integer | Total response time threshold in ms. |
dns |
integer | DNS resolution time threshold in ms. |
connect |
integer | TCP connection time threshold in ms. |
tls |
integer | TLS handshake time threshold in ms. Skipped when this.tlsMs is 0 (plain HTTP). |
ttfb |
integer | Time to first byte threshold in ms. |
transfer |
integer | Body transfer time threshold in ms. |
size |
integer | Exact response body size in bytes. |
redirects |
string | URL match against the redirect hops (this.redirects). See Redirects Scope. |
Size string format for bodySize: a number optionally followed by a unit --- 50, 50k, 50kb, 10m, 10mb, 1g, 1gb.
Default Operators¶
When you omit op, each scope uses a natural default:
| Scope | Default op | Why |
|---|---|---|
status |
eq |
Status codes are exact matches |
body |
eq |
Exact or schema match |
headers |
eq |
Each header matched exactly |
size |
eq |
Exact byte count |
redirects |
eq |
URL equality |
totalDelayMs |
lt |
"Must be faster than this" |
dns |
lt |
Timing threshold |
connect |
lt |
Timing threshold |
tls |
lt |
Timing threshold |
ttfb |
lt |
Timing threshold |
transfer |
lt |
Timing threshold |
bodySize |
lt |
Size threshold |
The available operators are: lt, lte, eq, neq, gte, gt.
Body Matching¶
The body scope supports three kinds of matching:
Schema validation¶
Validates the response JSON against a JSON Schema stored in a script variable:
The variable must contain a valid JSON Schema document. If the variable is null, this is a hard fail.
Schema match modes control how extra fields are handled:
// Loose (default): schema fields must be present and valid, extra fields allowed
get("$BASE_URL/api/user")
.expect(body: schema($user_schema))
// The same, with the mode written out
get("$BASE_URL/api/user")
.expect(body: { value: schema($user_schema), mode: "loose" })
// Strict: no extra fields allowed at any level
get("$BASE_URL/api/user")
.expect(body: { value: schema($user_schema), mode: "strict" })
| Mode | Behaviour |
|---|---|
loose (default) |
All schema fields must be present and valid. Additional fields are ignored. |
strict |
All schema fields must be present and valid. Any extra field at any level fails. |
When a schema check fails, the assertion record's actual carries the first validation error path and message (e.g. { path: ".user.id", detail: "expected integer, got string" }). A strict-mode extra field is reported with path .<field> and detail "unexpected field".
Literal string¶
The raw response body must match the string exactly:
Variable match¶
The raw response body must equal the runtime value of the variable:
Note
The mode field only applies to schema(...) body matching. Literal string and variable matches are always byte-exact; mode on those is silently ignored.
Redirects Scope¶
The redirects scope asserts against the redirect hops followed during the call (this.redirects, see Redirect Tracking). It uses a match field to select which hop to compare:
// Shorthand --- defaults to match: "any"
get("$BASE_URL/account")
.expect(redirects: "/login")
// Full form --- explicit match type
get("$BASE_URL/account")
.expect(redirects: { value: "/login", match: "first" })
get("$BASE_URL/old-home")
.check(redirects: { value: "/final", match: "last" })
match |
Behaviour |
|---|---|
first |
Compare against the first redirect hop. Fails if no redirects occurred. |
last |
Compare against the last redirect hop. Fails if no redirects occurred. |
any (default) |
Pass if the value matches any redirect in the chain. |
eq is the only operator redirects accepts --- ordered comparisons are not meaningful for URLs.
Combining .expect() and .check()¶
A common pattern: use .expect() for must-not-break thresholds and .check() for performance goals: