A modular test runner that executes API specification documents written in Markdown format. Designed for high cohesion and low coupling.
Cotton follows a clean, modular architecture with clear CLI/engine separation:
{{ name }}) — missing variables fail fastgo build
Run specs by file, directory, or glob. Key flags shown below.
# Run a single spec with 30s timeout (default)
./cotton examples/example.spec.md --timeout 30000
# Run a directory and fail fast on first error
./cotton examples/ -f
# Run multiple globs quietly (exit code only)
./cotton "examples/*.spec.md" -q
--timeout <ms>: Valid range 1..60000; defaults to 30000; values are capped at 60000.-f, --fail-fast: Stop on first failing spec.-q, --quiet: Suppress output (exit code still reflects success/failure).-d (debug), -m (monochrome), -v (version), --json-report/--html-report (optional outputs; HTML reserved).# Test Case Title
Variable declarations and setup links
\`\`\`
HTTP Request
\`\`\`
Expectations and teardown links
Code fence: Use either triple backticks or triple tildes. Language annotations (like http, json) are supported and ignored by the parser:
http ...Define variables before the request code fence:
`product_id` = `3``token` = `$.data.access_token`Variables can be referenced in requests using {{ variable_name }}.
If a placeholder has no value at substitution time, Cotton raises a ParseError and aborts the spec.
List expectations after the request code fence:
`$.data.name` == `"John"``$.data.count` > `0``$.data.records` != undefined`$.data.title` =~ `/^Executive/`Supported operators:
- Numeric: <, >, <=, >=, ==, !=
- String: ==, !=
- Regex: =~ (match), !~ (not match)
Value formats:
- Strings: "text" (quoted)
- Numbers: 42, 3.14
- Regex: /pattern/ (forward-slash delimited; e.g., /^[A-Z]/, /[0-9]{3}/)
- Special: null, undefined
All JSON paths must start with $ and follow GJSON syntax:
$.field - access field$0.field - array index (first element's field)$.nested.field - nested access$.records.#.name - all names in array# Get Product
- `base_url` = `"https://api.example.com"`
- `sku` = `$.data.sku`
- [Login](./login.md)
```http
GET {{ base_url }}/products/42 HTTP/1.1
Accept: application/json
$.status == "ok"$.data.id == 42$.data.name != undefined$.data.title =~ /^Product/$.data.sku == {{ sku }}
## Testing
Run all tests:
```bash
go test -v ./...
Tests are deterministic and use local httptest servers; no external network access is required.
fmt.Errorf with context; ParseError vs RunError labeling$ claude mcp add cotton \
-- python -m otcore.mcp_server <graph>