Tester — HTTP Contract Test Runner
Discovers and runs contract tests against any environment, with a web UI for visibility.
Test Definitions → Tester (Runner + UI) → Target API
Quick Start
CONTRACT_TEST_URL=http://localhost:8000 python -m tester run
python -m tester discover # list what was found
# In a built instance, the UI is mounted by the hub:
# http://localhost:12000/tools/tester
Where tests live
No test bodies are committed to core. The tool ships the base class, the runner and the UI. Tests belong to a room:
cfg/<room>/soleprint/station/tools/tester/tests/
They are merged into the built instance and discovered from tests/ there.
See tests/test_template.py — an intentionally empty
test file whose docstring covers the execution modes, environment targeting, the
ContractTestCase surface, and a worked example.
Keeping tests in the room rather than the runner means they version alongside the API they describe, and the runner stays reusable across projects.
Layout
tester/
├── base.py # ContractTestCase — httpx + stdlib unittest
├── core.py # discovery & execution
├── cli.py # python -m tester [discover|run]
├── config.py # .env + environment overrides
├── api.py # FastAPI routes
├── environments.json # named targets
├── templates/ # web UI
├── gherkin/ # optional feature/scenario metadata mapping
├── playwright/ # browser adapter (scaffolded; see the template)
└── tests/
├── base.py
├── test_template.py # start here
└── example/ # fallback health check, runs with no room config
Configuration
Single environment (.env)
CONTRACT_TEST_URL=https://api.example.com
CONTRACT_TEST_API_KEY=your-api-key-here
Multiple environments (environments.json)
Same suite, many targets — this is the point of the tool.
[
{
"id": "local",
"name": "Local",
"url": "http://localhost:8000",
"api_key": "",
"description": "Local development server",
"default": true
},
{
"id": "stage",
"name": "Staging",
"url": "https://stage.example.com",
"api_key": "stage-token-here",
"description": "Staging environment"
}
]
Selection is available in the UI header and persists via localStorage. Tokens are per-environment; keep real ones in a room's gitignored config, never here.
See the template for every CONTRACT_TEST_* variable.
API
GET /tools/tester/ # Runner UI
GET /tools/tester/filters # Filters UI
GET /tools/tester/api/tests # List all tests
GET /tools/tester/api/tests/tree # Tests grouped as a tree
GET /tools/tester/api/environments # List environments
POST /tools/tester/api/environment/select # Switch environment
POST /tools/tester/api/run # Start test run
GET /tools/tester/api/run/{run_id} # Run status (polling)
GET /tools/tester/api/runs # List all runs
GET /tools/tester/api/features # Gherkin features
POST /tools/tester/api/features/sync # Sync feature files
URL parameters
The runner accepts deep links:
/tools/tester/?run=abc123&modules=customers&search=invoice
run— auto-load results for this run IDdomains/modules— comma-separated filterssearch— search term for test namesstatus—passed,failed,skipped
Why this design
Tests scattered across repos give no visibility and are hard to demonstrate. Keeping definitions with the API while the runner stays generic means the suite evolves with the code, non-developers can run it from the UI, and the same tests prove the contract in every environment you can point them at.