Lifecycle hooks
┌──────────┐
│ setup() │ once, before any VU; may make requests;
└────┬─────┘ return value shared (read-only) with all VUs
│
┌─────────────┴──────────────┐
│ per iteration, per VU: │
│ flow steps │ beforeRequest(req) ─▶ request ─▶ afterRequest(res)
│ then exec function │ (around every YAML request step)
└─────────────┬──────────────┘
│
┌────┴──────┐
│ teardown()│ once, after the run (even on abort)
└───────────┘
setup() / teardown(data)
export function setup() {
const res = http.post('/auth/token', JSON.stringify({ id: __ENV.CLIENT_ID }));
return { token: res.json().token }; // must be JSON-serializable
}
export function teardown(data) {
http.post('/auth/revoke', JSON.stringify({ token: data.token }));
}
Scenario functions
A scenario runs its YAML flow first (if any), then its exec function
(default export when exec: default):
scenarios:
scripted: { executor: constant-vus, vus: 10, duration: 5m, exec: buyFlow }
export function buyFlow(data, ctx) {
// data = setup() result; ctx = { vu, iteration, scenario }
const res = http.get('/items', { headers: { Authorization: `Bearer ${data.token}` } });
check(res, { 'ok': (r) => r.status === 200 });
sleep(1);
}
beforeRequest(req) / afterRequest(res)
Fire around every YAML request: step (not around http.* calls made
from JS). beforeRequest may mutate and return the request:
export function beforeRequest(req) {
req.headers['X-Signature'] = crypto.hmac('sha256', __ENV.SIGNING_KEY, req.body || '', 'hex');
return req; // returning nothing keeps the request unchanged
}
export function afterRequest(res) {
if (res.status === 429) console.warn(`rate limited on ${res.url}`);
}
The req object: {name, method, url, headers, body} — url, method,
headers and body may be overridden by the returned object.
Per-VU on_start / on_stop
A scenario can name an exported function to run once per VU, around that
VU's stream of iterations (Locust's on_start / on_stop):
on_startruns once, just before the VU's first iteration.on_stopruns once, when the VU retires (after its last iteration). It is skipped for a VU that never ran an iteration.
Use them for per-user setup and cleanup that should happen once per virtual
user rather than once per iteration — e.g. log in on start, log out on stop.
Both receive the setup() result as their single argument:
scenarios:
users:
executor: constant-vus
vus: 50
duration: 5m
on_start: login # exported from the JS module
on_stop: logout
exec: browse
export function login(data) {
const res = http.post('/auth/login', JSON.stringify({ pw: __ENV.PW }));
// Stash per-VU state on the VU's session for later iterations.
session.vars.token = res.json().token;
}
export function browse(data) {
http.get('/feed', { headers: { Authorization: `Bearer ${session.vars.token}` } });
}
export function logout(data) {
http.post('/auth/logout', JSON.stringify({ token: session.vars.token }));
}
on_start runs per VU (so once per simulated user), whereas setup() runs
once for the whole test. A failing on_start / on_stop is logged as a
warning and does not abort the run.
handleSummary(data)
Export handleSummary to produce a custom end-of-run report. It runs once,
after teardown(), with the run summary as its single argument. If it returns
a string, that string replaces the default console summary; returning nothing
(or null) leaves the default summary in place. This matches k6's
handleSummary.
export function handleSummary(data) {
const reqs = data.metrics.find((m) => m.metric === 'http_reqs');
const dur = data.metrics.find((m) => m.metric === 'http_req_duration');
return [
`run ${data.run_id} — ${data.duration_secs.toFixed(1)}s`,
`requests: ${reqs ? reqs.agg.sum : 0}`,
`p95 latency: ${dur ? dur.agg.p95.toFixed(1) : 0} ms`,
`thresholds passed: ${data.thresholds_passed}`,
].join('\n');
}
data is the run summary (the same object written by the JSON output):
{
name, run_id,
started_ms, ended_ms, duration_secs,
scenarios: ['users', ...], // scenario names
metrics: [ { metric, kind, agg: { avg, min, med, max, p90, p95, p99,
sum, count, rate, per_second, last } }, ... ],
checks: [ { name, passes, fails }, ... ],
thresholds: [ ... ],
thresholds_passed: true,
aborted: null, // abort reason, if any
}
Non-string return values are pretty-printed as JSON and used as the report.