DSL overview
Tales test files are written in HCL2 with a small, fixed vocabulary. This page covers the top-level structure and the execution model. Provider-specific blocks are documented under Providers.
File layout
Section titled “File layout”Every .tales file accepts the following top-level blocks, all optional except scenario (you need at least one to run anything):
version = 1
config { base_url = env("BASE_URL", "http://localhost:1337")}
generator "<type>" "<name>" { … } // zero or morekeyword "<name>" { … } // zero or morescenario "<name>" { … } // zero or moreversion = 1 is optional and reserved for future format bumps. config { ... } is a free-form map exposed under the config.* expression namespace, put env-driven URLs, secrets, and connection definitions there.
Source order matters for scenario blocks (they run in suite order) and for step blocks inside a scenario (they run sequentially). The parser preserves textual order even when step and the alias case are interleaved.
Scenario structure
Section titled “Scenario structure”scenario "<name>" { tags = ["smoke", "critical"] // optional; filtered by --tag
skip_if { … } // optional, repeatable skip_unless { … } // optional, repeatable
step "<provider>" "<name>" { depends_on = ["earlier_step"] // optional, doc + validation only
when = can(result.previous.id) // optional, gates the step at runtime vars { … } // optional, step-local variables request { … } // provider-specific expect { … } // provider-specific (alias: response) capture { … } // optional retry { … } // optional
skip_if { … } skip_unless { … } }
// ... more steps in execution order
teardown { step "<provider>" "<name>" { … } }}A case block is a backward-compatible alias for step, same decoding, same behaviour. A response block is a backward-compatible alias for expect, both names are accepted and response + expect may coexist on the same step.
Execution model
Section titled “Execution model”- Scenarios run in parallel with a configurable concurrency cap (
--parallel, defaults toruntime.NumCPU()). - Steps inside a scenario run sequentially in
.talesfile order. A failing step halts the scenario; later steps are reported asskipped. teardownalways runs after the main steps, even when a step failed. Usewhen = can(result.foo.id)on a teardown step to skip it when the prerequisite capture is missing.- A step may reference (
result.<step>) ordepends_ononly steps defined earlier in the file, forward / unknown references are rejected at load time with exit code2.
Steps and scoping
Section titled “Steps and scoping”Each step decodes its request, expect, capture, and retry blocks in source order. Variables and helpers visible to expressions are:
| Variable | Scope | Example |
|---|---|---|
config | whole file | config.base_url |
result | downstream steps only (after the step runs) | result.create_user.id |
request | inside the current step’s expect / capture | request.body.json.email |
response | inside the current step’s expect / capture | response.json.id |
input | inside a keyword’s steps | input.email |
vars | inside the current step only | vars.ts |
host | anywhere | host.os ("darwin", "linux", "windows") |
Full reference: Expression variables.
Tags and filtering
Section titled “Tags and filtering”scenario "Smoke ping" { tags = ["smoke"] // ...}tales test ./e2e/pass --tag smoke # only smoketales test ./e2e/pass --tag smoke --tag critical # smoke OR criticaltales test ./e2e/pass --scenario "Smoke ping" # exact name; overrides --tagConditional execution
Section titled “Conditional execution”skip_if and skip_unless blocks gate scenarios or steps on the host OS, architecture, env vars, or any boolean expression. See Conditional execution for the full attribute matrix and cascade semantics.
What’s next
Section titled “What’s next”- Step-local vars for once-per-step helper values.
- Captures & result chaining for moving data between steps.
- Retry & teardown for resilience patterns.
- Keywords for reusable flows with typed inputs and outputs.