Skip to content

Custom code

The Custom code step (script.run, category Data) runs a short JavaScript or TypeScript snippet in a sandbox and keeps what it returns. Use it for logic that is awkward as steps or expressions: formatting values, calculating totals, generating unique data, checking the shape of an API response.

Writing or changing custom code needs the Write custom code steps permission (Test Developer, Test Manager, Admin). Testers can run tests that contain code, and edit their other steps, but cannot change the code.

Field Meaning
Language JavaScript or TypeScript (types are checked for syntax and then stripped)
Time limit (ms) CPU time for the snippet. Default 1000, maximum 10000. Separate from the step’s timeout.
Code * The body of a function. Use return for the result, or throw to fail the step. Up to 20,000 characters.
Save result as Variable that receives the returned value. Leave it empty to make the step a check: it passes unless the code throws.
Secrets it may read Names of secrets the code may use, e.g. API_TOKEN, SIGNING_KEY. None by default. Up to 20.
Test run Try the code in the editor against sample context.variables (JSON). Secrets are replaced by placeholders. Choose ▶ Run code.

Code that does not compile is reported when you save, and the test cannot be saved.

The function receives one argument, context, which is read-only:

Property Contents
context.variables Every variable in scope, with the same precedence as {{name}}: runtime, test, environment
context.data The current row of a data-driven run, or null
context.locals Loop variables (For each / Repeat) and component parameters
context.secrets Only the secrets listed in Secrets it may read, as context.secrets.NAME

console.log(...) output is kept with the step result.

Transform a value (Save result as paddedInvoice):

const value = context.variables.invoiceNumber; // "INV-12345"
return value.replace("INV-", "").padStart(10, "0"); // "0000012345"

A check (Save result as left empty):

const padded = context.variables.paddedInvoice;
if (!/^\d{10}$/.test(padded)) {
throw new Error(`Expected 10 digits, got "${padded}"`);
}
console.log("Invoice number looks right:", padded);

TypeScript, returning an object (Save result as totals):

interface Line { sku: string; qty: number; unitPrice: number }
const lines: Line[] = context.variables.orderLines;
const cents = (n: number): number => Math.round(n * 100) / 100;
const subtotal = cents(lines.reduce((sum, l) => sum + l.qty * l.unitPrice, 0));
return { subtotal, lineCount: lines.length };

Later steps use {{totals.subtotal}}, or totals.subtotal > 0 in an expression.

The code runs in an isolated JavaScript engine (QuickJS), not in the browser and not on the server’s Node.js:

Limit Value
Network, files, processes, timers Not available (fetch, require, process, setTimeout are undefined)
The page under test Not reachable. Read values from the page first with an Extract step.
CPU time Time limit, default 1 s, maximum 10 s
Memory 32 MB
Returned value Must be JSON-compatible, at most 256 KB
context Frozen; changing it throws a TypeError

Errors you may see:

Error Meaning
ScriptTimeoutError: The code ran longer than its time limit. An endless loop, or too much work for the limit
ScriptMemoryError: The code used more memory than it is allowed. Over 32 MB
ScriptResultError: The code returned nothing (undefined). Save result as is set but the code did not return. Return a value, or clear Save result as.
Line N: … A syntax or runtime error, with the line in your code

Custom code runs on the web engine only.

examples/custom-code/ has seven tests (CC 01–07): formatting, TypeScript totals, generating unique data, response-shape checks, a tour of the sandbox limits, per-row logic in a data-driven test, and building an API order. Import them with scripts/seed-custom-code-examples.mts (see Data, backup and seeding).