A CLI tool for defining multi-step API request scenarios in a YAML/JSON manifest, running them in order while threading response data from one request into the next, and generating timing/correctness reports (console, JSON, HTML).
Testing a real-world API flow usually means: call endpoint A, grab something from the response, use it in the headers/body of endpoint B, repeat. This tool lets you describe that flow declaratively and get a report out the other end — request/response detail, per-step timing, pass/fail against expectations, and aggregate stats (slowest step, average duration, etc).
npm install -g dxflow
dxflow run scenario.yamlOr run it without a global install, via npx (the package name is dx-flow, the command it installs is dxflow):
npx dxflow run scenario.yamlTo use it as a library in your own Node project instead:
npm install dxflow(See Programmatic API below.)
dxflow run scenario.yaml [--json report.json] [--html report.html] [--quiet]run <manifest>— required. Path to a.yaml,.yml, or.jsonmanifest.--json <path>— also write the report as JSON.--html <path>— also write the report as a self-contained HTML page (table + timing chart).--quiet— suppress console output (still respects--json/--html).
Exit code is 0 if every step succeeded, 1 otherwise — convenient for CI.
name: Checkout flow
baseUrl: https://api.example.com # optional if every step path is absolute
defaultHeaders: # merged into every step; step-level headers win
X-Client: my-test-suite
vars: # static values, available as {{vars.x}}
customerName: Ada Lovelace
steps:
- id: createUser # must be unique, used to reference this step later
name: Create user account # optional, friendlier label in reports
method: POST
path: /users
body:
name: "{{vars.customerName}}"
expect:
status: 201 # or a list: status: [200, 201]
- id: createOrder
method: POST
path: /orders
headers:
Authorization: "Bearer {{steps.createUser.body.token}}"
body:
userId: "{{steps.createUser.body.id}}"
expect:
status: 201
bodyContains:
status: created
- id: confirmOrder
method: GET
path: /orders/placeholder # overridden by the transform below
transform: ./transforms/confirm-order.mjs#buildConfirmRequest
expect:
status: 200Available under context.steps.<id>:
| Field | Meaning |
|---|---|
body |
parsed JSON response body (or raw text if not JSON) |
status |
HTTP status code |
statusText |
HTTP status text |
headers |
response headers (lowercased keys) |
requestBody |
the body that was actually sent for that step |
durationMs |
that step's request duration |
Dot-paths and numeric array indices both work: {{steps.createUser.body.items.0.id}}.
A field that is exactly one template (e.g. "{{steps.a.body.id}}") keeps its native type (number stays a number). A template embedded in a larger string (e.g. "Bearer {{steps.a.body.token}}") is stringified and interpolated.
{{vars.x}} resolves manifest-level vars.
For anything templating can't express, point a step at a JS/TS module + export:
transform: ./transforms/confirm-order.mjs#buildConfirmRequest// transforms/confirm-order.mjs
export function buildConfirmRequest(ctx) {
const orderId = ctx.steps.createOrder.body.orderId;
return {
path: `/orders/${orderId}`,
query: { verbose: "true" },
// method, headers, body can also be overridden here
};
}The function receives the full run context (scenarioName, baseUrl, vars, steps) and returns an object overriding any of method, path, headers, query, body. Anything it doesn't return falls back to the step's templated values. The path is resolved relative to the manifest file.
query— string-to-string map, supports templating, same as headers/body.delayMs— wait this many ms before sending the request.timeoutMs— per-step request timeout (default 30000).continueOnFailure: true— don't halt the scenario if this step fails (request error or failed expectation). By default, the first failure stops the run.
Everything is also exported from the package root for use as a library:
import { loadManifest, runScenario, renderHtmlReport } from "dx-flow";
const manifest = await loadManifest("./scenario.yaml");
const report = await runScenario(manifest, { manifestPath: "./scenario.yaml" });
console.log(report.success, report.summary);Current coverage: ~98.6% lines / ~93.6% branches / ~96.8% functions across src/ (excluding test files and the build output).