Share a k6 load test report
A k6 run ends with one of the best terminal summaries in the tooling world —
checks, thresholds, http_req_duration percentiles, all of it. Then the
pipeline finishes, the log scrolls into the archive, and the only record of
whether last week's deploy moved p95 is somebody's memory.
Load tests are comparative by nature. A p95 of 340 ms means nothing on its own; it means everything against last Thursday's 180 ms. But the artifacts a k6 run produces by default are the worst possible substrate for comparison: a CI log that expires, or an HTML file inside an artifact zip.
Step 1 — get HTML out of k6
handleSummary is the hook. k6 calls it once at the end of the run with the
full aggregated summary and writes whatever files you return:
export function handleSummary(data) {
const p95 = data.metrics.http_req_duration.values["p(95)"].toFixed(1);
const failRate = (data.metrics.http_req_failed.values.rate * 100).toFixed(2);
return {
"summary.json": JSON.stringify(data, null, 2),
"summary.html": `
<h1>Checkout load test</h1>
<table>
<tr><th>Requests</th><td>${data.metrics.http_reqs.values.count}</td></tr>
<tr><th>p95 duration</th><td>${p95} ms</td></tr>
<tr><th>Failure rate</th><td>${failRate}%</td></tr>
</table>`,
};
}
Keep it plain HTML — tables, headings, numbers. That's what a reviewer wants to point at, and it's what survives sanitization on publish.
Step 2 — publish it to a URL that doesn't move
Store a scoped token (reports:write only) in your CI
secret store, create the report once, and PATCH it every run:
k6 run load/checkout.js
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html summary.html \
--arg title "Checkout load test — $GIT_SHA" '{title: $title, html: $html}')"
Attach summary.json as an asset if you want the raw metrics to
travel with the document — 25 MB per file.
Publish on failure too (if: always() in GitHub Actions, when: always in
GitLab and CircleCI, post { always { … } } in Jenkins). A run that blew its
thresholds is the run people need to see. Provider wiring:
GitHub Actions,
GitLab CI,
Jenkins,
CircleCI.
Step 3 — let the history do the arguing
One report id per scenario means every run appends a revision at the same URL. Two consequences:
- Regressions are a diff. Compare this run against the last green one and the p95 line that moved is highlighted. Nobody has to open two CI logs side by side and squint.
- The thread lives on the number. A reviewer highlights the failure-rate row and pins a comment — "this spike is the new fraud check, expected, we're tuning the timeout in #1180." Three weeks later, when someone asks about the same spike, the answer is attached to it rather than lost in Slack. See commenting on HTML.
Add a webhook on revision.created and each run announces itself
in Slack — the notification points at the report instead of replacing it.
Running it on a schedule
Load tests that only run before a release tell you about releases, not about drift. A routine can run the publish on a schedule — nightly, weekly — so the trend line exists whether or not anyone remembered to kick off a run. See scheduled HTML reports.
Keep it internal
Throughput ceilings, error rates and endpoint names are a fairly precise
description of your system's capacity and its soft spots. Default the
visibility to team or domain-gated rather than public — see the
sharing model.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.
Related
- Share a Lighthouse report — the other performance number people argue about
- Share a Playwright report
- Publish from CI