dq-nbomber-cli 0.2.1

dotnet tool install --global dq-nbomber-cli --version 0.2.1
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local dq-nbomber-cli --version 0.2.1
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=dq-nbomber-cli&version=0.2.1
                    
nuke :add-package dq-nbomber-cli --version 0.2.1
                    

dq-nbomber-cli

All-in-one .NET global tool CLI for NBomber load testing.
Scaffold projects, generate scenarios from OpenAPI/GraphQL specs, run declarative YAML configs, validate, report, and enforce CI thresholds — with a single command.

Requirements


Install the tool:

dotnet tool install -g dq-nbomber-cli

Tip: During development you can skip the install and use dotnet run --project src/DqNBomber.Cli -- instead of dq-nbomber.


Quick start — generate and run a load test from your OpenAPI spec

The example below targets a service running at http://localhost:3000 with its spec exposed at http://localhost:3000/api-docs.json.

Step 1 — Generate a load test config

dq-nbomber generate http://localhost:3000/api-docs.json \
  --base-url http://localhost:3000 \
  --output-dir ./my-loadtest \
  --data-records 20

What this does:

  • Fetches and parses your OpenAPI spec from the live URL
  • Shows an interactive prompt — use Space to toggle endpoints, Enter to confirm
  • Detects auth schemes and automatically inserts a login step that captures a JWT token
  • Writes the following files to ./my-loadtest/:
    • dq-nbomber.yaml — declarative load test config
    • data/users.csv — 20 rows of Bogus-generated fake users (email, password, firstName, lastName)
    • .env.example — environment variable stubs

To skip the interactive picker and include every endpoint:

dq-nbomber generate http://localhost:3000/api-docs.json \
  --base-url http://localhost:3000 \
  --output-dir ./my-loadtest \
  --non-interactive

To include or exclude specific endpoints:

# Only POST and GET on /orders
dq-nbomber generate http://localhost:3000/api-docs.json \
  --base-url http://localhost:3000 \
  --output-dir ./my-loadtest \
  --include "POST /orders,GET /orders/{id}"

# Exclude destructive endpoints
dq-nbomber generate http://localhost:3000/api-docs.json \
  --base-url http://localhost:3000 \
  --output-dir ./my-loadtest \
  --exclude "DELETE *"

Step 2 — Review and edit the generated config

cat ./my-loadtest/dq-nbomber.yaml

A typical generated config looks like this:

scenarios:
  - name: api_load_test
    steps:
      - name: login
        http:
          method: POST
          url: ${BASE_URL}/auth/login
          body:
            json:
              email: ${data.users.email}
              password: ${data.users.password}
        capture:
          - jsonPath: "$.token"
            as: authToken

      - name: get_orders
        http:
          method: GET
          url: ${BASE_URL}/orders
          headers:
            Authorization: Bearer ${capture.authToken}

    loadSimulations:
      - kind: inject
        rate: 10
        interval: "00:00:01"
        during: "00:01:00"

    thresholds:
      - okRequest: "Percent > 95"
      - okLatency: "P99 < 500"

report:
  folder: reports

Set the BASE_URL env var (or copy .env.example to .env and edit it):

cd ./my-loadtest
cp .env.example .env
# edit .env: BASE_URL=http://localhost:3000

Step 3 — Validate the config

dq-nbomber validate ./my-loadtest/dq-nbomber.yaml

Expected output:

✓ Config is valid.

Step 4 — Run the load test

dq-nbomber run ./my-loadtest/dq-nbomber.yaml

Options:

Flag Default Description
--profile <name> Overlay config.<name>.yaml on top of the base config
--target <name> Run only specific scenario(s) by name (repeat for multiple)
--report-folder <path> reports/ Write reports to a custom root folder
--display-console-metrics false Show real-time per-second metrics in the terminal
--no-warmup false Skip the warm-up phase for all scenarios
--log-level <level> normal NBomber log verbosity: quiet · normal · verbose

Global --debug flag — place it before run:

# Trace every HTTP request and response while the test runs
dq-nbomber --debug run ./my-loadtest/dq-nbomber.yaml

For each step you will see:

[login] → POST https://api.example.com/auth
          Content-Type: application/json
          Body: {"username":"alice","password":"***"}

[login] ✓ ← 200 OK
          content-type: application/json
          Body: {"token":"eyJ..."}

[login] Captures:
          ✓ token = eyJ...

Sensitive headers (Authorization) and JSON fields (password, secret, token, apikey) are automatically redacted.

Suppress all NBomber output (CI mode):

dq-nbomber run ./my-loadtest/dq-nbomber.yaml --log-level quiet

Example with a profile:

dq-nbomber run ./my-loadtest/dq-nbomber.yaml \
  --profile staging \
  --display-console-metrics

This loads ./my-loadtest/config.staging.yaml and merges it on top of the base config.


Export to a runnable C# program

Once your dq-nbomber.yaml is validated, you can export it to a self-contained NBomber C# program:

dq-nbomber export ./my-loadtest/dq-nbomber.yaml

This writes two files into the same directory as the YAML:

File Description
Program.cs Runnable NBomber 6.x C# program using NBomber.Data typed data feeds
README.md Run instructions for the exported program

The generated Program.cs:

  • Uses #:package directives — no .csproj needed with .NET 10+
  • Loads data feeds with Data.LoadCsv<T>() / Data.LoadJson<T[]>() and DataFeed.Circular()
  • Reads BASE_URL and secrets from the .env file at runtime
  • Requires NBomber, NBomber.Http, and NBomber.Data packages (auto-restored by dotnet run)
cd ./my-loadtest
# edit .env: set BASE_URL and any auth secrets
dotnet run Program.cs

Export options

Option Default Description
--target nbomber Export target. Currently: nbomber
--format file file — single Program.cs with #:package (dotnet 10+); projectProgram.cs + LoadTest.csproj (dotnet 8/9)
--readme true Write a README.md alongside Program.cs

Export to a project format (dotnet 8/9 compatible):

dq-nbomber export ./my-loadtest/dq-nbomber.yaml --format project

This creates LoadTest.csproj in addition to Program.cs so you can use dotnet run without .NET 10.


Trend reporting

Every dq-nbomber run writes its results into a timestamped subfolder (reports/{yyyy-MM-dd_HH-mm-ss}_{yaml}/) and saves a run-meta.json snapshot alongside the standard NBomber reports. The trend command reads all of those snapshots and produces:

  • Console table — last N runs with per-step OK/Fail/RPS/P50/P95/P99, colour-coded latency, and ASCII sparklines.
  • Interactive HTML dashboard — self-contained single-file report with Chart.js charts, opened in your browser.
# View last 20 runs for a specific config (console + generate HTML)
dq-nbomber trend --yaml dq-nbomber.yaml

# Open the HTML report immediately
dq-nbomber trend --yaml dq-nbomber.yaml --open

# Look at a different reports folder and show the last 50 runs
dq-nbomber trend --folder ./perf-reports --yaml dq-nbomber.yaml --last 50

# Write the HTML to a custom path (useful for CI artefacts)
dq-nbomber trend --yaml dq-nbomber.yaml --out ./ci-output/trend.html

Trend command options

Option Default Description
--folder reports Root folder containing timestamped run subfolders.
--yaml Filter to runs generated from a specific YAML file (by base name).
--last 20 Maximum number of runs shown in the console table.
--open false Open the generated HTML report in the default browser.
--out reports/trend-{yaml}.html Custom output path for the HTML file.

HTML dashboard features

The generated HTML file is fully self-contained — no server required, works offline.

Feature Description
KPI summary cards Avg P95, Avg P50, Throughput, Error Rate, Total Requests — each with Δ% vs the previous run
Scenario tabs One tab per scenario; each has its own charts and table
Chart.js line charts P95, P50, P99, Mean latency · RPS · Failure Rate — one series per step
Inline sparklines Per-row mini trend canvas highlighting where each run sits in the overall history
Δ P95% column Red/green regression indicator showing percentage change vs the previous run
Run filter Multi-select dropdown to show only selected run timestamps
Step filter Multi-select dropdown to show only selected steps
Scenario filter Multi-select dropdown above the tab bar to show/hide scenarios
Column sort Click any column header to sort ascending/descending
Date range filter 7 / 14 / 30 / 90 days or All time
Run comparison Pick any two runs and diff all metrics side-by-side with red/green highlighting
CSV export Download all filtered data as a .csv file
Run history table Chronological list of all runs in the selected date range

Reports folder structure

After running several tests the reports/ folder looks like:

reports/
  2026-05-02_14-31-00_dq-nbomber/
    dq-nbomber.csv          ← NBomber per-step results
    dq-nbomber.html         ← NBomber single-run HTML
    dq-nbomber.md
    run-meta.json           ← trend snapshot (auto-written by dq-nbomber run)
  2026-05-02_14-44-00_dq-nbomber/
    ...
    run-meta.json
  trend-dq-nbomber_yaml.html  ← generated by dq-nbomber trend
Command Description
dq-nbomber init [path] Scaffold a new project with a starter dq-nbomber.yaml
dq-nbomber generate <spec> Generate config from an OpenAPI or GraphQL spec
dq-nbomber validate <config> Validate a config file
dq-nbomber export <config> Export a validated YAML to a runnable NBomber C# program
dq-nbomber run <config> Run load test scenarios
dq-nbomber trend View interactive trend report across historical runs
dq-nbomber report list List reports from past runs
dq-nbomber report open Open the latest HTML report in a browser
dq-nbomber thresholds check Run test and exit with code 2 on threshold violations
dq-nbomber run <config> (with cluster: block) Run in distributed NBomber cluster mode (coordinator or agent)

Run dq-nbomber <command> --help for full options on any command.


Config reference

Variable interpolation

Variables are resolved in this order (highest wins):

Syntax Resolved from
${capture.varName} Value captured from a previous step's response
${data.namespace.field} Current row from a data feed file
${ENV_VAR} Process environment variable or .env file
${ENV_VAR:-default} Env var with fallback default

Load simulations

loadSimulations:
  - kind: inject          # inject N new vusers per interval
    rate: 20
    interval: "00:00:01"
    during: "00:01:00"

  - kind: keepconstant    # maintain N concurrent vusers
    copies: 50
    during: "00:02:00"

  - kind: rampinginject   # ramp from 0 → rate over during
    rate: 100
    interval: "00:00:01"
    during: "00:02:00"

  - kind: rampingkeepconstant  # ramp from 0 → copies over during
    copies: 100
    during: "00:02:00"

  - kind: pause
    during: "00:00:10"

Thresholds

thresholds:
  - okRequest: "Percent > 95"      # at least 95% of requests must succeed
  - failRequest: "Percent < 1"     # fewer than 1% failures
  - okLatency: "P99 < 500"         # 99th percentile under 500ms
  - okLatency: "P95 < 200"
  - okLatency: "RPS >= 30"         # at least 30 requests per second

Data feeds

scenarios:
  - name: my_scenario
    dataFeeds:
      - file: data/users.csv
        namespace: users
        strategy: circular   # circular | random | unique
        partition: false     # true = slice rows by agent in cluster mode
    steps:
      - name: login
        http:
          method: POST
          url: ${BASE_URL}/login
          body:
            json:
              email: ${data.users.email}
              password: ${data.users.password}
Feed option Default Description
file required Path to .csv or .json file, relative to the YAML
namespace data Variable prefix — ${namespace.column}
strategy circular circular loops rows; random picks randomly; unique each row once
partition false When true in cluster mode, each agent receives a distinct slice of the rows. Safe to leave true for single-node runs — no slicing applied

Capture (cross-step variable extraction)

capture:
  - jsonPath: "$.token"        # simple dot path
    as: authToken
  - jsonPath: "$.data.id"
    as: userId
  - jsonPath: "$.results[0].sku"   # array index
    as: firstSku
  - jsonPath: "$.items[?@.active==true && @.kind=='delivery-truck'].id"  # RFC 9535 filter
    as: itemId                 # use [?@.field==value] NOT [?(@.field==value)]
  - header: "X-Request-Id"    # extract from response header
    as: requestId
  - statusCode: true           # capture HTTP status code
    as: lastStatus
  - regex: "id=([0-9]+)"      # extract first regex group from body
    as: resourceId
  - cookie: "session"          # extract Set-Cookie value
    as: sessionCookie

JsonPath syntax note: The evaluator follows RFC 9535 (JsonPath.Net). Filter expressions must use [?@.field==value] without outer parentheses. The =~ regex operator is not supported — use the regex: extractor instead.


Distributed cluster mode (Kubernetes / multi-node)

Run load tests across multiple nodes using NBomber's built-in coordinator/agent model, backed by NATS as the message bus.

How it works

  • Coordinator node orchestrates the run and collects results.
  • Agent nodes execute the scenarios.
  • All nodes use the same dq-nbomber.yaml — only the cluster.nodeType differs.
  • Cluster settings are declared in a cluster: block inside dq-nbomber.yaml and are automatically forwarded to the NBomber runner. CLI flags always override YAML values.

YAML cluster: block

cluster:
  nodeType: coordinator          # coordinator | agent
  natsUrl: nats://nats-service:4222
  agentsCount: 3                 # coordinator waits for this many agents
  clusterId: my-run              # shared ID — must match across all pods
  localDev: false                # true = single-machine local test
  agentGroup: groupA             # optional ManualCluster group name
  coordinatorTarget:             # scenarios assigned to coordinator
    - api_load
  agentTarget:                   # scenarios assigned to agents
    - api_load

scenarios:
  - name: api_load
    steps: ...

cluster: values map 1-to-1 to NBomber's native CLI args. Any --cluster-* token passed after -- on the command line overrides the YAML value.

Run on Kubernetes (minikube or internal cluster)

1. Deploy NATS

kubectl run nats --image=nats:latest --port=4222
kubectl expose pod nats --port=4222

For internal clusters use a Deployment + Service and reference it as nats://nats.<namespace>.svc.cluster.local:4222.

2. Build your image

The image must contain the .NET runtime, the dq-nbomber tool, and your YAML + data files.

FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY . .
RUN dotnet tool install -g dq-nbomber-cli --add-source /app/nupkg
ENV PATH="$PATH:/root/.dotnet/tools"
ENTRYPOINT ["dq-nbomber", "run", "dq-nbomber.yaml"]

3. Coordinator pod / job

# coordinator.yaml (Kubernetes Job)
apiVersion: batch/v1
kind: Job
metadata:
  name: nbomber-coordinator
spec:
  template:
    spec:
      containers:
        - name: coordinator
          image: your-test-image:latest
          env:
            - name: NBOMBER_LICENSE
              valueFrom:
                secretKeyRef:
                  name: nbomber-secret
                  key: license
      restartPolicy: Never

The cluster: block in dq-nbomber.yaml already sets nodeType: coordinator. Override at deploy time if needed:

kubectl run coordinator --image=your-test-image \
  -- dq-nbomber run dq-nbomber.yaml -- --cluster-node-type=coordinator

4. Agent pods

# Create N agent pods (same image, same YAML, different nodeType)
for i in 1 2 3; do
  kubectl run agent-$i --image=your-test-image \
    -- dq-nbomber run dq-nbomber.yaml -- --cluster-node-type=agent
done

Or use a Deployment with replicas: 3.

5. Minikube quick test (single machine)

Set localDev: true in the YAML (skips NATS, runs coordinator + agents in-process):

cluster:
  localDev: true
  agentsCount: 2

Then run normally:

dq-nbomber run dq-nbomber.yaml

Coordinator console output

When a cluster: block is present, dq-nbomber run prints:

Cluster mode: node=coordinator  nats=nats://nats-service:4222  agents=3

Precedence rules

Source Priority
-- unparsed CLI tokens Highest (always override YAML)
cluster: YAML block Applied if no matching CLI token
NBomber defaults Lowest

CI / CD integration

The run command exits with:

  • 0 — all scenarios passed thresholds
  • 1 — config error or test error
  • 2 — threshold violation

Example GitHub Actions step:

- name: Load test
  run: |
    dq-nbomber run dq-nbomber.yaml --profile ci
  env:
    BASE_URL: ${{ secrets.STAGING_URL }}
    NBOMBER_LICENSE: ${{ secrets.NBOMBER_LICENSE }}

Development

# Build
dotnet build

# Run tests
dotnet test

# Run CLI without installing
dotnet run --project src/DqNBomber.Cli -- <command> [options]

License

This project is licensed under the GNU Affero General Public License v3.0.

You are free to use, modify, and distribute this software under the terms of the AGPL-3.0. If you run a modified version of this program as a network service, you must make the corresponding source code available to users of that service.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.2.1 137 5/9/2026
0.2.0 90 5/9/2026
0.1.0 94 5/9/2026