QANode Logo

Load Test Node

The Load Test node runs load and performance tests against HTTP endpoints, simulating multiple virtual users (VUs) in parallel. It generates PNG visual evidence with detailed metrics and supports automatic pass/fail criteria via thresholds.


Overview

PropertyValue
Typeload-test
CategoryPerformance
Color🟫 Amber (#92400E)
Inputin
Outputout

Test Types

Each test type automatically generates a different load profile from the VUs and Duration fields.

TypeDescriptionWhen to Use
Smoke1 VU, configured durationValidate that the endpoint responds before running larger tests
LoadRamp up → sustain → ramp downVerify behavior under expected normal load
StressAggressive ramp to the limitIdentify the point where the system starts to degrade
SpikeSudden VU spikeTest reaction to sudden traffic spikes (e.g., flash sales)
SoakSustained load over long durationDetect memory leaks and gradual degradation
BreakpointProgressive VU staircaseFind the exact breaking point of the system

Configuration

Request

FieldTypeDescription
CredentialstringSaved HTTP credential (auto-fills URL and auth)
MethodstringGET, QUERY, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
URLstringTarget endpoint (supports {{ }})
AuthstringManual authentication type (if not using a credential)
HeadersobjectAdditional request headers
Query ParametersarrayKey-value pairs added to the URL
Body TypestringNone, JSON, Raw text, x-www-form-urlencoded, multipart/form-data, or Binary file
Body / Fields / FileanyContent sent according to the selected body type

Request Body

The Load Test request builder follows the same model as the HTTP Request node:

TypeWhen to use
NoneRequests without a body
JSONREST APIs that receive JSON objects or arrays
Raw textXML, SOAP, textual payloads, or custom formats
x-www-form-urlencodedURL-encoded forms
multipart/form-dataForms with text fields and files
Binary fileDirect upload of a fileRef as the request body

Header and body fields accept {{ }} expressions. To send a file, use a fileRef produced by another node:

File: {{ steps["file-generate"].outputs.fileRef }}

You can also import a curl command; QANode tries to convert the method, URL, headers, query, body, and form fields to the matching visual mode.

Dynamic Data per Request

The Dynamic data section generates fresh values immediately before every request made by each virtual user. Use it when an endpoint requires a unique email, external identifier, sequence number, future date, or any value that must not be repeated during the test.

Create a value under Dynamic data and reference it with:

{{ load.valueName }}

Dynamic values can be used in:

  • the URL and query parameters;
  • header names and values;
  • JSON or raw-text bodies;
  • x-www-form-urlencoded keys and values;
  • names and values of text fields in multipart/form-data.

Binary file contents are not changed. In multipart requests, only the field name and text fields accept dynamic data.

Built-in values

These values are available automatically and do not need to be created:

ExpressionValue
{{ load.runId }}QANode execution ID
{{ load.requestId }}Global request sequence, starting at 1 and unique across all VUs
{{ load.vuId }}Virtual user identifier
{{ load.iteration }}Iteration number within that VU
{{ load.timestamp }}ISO timestamp generated at request time

The same value is reused everywhere within one request. Generated values are refreshed for the next request.

Example:

URL: https://api.example.com/orders/{{ load.requestId }}
Header X-Request-Id: {{ load.requestId }}
Body: { "requestId": "{{ load.requestId }}" }

The URL, header, and body receive the same requestId for that call.

Available types

TypeConfigurationResult
UUIDName onlyNew UUID v4 for every request
EmailPrefix and domainUnique email containing the execution token and requestId
SequenceStart and incrementstart + (requestId - 1) × increment
Random numberMinimum, maximum, and decimal placesRandom number within the range
ListOne value per line; random or sequential selectionOne list item per request
TimestampFormat, offset, and unitCurrent, past, or future date
TemplateText with other load expressionsValue composed after the other dynamic values are generated
UUID
Name: externalId
Type: UUID
Use: {{ load.externalId }}
Email
Name: customerEmail
Type: Email
Prefix: performance
Domain: example.com
Use: {{ load.customerEmail }}

The result follows this pattern and does not repeat within the run:

performance_executionToken_142@example.com

Use a reserved domain or a domain controlled by your company. Do not use real addresses in a test that may send email.

Sequence
Name: customerNumber
Start: 1000
Increment: 5

The first requests produce 1000, 1005, 1010, and so on, regardless of which VU executes them.

Random number
Name: amount
Minimum: 10
Maximum: 100
Decimal places: 2

From 0 to 10 decimal places are supported. The maximum must be greater than or equal to the minimum.

List
Name: region
Values:
  south
  north
  east
Selection: Sequential

In Sequential mode, items cycle according to requestId, which helps distribute load evenly. In Random mode, any item can be selected for each request.

Timestamp

Available formats:

FormatExample
ISO2026-07-21T18:30:00.000Z
Unix1784658600 in seconds
Unix ms1784658600000 in milliseconds

The offset supports seconds, minutes, hours, or days. Use a positive number for the future and a negative number for the past.

Name: expiresAt
Format: ISO
Offset: 2
Unit: Hours
Template

Templates are evaluated after the other dynamic data and can combine their values:

Name: customerKey
Template: customer-{{ load.customerNumber }}-{{ load.externalId }}
Use: {{ load.customerKey }}

Complete example — unique user creation

Create these values:

NameTypeConfiguration
emailEmailprefix performance, domain example.com
externalIdUUID
planListbasic, pro, sequential selection
sequenceSequencestart 1, increment 1

Use them in the body:

{
  "email": "{{ load.email }}",
  "externalId": "{{ load.externalId }}",
  "plan": "{{ load.plan }}",
  "sequence": "{{ load.sequence }}",
  "requestId": "{{ load.requestId }}",
  "createdAt": "{{ load.timestamp }}"
}

Every call creates a different payload, even when multiple VUs run concurrently.

Naming rules

  • names must start with a letter;
  • they may contain letters, numbers, _, or $;
  • names must be unique;
  • reserved names cannot be used: runId, requestId, vuId, iteration, and timestamp.

QANode validates unknown references before starting the load. If the body uses {{ load.customer }} without a customer definition, the execution fails with a configuration message instead of sending incorrect requests.

Best practices

  • use a sequence when distribution must be predictable;
  • use a sequential list to balance regions, plans, or categories;
  • use UUID or email when the API requires uniqueness;
  • plan cleanup for records created by the test;
  • remember that dynamic data changes inputs but does not replace thresholds and metric analysis;
  • run a Smoke Test before the full load to validate the generated format.

Load Configuration

FieldTypeDefaultDescription
VUsnumber10Number of concurrent virtual users
Duration (s)number30Total test duration in seconds
Think Time (ms)number0Pause between requests per VU
Timeout (ms)number30000Maximum wait time per response

For Smoke, the VUs field is fixed at 1 and not displayed.
For Soak, the default duration is 1800s (30 min).
For Breakpoint, VUs represents the maximum VUs to be reached.

How Breakpoint works

The test divides the total duration into ~30s steps, progressively increasing VUs:

VUs: 10 | Duration: 60s  →  2 steps of 30s
  Step 1: 0s → 30s  →  5 VUs
  Step 2: 30s → 60s →  10 VUs

Custom Stages

Enable Custom in the Stages section to manually define the load profile:

FieldDescription
Duration (s)Duration of this stage in seconds
Target VUsNumber of VUs at the end of this stage

Example stages for a manual stress test:

DurationTarget VUsDescription
30s10Initial ramp up
60s50Sustained load
30s100Stress peak
15s0Ramp down

Authentication

Using Saved Credentials

Select an HTTP/API credential. The base URL and authentication data are applied automatically:

  1. Select the credential in the Credential field
  2. The base URL is automatically filled in the URL field
  3. Complete with the endpoint path: /api/checkout

Manual Authentication

TypeFieldsResult
Bearer TokenTokenHeader Authorization: Bearer {token}
Basic AuthUsername + PasswordHeader Authorization: Basic {base64}
API KeyHeader Name + TokenCustom header with the token

Thresholds

Thresholds define automatic pass/fail criteria. If any threshold is not met, the node is marked as FAILED.

MetricDescription
p5050th percentile latency (ms)
p9595th percentile latency (ms)
p9999th percentile latency (ms)
avgDurationAverage latency (ms)
errorRateError rate (%)
rpsRequests per second
OperatorMeaning
<Less than
Less than or equal
>Greater than
Greater than or equal

Common threshold examples:

ThresholdMeaning
p95 < 50095% of responses under 500ms
errorRate < 1Error rate below 1%
rps > 10Minimum 10 requests per second
p99 < 200099% of responses under 2s

Without configured thresholds, the node always passes (as long as the endpoint responds).


Outputs

OutputTypeDescription
passedbooleantrue if all thresholds were met
testTypestringTest type executed
metricsobjectConsolidated test metrics
thresholdsarrayResult of each configured threshold
stagesarrayExecuted stages (auto or custom)

metrics object structure

{
  "totalRequests": 1141,
  "errorCount": 0,
  "errorRate": 0.0,
  "rps": 34.49,
  "avgDuration": 212,
  "minDuration": 98,
  "maxDuration": 668,
  "p50": 182,
  "p90": 332,
  "p95": 451,
  "p99": 579
}

Accessing Outputs

// Check if passed
{{ steps["load-test"].outputs.passed }}  →  true

// Total requests
{{ steps["load-test"].outputs.metrics.totalRequests }}  →  1141

// p95 latency
{{ steps["load-test"].outputs.metrics.p95 }}  →  451

// Error rate
{{ steps["load-test"].outputs.metrics.errorRate }}  →  0.0

// RPS
{{ steps["load-test"].outputs.metrics.rps }}  →  34.49

Generated Evidence

The node automatically generates two PNG charts as execution evidence:

1. Summary Report (load-test-report.png)

Consolidated view with:

  • Metric cards (p50, p95, p99, Avg, RPS, Requests, Errors, Error Rate)
  • Horizontal bar chart with latency distribution
  • Throughput over time chart (req/s)
  • Status pills for each configured threshold
  • Footer with executed stages

2. RPS × Latency Timeline (load-test-timeline.png)

Dual-axis chart with:

  • Blue bars (left axis): RPS over time
  • Orange line (right axis): Average latency over time
  • Purple dashed line (right axis): p95 latency over time

The timeline chart is especially useful for Breakpoint and Stress tests, where you can visually identify the exact moment latency starts rising in response to increased load.


Practical Examples

Smoke test — Basic validation

Type: Smoke
URL: https://api.example.com/health
Method: GET
Duration: 10s

Ideal to run at the start of a test flow — ensures the environment is up.

Load test — Normal load with thresholds

Type: Load
URL: https://api.example.com/products
Method: GET
VUs: 20
Duration: 60s
Thresholds:
  - p95 < 500
  - errorRate < 1

Stress test — System limits

Type: Stress
URL: https://api.example.com/checkout
Method: POST
VUs: 100
Duration: 120s
Body: { "productId": "123", "quantity": 1 }
Auth: Bearer → {{ variables.API_TOKEN }}
Thresholds:
  - p99 < 2000
  - errorRate < 5

Breakpoint — Finding the breaking point

Type: Breakpoint
URL: https://api.example.com/search
Method: GET
Max VUs: 200
Duration: 300s
Thresholds:
  - p95 < 1000
  - errorRate < 2

The system will progressively increase from 1 to 200 VUs in ~30s steps. When p95 exceeds 1000ms or errors surpass 2%, the node marks as failed — indicating the breaking point.

Chaining with other nodes

[Load Test: Smoke]
    │ outputs.passed = true
    ▼
[If: {{ steps.smoke.outputs.passed }}]
    │ true  → [Load Test: Full load]
    │ false → [Log: "Smoke failed — environment unavailable"]

Queue Isolation

The Load Test node runs in a separate queue (qanode-load-tests) to avoid interfering with other running flows.

To configure a dedicated Load Test worker:

WORKER_QUEUES=load-tests node dist/start.js

For a worker that processes both queues:

WORKER_QUEUES=executions,load-tests node dist/start.js

Tips

  • Start with Smoke before running load tests — ensures the endpoint is responding correctly
  • Configure thresholds so the test fails automatically when the system degrades, without having to analyze numbers manually
  • Use the timeline chart to identify the exact moment of degradation in Breakpoint and Stress tests
  • Think Time simulates human behavior — useful for Soak tests where you want continuous but realistic load
  • Saved credentials make it easy to run in different environments (staging, production) without changing the flow
  • For reliable tests, up to 100 VUs per worker instance. Above that, consider a dedicated worker