# Choreo DSL Reference
**choreo** is an executable Domain-Specific Language (DSL) for writing "model clients" for your command-line
applications and APIs. It uses a structured, human-readable format to define test scenarios that are easy to write,
read, and maintain.
This document serves as the official reference guide for the `.chor` file format and its syntax.
## File Structure: The BDD Hierarchy
A `.chor` file is structured hierarchically to tell a clear story, following the standard BDD pattern of
`Feature -> Scenario -> Test`.
```
feature "A high-level description of the capability being tested"
# ... (settings, vars, actors)
scenario "A concrete example of the feature's behaviour" {
test TestNameOne "The first step in the scenario" {
given:
# Pre-conditions and setup actions
when:
# The single action being tested
then:
# The expected outcomes and assertions
}
test TestNameTwo "The second step, depending on the first" {
given:
Test has_succeeded TestNameOne
# ... and so on
}
after {
# Cleanup actions that run after all tests in this scenario
}
}
```
## Keywords
`choreo` uses a set of keywords to define the structure and logic of a test suite.
#### `feature`
Provides a high-level description of the software capability being tested and groups related scenarios. A `.chor` file
should contain exactly one `feature`.
**Example:**
```
feature "User account management via the CLI"
```
#### `settings`
A block for configuring the behavior of the choreo test runner for the current file.
| `timeout_seconds` | Number | 30 | The maximum time in seconds a scenario can run before failing. |
| `stop_on_failure` | Boolean | false | If true, the entire test suite will stop immediately after the first test fails. |
| `shell_path` | String | "zsh" | The absolute path to the shell to use for the Terminal actor. |
| `report_path` | String | "reports/" | The directory where the report file will be saved. |
| `expected_failures` | Number | 0 | Declares the number of tests that are expected to fail for the suite to be considered a success. |
**Example:**
```
settings {
timeout_seconds = 10
stop_on_failure = true
expected_failures = 1
}
```
#### `background`
A block that provides a common set of readable `given` steps that apply to all scenarios in a feature. Background steps
run **once** before all scenarios (not before each scenario), and any side-effects they produce — such as environment
variable assignments or HTTP headers — are inherited by every subsequent scenario. Think of it as suite-level setup.
> **Note:** `Terminal set_cwd` in a background block will **not** persist to scenarios. Each scenario creates its own
> isolated terminal backend (with its own working directory) for test isolation. If you need a shared working directory,
> use a `var` and reference it with `Terminal set_cwd "${WORKDIR}"` at the start of each scenario instead.
**Example:**
```
background {
FileSystem create_dir "temp_data"
FileSystem create_file "temp_data/initial.txt" with_content "setup data"
}
```
#### `var`
A keyword for defining key-value variables that can be used throughout the test file. This is useful for making tests
more readable and maintainable by avoiding "magic strings."
**Example:**
```
var FILENAME = "my_output.txt"
var GREETING = "Hello, Choreo!"
```
#### `env`
Declares a list of environment variables that the test suite requires. The test runner will read these from the shell
environment where `choreo` is executed and make them available for substitution.
**Example:**
```
env: API_TOKEN, GITHUB_USER
```
#### `actors`
Declares the different systems or components that the test will interact with. The two currently supported actors are
`Terminal` and `FileSystem`.
**Example:**
```
actors: Terminal, FileSystem
```
#### `scenario`
Describes a single, concrete example of the feature's behaviour. It acts as a container for a sequence of related `test`
blocks that form a user story or workflow.
**Example:**
```
scenario "A user can successfully create and then delete a file" {
# ... test blocks go here ...
}
```
#### `test`
The core unit of testing in `choreo`. Each `test` block has a unique name (for dependencies) and a human-readable
description. It is composed of `given`, `when`, and `then` blocks.
**Example:**
```
test FileIsCreated "it creates a new file with content" {
given: # ...
when: # ...
then: # ...
}
```
#### `after`
An optional block inside a `scenario` that contains a list of cleanup actions and/or task calls. These are executed
after
all `test` blocks within that scenario have completed, regardless of whether they passed or failed.
**Example:**
```
scenario "..." {
# ... tests ...
after {
FileSystem delete_file "${FILENAME}"
cleanup_repo("${REPO_DIR}")
}
}
```
## Test Blocks: Given, When, Then
Each `test` block is structured using the standard BDD keywords to create a clear narrative.
#### `given`:
The `given` block sets up the context for a test. It can contain a mix of **actions** (to set up the environment) and
**conditions** (to check pre-requisites, including dependencies on other tests).
**Example:**
```
given:
# Action: Ensure a clean state
FileSystem delete_file "data.txt"
# Condition: This test can only run after the setup test has passed
Test has_succeeded InitialSetup
```
#### `when`:
The `when` block contains the single, specific action that is being tested. A `when` block should contain only actions,
not conditions.
**Example:**
```
when:
Terminal run "data-processor --input data.txt"
```
#### `then`:
The `then` block contains the assertions that verify the outcome of the `when` action. A `then` block should contain
only conditions. The test passes if all `then` conditions are met.
**Example:**
```
then:
Terminal last_command succeeded
FileSystem file_exists "output.txt"
```
## Vocabulary: Actions & Conditions
This is the reference for all available commands that can be used within the `test` blocks.
### Wait Conditions
| `wait \>= 1.5s` | Passes if the test has been running for at least 1.5 seconds. |
| `wait \<= 100ms` | Passes if the test has been running for no more than 100 milliseconds. |
### State Conditions
| `Test has_succeeded <TestName>` | Passes if the test with the given name has already passed. This is the primary mechanism for creating dependencies. |
### Terminal Commands
#### Actions
| `Terminal run "..."` | Executes a shell command non-interactively. The command and a newline are sent at once. |
| `Terminal set_cwd "..."` | Sets the working directory for all subsequent `Terminal run` commands in this scenario. |
| `Terminal types "..."` | Simulates a user typing a string into the terminal. |
| `Terminal presses "Enter"` | Simulates a user pressing the Enter key. |
#### Conditions
| `Terminal last_command succeeded` | Passes if the last `Terminal run` command exited with code 0. |
| `Terminal last_command failed` | Passes if the last `Terminal run` command exited with a non-zero code. |
| `Terminal last_command exit_code_is <num>` | Passes if the last `Terminal run` command exited with the specified code. |
| `Terminal output_contains "..."` | Passes if the combined stdout/stderr stream from the PTY contains the substring. |
| `Terminal output_not_contains "..."` | Passes if the combined stdout/stderr does NOT contain the specified substring. |
| `Terminal stdout_is_empty` | Passes if the stdout from the last `Terminal run` command was empty. |
| `Terminal stderr_is_empty` | Passes if the stderr from the last `Terminal run` command was empty. |
| `Terminal stderr_contains "..."` | Passes if the stderr from the last `Terminal run` command contains the substring. |
| `Terminal output_starts_with "..."` | Passes if the trimmed stdout of the last `runs` command starts with the string. |
| `Terminal output_ends_with "..."` | Passes if the trimmed stdout of the last `runs` command ends with the string. |
| `Terminal output_equals "..."` | Passes if the trimmed stdout of the last `runs` command is an exact match. |
| `Terminal output_matches "..."` | Passes if the combined stdout/stderr stream from the PTY matches the regex. |
| `Terminal output_is_valid_json` | Passes if the combined stdout/stderr stream from the PTY is valid JSON. |
| `Terminal json_output has_path "..."` | Passes if the JSON output has the specified JSON path. |
### FileSystem Commands
#### Actions
| `FileSystem create_dir "..."` | Creates a directory, including any necessary parent directories. |
| `FileSystem create_file "..."` | Creates an empty file. |
| `FileSystem create_file "..." with_content "..."` | Creates a file and writes the specified content to it. |
| `FileSystem delete_dir "..."` | Deletes a directory and all its contents. |
| `FileSystem delete_file "..."` | Deletes a file. |
#### Conditions
| `FileSystem dir_exists "..."` | Passes if a directory exists at the specified path. |
| `FileSystem dir_does_not_exist "..."` | Passes if no directory exists at the specified path. |
| `FileSystem file_exists "..."` | Passes if a file exists at the specified path. |
| `FileSystem file_does_not_exist "..."` | Passes if nothing exists at the specified path. |
| `FileSystem file_contains "..." "..."` | Passes if the file at the first path contains the second string. |
| `FileSystem file "..." is_empty` | Passes if the file at the specified path is empty. |
| `FileSystem file "..." is_not_empty` | Passes if the file at the specified path is not empty. |
### Web Commands
#### Actions
| `Web http_get "..."` | Sends a GET request to the specified URL. |
| `Web set_header "<key>" "<value>"` | Sets a custom HTTP header (e.g., `Authorization`) for subsequent requests. |
| `Web clear_header "..."` | Clears a custom header previously set with `Web set_header`. |
| `Web set_cookie "<key>" "<value>"` | Sets a cookie for subsequent requests. |
| `Web clear_cookie "..."` | Clears a cookie previously set with `Web set_cookie`. |
| `Web http_get "<url>"` | Sends a GET request to the specified URL. |
| `Web http_post "<url>" with_body "..."` | Sends a POST request with the given body. |
| `Web http_put "<url>" with_body "..."` | Sends a PUT request with the given body. |
| `Web http_patch "<url>" with_body "..."` | Sends a PATCH request with the given body. |
| `Web http_delete "<url>"` | Sends a DELETE request to the specified URL. |
#### Conditions
| `Web response_status_is <num>` | Passes if the last HTTP response had the specified status code. |
| `Web response_status is_success` | Passes if the last HTTP response status code is in the 200-299 range. |
| `Web response_status is_error` | Passes if the last HTTP response status code is in the 400-599 range. |
| `Web response_status is_in [num, num, num]` | Passes if the last HTTP response status code is in the specified list. |
| `Web response_time is_below 1s/200ms` | Passes if the last HTTP response was received in under the specified time. |
| `Web response_body_contains "..."` | Passes if the last HTTP response body contains the specified substring. |
| `Web response_body_matches "..." [as JSON]` | Passes if the last HTTP response body matches the specified regex. |
| `Web json_body has_path "..."` | Passes if the last HTTP response body (as JSON) has the specified JSON path. |
| `Web json_path at "..." equals <value>` | Passes if the value at the specified JSON path equals the given value. |
| `Web json_response at "..." is_a_string` | Passes if the value at the specified JSON path is a string. |
| `Web json_response at "..." is_a_number` | Passes if the value at the specified JSON path is a number. |
| `Web json_response at "..." is_an_array` | Passes if the value at the specified JSON path is an array. |
| `Web json_response at "..." is_an_object` | Passes if the value at the specified JSON path is an object. |
| `Web json_response at "..." has_size <number>` | Passes if the value at the specified JSON path (array or string) has the given size. |
## Variables
`choreo` supports both environment variables and file-defined variables for making tests dynamic.
### Substitution
To use a variable, use the `${VAR_NAME}` syntax inside any string literal. The test runner will replace this placeholder
with the variable's value before executing the step.
**Example:**
```
var FILENAME = "output.log"
when:
Terminal run "echo 'hello' > ${FILENAME}"
```