Pytest for HTTP APIs: a local CLI that runs Git-diffable .http request files
as an enforceable test suite. No accounts, cloud sync, GUI, or team workspace service.
probeflow reads IntelliJ / VS Code REST Client request files, executes them, checks explicit response assertions, and fails CI when the contract breaks.
pip install probeflowAfter installation the probeflow command is available in your PATH.
To work on probeflow itself, install from a clone in editable mode with the dev dependencies (test suite, linter):
git clone https://github.com/Kunal241207/probeflow.git
cd probeflow
pip install -e ".[dev]"Create a .http file:
### @name = getUsers
GET https://api.example.com/users
Accept: application/json
###
### @name = createUser
POST https://api.example.com/users
Content-Type: application/json
{
"name": "Alice",
"email": "alice@example.com"
}Run it:
probeflow run requests.http
probeflow test requests.http # Execute as an enforceable test suiteExecute requests from a .http file interactively.
probeflow run requests.http # Run all requests
probeflow run requests.http --index 0 # Run one request by 0-based index
probeflow run requests.http --env dev # Use .env.dev for variable substitution
probeflow run requests.http --headers # Show response headers
probeflow run requests.http --timeout 60 # Set timeout in seconds (default: 30)
probeflow run requests.http --quiet # Suppress output, show errors only
probeflow run requests.http --check-assertions # Evaluate assertions after each requestResponse chaining works in run too — a named request's response is available
as {{name.response.body.$.field}} in subsequent requests within the same file.
Use --check-assertions to evaluate assertions during development (exit code 1
on failure). For CI-enforced assertions, use test.
Execute requests in declaration order and evaluate every ### @assert block.
Exits 0 only when every request completes and every assertion passes.
probeflow test tests/api.http
probeflow test tests/api.http --env staging
probeflow test tests/api.http --json results.json --junit-xml results.xmlCheck a .http file for syntax errors without executing any requests.
Request names (### @name = ...) must be unique within a file. Duplicate names
produce an error. In test mode, unresolved variables (variables not found in
.env files, system environment, or response chains) are treated as errors.
probeflow validate requests.httpNormalize a .http file for consistent style (method casing, header formatting,
separator spacing).
probeflow format requests.http # Format in-place
probeflow format requests.http --check # Check only — exit 0 if already formatted
probeflow format requests.http -o out.http # Write to a different fileShow the installed version and implemented grammar version.
probeflow versionThe test command's non-zero exit status makes a broken API produce a red check:
name: API tests
on: [push, pull_request]
jobs:
api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install probeflow
- name: Run API contract tests
run: probeflow test tests/api.http --junit-xml api-results.xmlIf tests/api.http contains # status == 200 and the API returns 500, the
step exits 1 and GitHub marks the workflow with a red X.
probeflow follows the IntelliJ / VS Code REST Client .http file format.
All probeflow-specific extensions (### @assert, ### @name, etc.) use constructs
that non-probeflow tools treat as comments or separators, so files remain
compatible with both editors.
GET https://api.example.com/usersPOST https://api.example.com/users
Content-Type: application/json
Accept: application/json
Authorization: Bearer token123
{
"name": "Alice",
"role": "admin"
}Separate requests with ###:
### @name = listUsers
GET https://api.example.com/users
###
### @name = createUser
POST https://api.example.com/users
Content-Type: application/json
{"name": "Bob"}### @name = healthCheck
GET https://api.example.com/healthNames are used in test output and for response chaining.
// This is a comment
# This is also a comment
GET https://api.example.com/dataGET https://api.example.com/data HTTP/1.1### @before and ### @after directives point to Python hook functions:
### @name = createResource
### @before = hooks.py:sign_request
POST https://api.example.com/resources
Content-Type: application/json
{"name": "test-resource"}
### @after = hooks.py:verify_signatureHook references are parsed and validated, but not executed in this release.
The test runner refuses files containing hooks rather than running arbitrary code
implicitly. A future executor will require an explicit --allow-scripts flag.
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Add a ### @assert block after any request to make probeflow test enforce
the response contract:
### @name = getUser
GET https://api.example.com/users/1
### @assert
# status == 200
# body.$.name == "Alice"
# body.$.email is string
# body.$.roles is array
# body.$.address exists
# duration < 500ms
# header.Content-Type contains "application/json"| Target | Example |
|---|---|
| Status code | status == 200 · status in [200, 201] · status < 300 |
| Body field | body.$.name == "Alice" · body.$.count > 0 |
| Body type | body.$.id is number · body.$.tags is array |
| Body existence | body.$.token exists · body.$.error not exists |
| Body pattern | body.$.email matches ".*@example\\.com" |
| Header | header.Content-Type contains "json" · header.X-Id exists |
| Duration | duration < 500ms |
== != < > <= >= in contains matches exists not exists is
string · number · boolean · array · object
Use a preceding named request's response as a variable in later requests:
### @name = login
POST https://api.example.com/auth
Content-Type: application/json
{"username": "alice", "password": "{{auth_password}}"}
###
### @name = getProfile
GET https://api.example.com/me
Authorization: Bearer {{login.response.body.$.token}}
### @assert
# status == 200
# body.$.username == "alice"| Reference | Resolves to |
|---|---|
{{name.response.status}} |
HTTP status code as a string |
{{name.response.body.$.field}} |
JSONPath value from the response body |
{{name.response.headers.X-Request-Id}} |
Response header value |
Chaining references resolve in declaration order. Referencing a request that hasn't run yet, or one that failed, is a hard error with a clear message.
Place a .env file (or .env.<name>) next to your .http file, or in any
parent directory. ProbeFlow searches upward from the .http file's directory
to find the nearest .env file:
# .env.dev
base_url=https://api.example.com
auth_token=my-secret-tokenUse {{variable}} syntax in URLs, headers, and bodies:
### @env = dev
GET {{base_url}}/users
Authorization: Bearer {{auth_token}}- Chaining references (
{{name.response.*}}) — resolved from prior responses .env.<name>file variables (when@envis set)- Default
.env/.env.localfile variables - System environment variables (
os.environ) - Left as-is (
{{name}}) — visible in output so you know what's missing
probeflow run displays:
- Status line color-coded by range — green 2xx, yellow 3xx, red 4xx, bold red 5xx
- Timing in milliseconds and response size in human-readable bytes
- JSON syntax highlighting for
application/jsonresponses - Response headers when
--headersis passed
probeflow test displays per-request PASS / FAIL with the first failing
assertion message. Use --json or --junit-xml for machine-readable output.
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
# Run the full test suite (coverage included)
python -m pytest
# Lint
ruff check .
# Format
ruff format .
# Check formatting without modifying
ruff format --check .probeflow/
├── probeflow/
│ ├── __init__.py # Package metadata and version
│ ├── models.py # Pydantic data models (AST)
│ ├── parser.py # .http file lexer + grammar-driven parser
│ ├── client.py # HTTP client (httpx wrapper)
│ ├── environment.py # Variable substitution, .env loading, auth helpers
│ ├── evaluator.py # Assertion evaluation engine
│ ├── formatter.py # Rich terminal output
│ ├── test_runner.py # Test suite loop, JUnit/JSON report writers
│ └── cli.py # Typer CLI — run, test, validate, format, version
├── tests/
│ ├── conftest.py
│ ├── fixtures/ # .http fixture files + golden JSON snapshots
│ ├── test_parser.py
│ ├── test_parser_golden.py
│ ├── test_environment.py
│ ├── test_evaluator.py
│ ├── test_client.py
│ ├── test_cli.py
│ └── test_phase2.py # Auth, multipart, response chaining
├── examples/
│ ├── simple.http
│ ├── requests.http
│ └── .env.dev
├── docs/
│ └── spec.md # Formal .http grammar (EBNF)
├── pyproject.toml
└── README.md
MIT