Init scaffolds
- health.tarn.yaml — safe first-run smoke test
- auth-flow.tarn.yaml — setup + bearer token reuse
- polling-job.tarn.yaml — poll until ready
- multipart-upload.tarn.yaml — file upload structure
- multi-user-session.tarn.yaml — named cookie jars
Local demo-server suites
- hello-world.tarn.yaml
- redirects.tarn.yaml
- cookies.tarn.yaml
- form-and-text.tarn.yaml
- error-responses.tarn.yaml
- auth-user-lifecycle.tarn.yaml
Public API and feature demos
- minimal.tarn.yaml
- auth.tarn.yaml
- assertions.tarn.yaml
- crud.tarn.yaml
- variables.tarn.yaml
- public-api/httpbin.tarn.yaml
- public-api/jsonplaceholder.tarn.yaml
Run them locally
$ PORT=3000 cargo run -p demo-server & $ cargo run -p tarn -- run examples/demo-server/ $ tarn fmt examples/demo-server/ --check $ tarn run examples/demo-server/ --select examples/demo-server/auth-user-lifecycle.tarn.yaml::login $ tarn run examples/demo-server/ --ndjson --format json=reports/run.json
Use --select FILE[::TEST[::STEP]] to run a single test or step, --ndjson to stream events line-by-line while also writing a full report, and tarn fmt --check in CI to fail when example files drift from canonical YAML.
Streaming and selective execution
These three patterns cover the common automation shapes: streaming events to an event-driven consumer, scoping a rerun to a single test, and gating CI on canonical formatting.
Pipe --ndjson into jq (or any line-oriented consumer) and pull just the event type out of each record:
$ tarn run --ndjson | jq -r '.event' # file_started step_finished step_finished test_finished file_finished done
Each line is a complete JSON object — file_started, step_finished, test_finished, file_finished, and a final done — so live dashboards, CI progress annotations, and LLM retry loops can all read the stream without parsing a full report.
Scope a rerun to a single named test with --select FILE::TEST (repeatable, ANDs with --tag):
$ tarn run --select tests/users.tarn.yaml::login --format json --json-mode compact # runs just the "login" test inside tests/users.tarn.yaml
Append a third segment (FILE::TEST::STEP_INDEX) to drill into a single step. The selector grammar is the same one tarn-lsp's code lens emits on its tarn.runTest and tarn.runStep commands, so editor runs and terminal runs target identical slices.
Fail CI when canonical formatting drifts:
$ tarn fmt --check tests/ # exits non-zero if any file would be reformatted
tarn fmt --check shares tarn::format::format_document with the LSP's textDocument/formatting handler, so a file that passes --check in CI is byte-identical to what "Format Document" produces in any LSP client.
Examples Gallery
Each example is a complete .tarn.yaml file you can copy and run.
Basic health check
name: Health check
description: Simple smoke test
env:
base_url: "http://localhost:3000"
steps:
- name: GET /health
request:
method: GET
url: "{{ env.base_url }}/health"
assert:
status: 200
body:
"$.status": "ok"
Login and authenticated request
name: Auth flow
tests:
- name: Login and access
steps:
- name: POST /login
request:
method: POST
url: "{{ env.base_url }}/login"
body:
username: "admin"
password: "secret"
capture:
token: "$.token"
assert:
status: 200
- name: GET /me
request:
method: GET
url: "{{ env.base_url }}/me"
auth:
bearer: "{{ capture.token }}"
assert:
status: 200
body:
"$.username": "admin"
Full CRUD
name: User CRUD
setup:
- name: Cleanup
request:
method: DELETE
url: "{{ env.base_url }}/users/test-user"
assert:
status: { in: [200, 204, 404] }
tests:
- name: User lifecycle
steps:
- name: Create user
request:
method: POST
url: "{{ env.base_url }}/users"
body:
name: "{{ $name }}"
email: "{{ $email }}"
capture:
user_id: "$.id"
assert:
status: 201
body:
"$.id": { type: string, not_empty: true }
- name: Get user
request:
method: GET
url: "{{ env.base_url }}/users/{{ capture.user_id }}"
assert:
status: 200
- name: Update user
request:
method: PATCH
url: "{{ env.base_url }}/users/{{ capture.user_id }}"
body:
name: "Updated Name"
assert:
status: 200
body:
"$.name": "Updated Name"
teardown:
- name: Delete user
request:
method: DELETE
url: "{{ env.base_url }}/users/{{ capture.user_id }}"
assert:
status: { in: [200, 204] }
Using faker data
name: Faker demo
env:
base_url: "{{ env.BASE_URL }}"
steps:
- name: Create user with faker
request:
method: POST
url: "{{ env.base_url }}/users"
body:
name: "{{ $name }}"
email: "{{ $email }}"
username: "{{ $username }}"
phone: "{{ $phone }}"
role: "{{ $choice(admin, user, viewer) }}"
tags: "{{ $words(3) }}"
active: "{{ $bool }}"
assert:
status: 201