Usage & Configuration
SpecTracer is configured entirely through a JSON file — no CLI flags. Drop a config at your project root and run.
CLI Usage
The tool follows an ESLint-style convention: all configuration lives in a JSON file, and the CLI just runs it.
# Auto-discover spectracer.config.json in current directory
uv run python build_pyramid.py
# Point at an explicit config file
uv run python build_pyramid.py path/to/other-config.json
That's the entire CLI surface — one command, one optional argument. No --help flags, no subcommands, no environment variables.
./config.json) or a bare filename. A leading .\ (PowerShell-style) can be mangled by POSIX-style shells (Git Bash, WSL) since backslash is their escape character.
Exit Codes
| Code | Meaning | When |
|---|---|---|
| 0 | Success | Report generated, no errors |
| 1 | Failure | Config error, parse error, or error_on_failure: true with test failures |
Configuration File
By default, SpecTracer looks for spectracer.config.json in the current directory. Here's a complete example:
{
"features": ["./features"],
"unit": {
"": ["./reports/unit.xml"],
"billing": ["./reports/billing-unit.xml"]
},
"integration": {
"": ["./reports/integration.xml"]
},
"e2e": ["./reports/e2e.json"],
"output": "./report.html",
"error_on_failure": false,
"health_checks": {
"progress_threshold_green": 80,
"progress_threshold_amber": 50,
"e2e_duration_amber_seconds": 600,
"e2e_duration_red_seconds": 1800
}
}
Tagging Convention
Feature files and test results connect through shared tags on scenarios. There are two kinds:
Linking Tags
Tags like @FC-42 or @regression appear on both the Gherkin scenario and the test result. Any tag match links the test to the scenario (OR logic).
Layer Requirement Tags
Tags like @require-unit, @require-integration, or @require-e2e declare which layers must have coverage. These are excluded from linking — they only affect the report's "Required Layers" status.
Feature: User Login
@FC-42 @regression @require-unit:auth @require-integration:auth @require-e2e
Scenario: Successful login with valid credentials
Given the user is on the login page
When they enter valid credentials
Then they should be redirected to the dashboard
@FC-43
Scenario: Login with invalid password shows error
Given the user is on the login page
When they enter an invalid password
Then an error message should be displayed
Matching Rules
| Rule | Detail |
|---|---|
| Exact string match | @FC-42 matches @FC-42 only, not @FC-4 or @FC-42-smoke |
| OR logic | A scenario tagged @FC-42 & @regression links to a test carrying just @regression |
| Scenario tags only | Tags on the Feature: line are not inherited by scenarios |
No linking for @require-* | Layer requirement tags are excluded from linking logic |
| Collisions | If two scenarios share a tag, one matching test links to both |
Where the Tool Looks for Tags
| Format | Tag Locations |
|---|---|
| JUnit XML | name attribute, classname attribute, or <properties><property> elements |
| Cucumber JSON | Native scenario-level tags array |
Full Configuration Reference
| Key | Required | Type | Description |
|---|---|---|---|
features |
Yes | string[] |
Gherkin .feature file/directory paths. Directories are searched recursively. |
unit |
No | {string: string[]} |
Module-keyed JUnit XML paths. Key "" for unscoped results. Matches @require-unit / @require-unit:<module>. |
integration |
No | {string: string[]} |
Same shape as unit. Matches @require-integration / @require-integration:<module>. |
e2e |
No | string[] |
Cucumber JSON file/directory paths. Not module-scoped. |
output |
Yes | string |
Output path for the HTML report. Parent directories are auto-created. |
error_on_failure |
No | boolean |
Exit non-zero when any test fails. Default: false. Health checks never affect exit code. |
health_checks |
No | object |
Threshold overrides for health indicators. |
Health Checks
Health checks are visual indicators on the report dashboard. They never affect the exit code — only error_on_failure does.
| Check | Condition | Pass | Warn | Fail |
|---|---|---|---|---|
| Progress | % of scenarios with at least one linked test | ≥ green threshold | ≥ amber threshold | < amber threshold |
| Pyramid Ratio | Unit count vs Integration + E2E count | Unit > I+E | — | Unit ≤ I+E |
| E2E Runtime | Total E2E duration in seconds | ≤ amber | ≤ red | > red |
| Unlinked Tests | Number of orphaned test results | 0 | 1–3 | 4+ |
Default thresholds:
progress_threshold_green: 80%progress_threshold_amber: 50%e2e_duration_amber_seconds: 600 (10 min)e2e_duration_red_seconds: 1800 (30 min)
Module-Scoped Requirements
@require-unit and @require-integration accept an optional :modulename suffix (e.g. @require-unit:auth). This pairs with module-keyed entries in the config:
{
"unit": {
"": ["./reports/unit.xml"], // unscoped — matches bare @require-unit
"auth": ["./reports/auth-unit.xml"] // module "auth" — matches @require-unit:auth
}
}
@require-unit:auth) is only satisfied by a result registered under that exact module key. An unscoped result (config key "") never satisfies it. A bare @require-unit (no module) is satisfied by any linked unit result regardless of module.
@require-e2e does not accept a module suffix — E2E scenarios typically span multiple modules.
Configuration Examples
Minimal setup (E2E only)
{
"features": ["./features"],
"e2e": ["./reports/e2e.json"],
"output": "./report.html"
}
Full stack (three layers)
{
"features": ["./features"],
"unit": { "": ["./reports/unit.xml"] },
"integration": { "": ["./reports/int.xml"] },
"e2e": ["./reports/e2e.json"],
"output": "./report.html",
"error_on_failure": true
}
Multi-module with health check thresholds
{
"features": ["./features", "./modules/billing/features"],
"unit": {
"": ["./reports/unit.xml"],
"billing": ["./modules/billing/reports/unit.xml"],
"auth": ["./modules/auth/reports/unit.xml"]
},
"integration": {
"": ["./reports/int.xml"],
"billing": ["./modules/billing/reports/int.xml"]
},
"e2e": ["./reports/e2e.json"],
"output": "./report.html",
"health_checks": {
"progress_threshold_green": 90,
"progress_threshold_amber": 70,
"e2e_duration_amber_seconds": 300,
"e2e_duration_red_seconds": 900
}
}