API workflows
Chain HTTP requests with captured IDs, assert JSON with rich matchers, sign webhooks with HMAC, upload multipart files. The HTTP provider is the heart of Tales.
Tales is a declarative integration and end-to-end testing tool packaged as a single Go binary. You write tests as .tales files using the readable HCL2 syntax, and a single tales test command runs them with deterministic, seedable data generation.
Tales is an open-source alternative to Robot Framework, Karate, and Venom, with one runner for HTTP, SQL and mobile, no Python toolchain, no JavaScript creep, and no YAML soup.
You’ll feel at home in Tales if you have ever:
Tales is designed to remove all those daily frictions.
API workflows
Chain HTTP requests with captured IDs, assert JSON with rich matchers, sign webhooks with HMAC, upload multipart files. The HTTP provider is the heart of Tales.
Database hooks
Run plain SQL statements (Postgres or MySQL) inside a scenario to flip an internal flag, seed a row, or read state your public API does not expose. Not a fixture loader, not a migration tool, just a thin escape hatch alongside HTTP.
iOS smoke tests
Drive a real iOS simulator with an embedded XCUITest driver. Zero Swift code to write. The visual HTML report shows every tap, swipe, and screenshot.
Reusable flows
Extract login, signup, or any common sequence into a keyword with typed inputs and named outputs. Call it from any scenario.
generator "email" "user_email" { prefix = "qa-" domain = "example.com"}
scenario "Create user" { step "http" "register" { request { method = "POST" url = "${config.base_url}/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 } }}Three things to notice:
generate("user_email") produces a stable value for the same seed.expect, capture, and assertions are all expressions.result.register.id to chain workflows without writing imperative code.Tales generators are seeded. Two runs with the same --seed produce byte-identical generated values. This is what lets you reproduce a flaky CI run on your laptop with one flag:
tales test ./e2e/pass --seed 1234The seed is mixed with the scenario name, the step name, the generator name, and the expression path, so identical runs produce identical values even under --parallel. See the Deterministic test data guide.