API testing
Requests live in collections inside the same project as your browser tests, so they can share environments, suites and run history.
Capture requests from a session
The fastest way to get API coverage of a flow is to record the flow. A browser opens, every HTTP call your application makes is captured, and you choose which to keep.
qaclan api record --url https://your-app.testStatic assets and analytics beacons are usually noise; the calls that carry your data are the ones worth keeping. What you save becomes a named collection.
Import what you already have
qaclan api import <file_or_url> --format auto --collection <name>Format is detected from the extension and content when --format auto is used, which is the default. Accepted values:
| Format | Source |
|---|---|
har | A HAR capture exported from your browser's network tab |
openapi | An OpenAPI or Swagger document — a local file or a URL, YAML or JSON |
postman | A Postman collection export |
bruno | Bruno .bru files |
An OpenAPI import groups requests into collections by tag, so a well-tagged document arrives already organised. In the local UI you can also paste one or more curl commands and preview the requests before saving them.
Collections, folders and variables
A collection holds requests and folders. Collection-level variables and environment variables both resolve as {{var}} anywhere in a URL, header, query parameter or body — see Environments and secrets.
qaclan api listqaclan api list --collection OrdersAuthentication
Auth is configured on the request or inherited from the collection, and applied when the request runs rather than pasted into headers by hand. Supported types:
- Bearer — a token sent as
Authorization: Bearer … - Basic — username and password
- API key — a named key in a header or query parameter
- OAuth2
Assertions without code
An assertion is a type, an operator and an expected value. Add them in the request editor's Assertions tab; results appear per request in the run.
| Type | Operators | Checks |
|---|---|---|
status | eq ne lt gt | HTTP status code, compared numerically |
json_path | eq ne lt gt contains exists not_exists matches | The value at a JSONPath expression in the JSON response body |
header | eq ne contains exists not_exists matches | A response header, matched case-insensitively by name |
response_time | lt gt eq | Request duration in milliseconds |
body_text | contains eq matches | The raw response body as text |
matches is a real regular expression search — case-sensitive, not anchored. For a JSONPath expression that matches several nodes, a match mode decides whether the first, any or all matches must satisfy the operator.
Pre- and post-request scripts
When a check needs real logic, attach a script. Pre-scripts run before the call and can stage headers or parameters; post-scripts run after the response and are the main way a value moves into the next step of a suite. Both are available in JavaScript and Python.
post-script · javascript
const body = qc.response.json();
qc.set("order_id", body.id);
qc.expect(body.status === "confirmed", "order not confirmed");Commonly used bindings:
| Binding | Purpose |
|---|---|
qc.set(key, value) | Write to shared state, so later requests can use it as a variable |
qc.setHeader(key, value) / qc.set_header(...) | Add or overwrite a request header before the call is sent |
qc.setParam(key, value) / qc.set_param(...) | Add or overwrite a query parameter before the call is sent |
qc.expect(condition, message) | Record a pass or fail, shown next to the no-code assertion results |
qc.test(name, fn) | Run a function and record one named assertion from its outcome |
JavaScript uses camelCase (setHeader) and Python uses snake_case (set_header). The agent repository carries the complete syntax reference, including every binding and its edge cases.
GraphQL
A request body can be GraphQL, with a dedicated editor giving syntax highlighting, bracket matching and grammar-level completion, plus a formatter for minified documents.
No schema introspection
The editor does not load your schema, so there is no field- or type-aware autocomplete. It understands GraphQL grammar, not your particular API.
Running requests
qaclan api run <name_or_id>qaclan api run <name_or_id> --collection <name> --env stagingRun one request while you are building it, or a whole collection against an environment. Results, including per-assertion outcomes, are recorded in run history.
Documentation from real traffic
Every request that runs updates an API reference: the method, the path with variables inferred (a call to /posts/1 becomes /posts/{post_id}), and the response shapes observed. It is visible under the API Docs tab in the local UI, and on the server if you are syncing.
Export it when someone outside the team needs a spec:
- OpenAPI YAML — from the API Docs view
- Postman JSON or Bruno files— from a collection's menu
qaclan api export <collection> --output ./exportedThe CLI export writes Bruno .bru files. Postman and OpenAPI export are available from the local UI.
Mixing API and browser steps
The reason all of this lives in one project is that a suite can interleave API requests with recorded browser scripts and pass state between them. That is covered in Suites, runs and reports.