Embedded JavaScript overview
loadr embeds a JavaScript engine (QuickJS — see ADR-001) so dynamic logic lives next to the declarative YAML. JS is usable three ways:
1. Inline expressions
Anywhere ${...} works, ${js: <expr>} evaluates in the VU's runtime:
headers:
X-Request-Id: "${js: crypto.uuidv4()}"
params:
page: "${js: Math.ceil(Math.random() * 10)}"
2. Inline script steps
flow:
- js: "session.counterAdd('pages_viewed', 1)"
- js:
script: |
const row = session.data('users');
session.vars.greeting = `hello ${row.username}`;
- js:
call: warmCache # an exported function from the module
3. A module (inline or file)
js:
file: ./script.js # or script: | (inline source)
timeout: 10s # per-call wall-clock limit (default 10s)
memory_limit_mb: 64 # per-VU heap limit (default 64)
The module is an ES module with k6-compatible imports:
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Counter, Trend } from 'k6/metrics';
export function setup() { /* once, before VUs start */ return {...}; }
export default function (data) { /* per iteration when exec/default used */ }
export function teardown(data) { /* once, after the run */ }
export function beforeRequest(req) { /* around every YAML request */ return req; }
export function afterRequest(res) { /* ... */ }
Isolation & limits
Every VU gets its own JS runtime and context — no shared mutable state between VUs (matching k6). Each runtime enforces:
- a heap limit (
memory_limit_mb) — exceeding it throws; - a wall-clock interrupt per call (
timeout) — infinite loops are killed; - no filesystem or network access except through the provided APIs
(
open()is restricted to the test's directory).
Values flow both ways
- Extracted YAML values appear in JS as
session.vars.<name>. - Values set from JS (
session.vars.x = ...) are usable in YAML as${x}. setup()'s return value is passed to every scenario function and is readable in hooks.