Captures & result chaining
capture blocks copy values out of a step’s response (or request) into named slots under result.<step>.<key>. Later steps in the same scenario can read those slots in any expression: URL templates, request bodies, headers, assertions, even other captures.
Syntax
Section titled “Syntax”step "http" "create_user" { request { method = "POST" url = "${config.base_url}/users" body { json = { password = "Sup3rS3cret!" } } }
expect { status = 201 json = { id = is_string() email = request.body.json.email } }
capture { id = response.json.id email = response.json.email }}After this step:
| Expression | Value |
|---|---|
result.create_user.id | the JSON id field |
result.create_user.email | the JSON email field |
Reading captures downstream
Section titled “Reading captures downstream”step "http" "get_user" { request { method = "GET" url = "${config.base_url}/users/${result.create_user.id}" }
expect { status = 200 json = { email = result.create_user.email } }}You can mix capture references with literal values, env vars, generators, etc., anywhere an expression is valid.
What you can capture
Section titled “What you can capture”capture blocks accept any HCL expression. Common patterns:
capture { # Direct JSON fields id = response.json.id email = response.json.email
# Nested paths first_tag = response.json.tags[0]
# Header values request_id = response.headers["X-Request-Id"]
# Status code (rarely useful but possible) status = response.status_code
# Echoes of the request, useful for tests that round-trip sent_email = request.body.json.email
# Computed values greeting = "Hello, ${response.json.first_name}!"}For SQL steps, response.json.rows[i].<column> works the same way, see SQL provider for the row shape.
For mobile steps, the runtime injects two extra helpers, value("id") and text("id"), which read the current accessibility tree:
capture { current_text = text("login.email") current_value = value("login.email")}Forward references are rejected
Section titled “Forward references are rejected”A step may only read captures from steps defined earlier in the file. The parser checks this at load time and exits with code 2 on a forward or unknown reference.
step "http" "first" { // ... expect { json = { id = result.second.id // ERROR: forward reference to a later step } }}
step "http" "second" { // ... capture { id = response.json.id }}The same applies to depends_on. Source order is the source of truth.
Captures across scenarios
Section titled “Captures across scenarios”Captures never cross scenario boundaries. Each scenario starts with an empty result namespace. To share state between scenarios, persist it externally (database row, file, env var injected at the suite level), or, more often, refactor the dependency into a keyword.
Captures and skip cascade
Section titled “Captures and skip cascade”If a step is skipped (via skip_if, skip_unless, or because a dependency failed/skipped), its capture block does not run. Any downstream step referencing result.<skipped_step>.<key> is automatically skipped with a depends on skipped step "..." reason, see Conditional execution.
This means you can write naïve downstream steps without defensive can(...) checks, the cascade handles it for you.