Variables

Define test data upfront and extract runtime values to chain multi-step workflows.

Defining variables

Variables are key-value pairs defined at the definition level or test level. Values can be any JSON type — strings, numbers, booleans, arrays, or objects:

{
  "name": "my-suite",
  "variables": {
    "baseEmail": "test@example.com",
    "retryCount": 3,
    "enableAuth": true,
    "allowedRoles": ["admin", "editor"],
    "defaultUser": { "name": "Alice", "email": "alice@example.com" }
  },
  "tests": [
    {
      "name": "Test 1",
      "variables": {
        "testEmail": "specific@example.com"
      },
      "steps": [ ... ]
    }
  ]
}

Interpolation: {{variableName}}

Reference variables with {{variableName}} in item fields, actions, and assertions:

WhereResolvedExample
Item env valuesBuild time"value": "redis://:{{REDIS_PASSWORD}}@my-redis:6379"
Item imagesBuild time"image": "{{REGISTRY}}/my-service:latest"
Action URLsRuntime"url": "api-gateway/api/users/{{userId}}"
Action headersRuntime"Authorization": "Bearer {{token}}"
Action body (string values)Runtime"email": "{{email}}"
Database queriesRuntime"query": "SELECT * FROM users WHERE id = {{userId}}"
Assertion valuesRuntime"value": "{{email}}"
Match where valuesRuntime"value": "user-service/{{path}}"
Console log messagesRuntime"value": "User {{userId}} created"

Type preservation

When {{var}} is the entire value of a field, the typed value is preserved. When embedded in a larger string, the value is stringified:

Dotted path access

Variable references support dotted paths and array indexing: {{user.email}}, {{users[0].name}}, {{config.retries}}.

Extraction

Extract values from responses at runtime using JSONPath expressions:

{
  "name": "Create user",
  "action": {
    "type": "httpRequest",
    "method": "POST",
    "url": "api-gateway/api/users",
    "body": { "email": "{{testEmail}}" }
  },
  "extract": {
    "userId": "$.response.body.id",
    "authToken": "$.response.headers.x-auth-token",
    "firstName": "$.response.body.user.profile.name",
    "firstItem": "$.response.body.items[0].id"
  }
}

Extracted values become variables available in all subsequent steps.

Regex extraction

Extract rules can also be regex extract objects — resolve a JSONPath first, then apply a regex pattern to pull out a substring:

{
  "extract": {
    "userId": "$.response.body.id",
    "correlationId": {
      "path": "$.response.body.message",
      "pattern": "correlation_id=([a-f0-9-]+)",
      "group": 1
    }
  }
}
FieldTypeRequiredDescription
pathstringYesJSONPath to the source value
patternstringYesRegex pattern (with capture groups)
groupnumberNoCapture group index (default: 1; use 0 for full match)

The JSONPath is resolved first, the result is coerced to a string, then the regex is applied. An error is raised if the path doesn't exist, the pattern doesn't match, or the capture group is out of range.

When to use regex extraction. Use the simple string form when the value you need is already a discrete field. Use the regex form when the value is embedded inside a larger string — for example, extracting an ID from a log message or a token from a URL.

Extract paths

For HTTP responses:

For database query results:

Unified root context. Extract and assertion paths use the same unified root context. See Assertions for the full path reference.

Extraction in assertion blocks

You can also extract values from within assertion blocks — useful for capturing values from intercepted inter-service traffic:

{
  "match": {
    "path": "$.traffic",
    "where": [
      { "path": "$$.origin", "operator": "eq", "value": "api-gateway" },
      { "path": "$$.request.method", "operator": "eq", "value": "POST" },
      { "path": "$$.request.url", "operator": "contains", "value": "order-service/api/orders" }
    ],
    "count": 1
  },
  "extract": {
    "internalOrderId": "$.match.response.body.orderId"
  },
  "assertions": [
    { "path": "$.match.response.status", "operator": "eq", "value": 201 }
  ]
}

Precedence

In test steps (runtime), variables come from three sources in order of increasing precedence:

  1. Definition-level variables — shared across all tests
  2. Test-level variables — per-test overrides
  3. extract on steps — runtime extraction (overwrites all hardcoded values)

In item fields (build time), the resolver assembles one flat map seeded with config.yaml env entries, then definition-level variables are merged on top. All {{VAR}} in items resolve against this merged map. For keys not overridden by a definition variable, {{FOO}} and ${{FOO}} resolve to the same value. Unresolved references are errors.

Scoping rules

Build-time vs runtime variables

SyntaxResolvedScopePurpose
${{VAR}}Build timeEntire definitionConfig values from config.yaml env only
{{VAR}}Build timeItem fieldsMerged map of config.yaml env + definition-level variables
{{VAR}}RuntimeTest stepsVariables from variables / extract / loops

This means definition-level variables can share values across items — passwords, hostnames, image tags — without duplication:

{
  "name": "my-tests",
  "variables": {
    "REDIS_PASSWORD": "changeme"
  },
  "items": [
    {
      "type": "DATABASE",
      "name": "my-redis",
      "database": "redis",
      "dbPassword": "{{REDIS_PASSWORD}}"
    },
    {
      "type": "SERVICE",
      "name": "my-server",
      "port": 3000,
      "healthCheck": "/health",
      "env": [
        { "name": "REDIS_URL", "value": "redis://:{{REDIS_PASSWORD}}@my-redis:6379" }
      ]
    }
  ]
}

You can also combine ${{}} and {{}} — use ${{}} inside a variables value to compose variables from config:

{
  "variables": {
    "baseUrl": "${{API_HOST}}/v1"
  }
}