Tales

Integration testing, reinvented

End-to-end tests, written once, replayable forever, in a single Go binary.

Tales is the integration testing tool we wished existed. A modern alternative to Robot Framework, Karate, and Venom, without the Python toolchain to babysit, the JavaScript creep, or the YAML soup. One declarative HCL2 syntax, one seedable run, one tool for API, SQL, Browser, iOS, and HTTP load workflows. Android coming soon.

AI agent-ready: ships with a Claude Code skill that writes your tests
~/projects/api · bash
$ tales test ./e2e/pass --seed 1234 --parallel 4
tales: loaded 12 scenarios from 5 files; timeout=disabled
 PASS  e2e/pass/blog.tales / Create blog post (842ms)
 PASS  e2e/pass/keyword.tales / Use keyword (231ms)
 PASS  e2e/pass/sql.tales / PostgreSQL operations (95ms)
 PASS  e2e/pass/file_upload.tales / Multipart upload (47ms)
 PASS  e2e/pass/signed_webhook.tales / Signed webhook (38ms)

Summary: 12 passed · 0 failed · 0 skipped · 1.24s

AI agent-ready

Built for the way you code now: with an agent

Tales is designed to be driven by AI coding agents, not just typed by hand. A declarative HCL2 surface, seedable deterministic runs, and structured failure output give an agent exactly what it needs to write a test, run it, read the result, and fix it on its own.

  • A dedicated Claude Code skill

    Tales ships the `tales-test-generator` skill: it grounds the agent in the DSL source of truth, then generates valid, runnable `.tales` suites (scenarios, keywords, captures, teardown) instead of plausible-looking guesses.

  • Deterministic, so agents self-correct

    Seeded faker plus JSONL output mean the agent gets the same data and the same diagnostics every run. It can reproduce a failure, reason about it, and verify its own fix.

  • Declarative surface, small blast radius

    No glue code, no JavaScript escape hatch. The agent edits one contained HCL block, and what it writes is what runs: easier to generate, easier to review.

Install the skill
make install-skill

Copies it to ~/.claude/skills/tales-test-generator.

tales-test-generator
you › Write a Tales suite for the login + refresh-token flow.
generated .tales
scenario "Login then refresh" {
  step "http" "login" {
    request {
      method = "POST"
      url    = "${config.base_url}/auth/login"
      body { json = { email = config.user, password = config.pass } }
    }
    expect { status = 200 }
    capture { token = response.json.access_token }
  }

  step "http" "refresh" {
    request {
      method  = "POST"
      url     = "${config.base_url}/auth/refresh"
      headers = { Authorization = "Bearer ${result.login.token}" }
    }
    expect { status = 200 }
  }
}

Why Tales exists

Built after years of fighting the same problems with Robot Framework, Karate, and Venom.

No Python env to babysit

Robot Framework drags a Python toolchain that breaks on every OS update, every pip upgrade, every CI runner refresh. Tales is one static Go binary you drop into CI and forget about.

No DSL-meets-JavaScript creep

Karate scenarios tend to grow JavaScript blocks until they are a codebase. Tales is fully declarative HCL2 with built-in functions, generators, and matchers: what you write is what you read.

No YAML soup either

Venom and similar YAML-driven runners get hard to read once scenarios chain captures and conditionals. HCL2 keeps the same declarative spirit, but with typed values, comments, and expressions that scale to real workflows.

API, SQL, Browser, iOS, Load in one runner

Stop juggling separate tools for HTTP, database state, browser flows, mobile UI tests, and smoke load. Tales runs them in the same scenario file, with the same syntax, in the same report. Android support is on the roadmap.

What you get out of the box

A focused toolset for the test problems that actually slow teams down.

Single binary

Drop `tales` into your CI. No runtime, no plugins, no version manager. Static Go binary for Linux and macOS.

Declarative HCL2

Readable scenarios that diff cleanly. The DSL is the test: no callbacks, no glue code, no JavaScript escape hatch.

Deterministic faker

Generate emails, passwords, people, MAC addresses, bytes. Same seed → same data, every run, on every machine.

Seedable replay

Reproduce a flaky CI failure locally with one flag. `--seed 1234` and your laptop replays exactly what the runner saw.

Five providers, one binary

Drive your API, set up database state (Postgres, MySQL), drive Chrome via CDP with Web performance budgets, tap through an iOS simulator, replay HTTP at concurrent load. Android coming soon.

Parallel by default

Scenarios run concurrently with `--parallel`. Steps inside a scenario stay sequential, so chained captures remain deterministic.

Visual HTML report

A self-contained HTML report with timeline, action tiles, and screenshot replay. Open it. Share it. Debug in two clicks.

CI-native outputs

JUnit XML and JSONL out of the box. Exit codes your pipeline already understands. No glue scripts, no reporters to wire up.

See it in 30 seconds

Three tabs: a scenario, the command that runs it, the report your CI gets.

version = 1

generator "email" "user_email" {
  prefix = "qa-"
  domain = "example.com"
}

scenario "Create blog post" {
  step "http" "create_user" {
    request {
      method = "POST"
      url    = "https://api.example.com/users"
      body {
        json = {
          email    = generate("user_email")
          password = "Sup3rS3cret!"
        }
      }
    }

    expect {
      status = 201
      json = {
        id    = is_string()
        email = request.body.json.email
      }
    }

    capture {
      id    = response.json.id
      email = response.json.email
    }
  }

  step "http" "create_post" {
    request {
      method  = "POST"
      url     = "https://api.example.com/blog/posts"
      headers = { Author = result.create_user.id }
      body {
        json = {
          title = "Hello from Tales"
          body  = "Reproducible test data, every run."
        }
      }
    }

    expect {
      status = 201
    }
  }

  teardown {
    step "http" "delete_user" {
      when = can(result.create_user.id)
      request {
        method = "DELETE"
        url    = "https://api.example.com/users/${result.create_user.id}"
      }
      expect { status = one_of([200, 204, 404]) }
    }
  }
}

Built for real-world test problems

Pick a starting point. Every use case is one binary away.

API

HTTP workflows

Chain requests with captured IDs, assert JSON with matchers, sign webhooks with HMAC, upload multipart files. The HTTP provider is the heart of Tales.

step "http" "send_signed_webhook" {
  vars {
    ts   = now_unix()
    body = jsonencode({ id = "evt-1", type = "ping" })
    sig  = hmac_sha256_hex(config.webhook_secret, "${vars.ts}.${vars.body}")
  }

  request {
    method  = "POST"
    url     = "${config.base_url}/webhook"
    headers = { X-Signature = "t=${vars.ts},v1=${vars.sig}" }
    body { raw = vars.body }
  }
}

Same seed. Same data. Every run.

Tales generators are seeded: pass `--seed 1234` once and your CI gets the same emails, passwords, person names, and IDs as your laptop. No more "works on my machine". No more rerunning a CI job five times hoping the flake goes away.

  • A single `--seed` flag controls every faker call across every scenario.
  • Generator outputs are mixed with scenario, step, and generator names, so identical runs produce identical values even under `--parallel`.
  • Reproduce a red CI build by copying its seed into your local command line. The data lines up byte for byte.
# first run, today, on your laptop
$ tales test ./e2e/pass --seed 1234
[email protected] · Sup3rS3c! · Alice Martin
# second run, six months later, in CI
$ tales test ./e2e/pass --seed 1234
[email protected] · Sup3rS3c! · Alice Martin
Identical. Byte for byte.

Install Tales

Four ways in. Pick whichever fits your stack.

Homebrew (macOS / Linux)

The fastest path on a laptop. Linux and macOS, amd64 and arm64.

brew install --cask tales-testing/tap/tales

Pre-built binary

Grab the latest release tarball for Linux or macOS (amd64 / arm64) from GitHub Releases.

Open releases →

Build from source

You will need Go 1.26+. The Makefile handles the rest.

git clone https://github.com/tales-testing/tales
cd tales
make install

GitHub Action

Drop one step into your workflow to pin and install Tales on the runner. Used by the example CI recipes.

- uses: tales-testing/setup-tales-action@v1
  with:
    version: latest
View on GitHub →