skip to content

In k6, what must an exported `handleSummary(data)` function return, and where does each entry go?

level: juniorimportance: must knowfreq 72%

answer

  1. return a map, not a report
  2. keys name destinations
  3. stdout and stderr are reserved
  4. values: string or ArrayBuffer
  5. any other key is a file path

basics

~20 s

An object whose keys are destinations and whose values are strings or ArrayBuffers. k6 sends the key stdout to standard output, stderr to standard error, and treats every other key as a file path it creates or overwrites.

solid answer

~40 s

k6 calls `handleSummary(data)` once at the very end of a run, after `teardown()`, and expects a map back in the form `{ destination: content }`. Two keys are reserved: `stdout` and `stderr` go to those streams. Every other key is taken as a relative or absolute file path, which k6 opens create-and-truncate — it overwrites, it never appends. Each value must be a `string` or an `ArrayBuffer`; anything else is rejected with `invalid type ..., it needs to be a string or ArrayBuffer`. One return can carry several entries, so the same run can print a one-line note to `stdout` and write `junit.xml`. k6 produces no result file of its own, so a run whose hook returns no file path saved nothing.

code

javascript · 12 lines
javascript
import { jUnit } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';

export const options = { vus: 5, iterations: 10 };

export default function () {}

export function handleSummary(data) {
  return {
    'junit.xml': jUnit(data),
    stdout: 'summary written to junit.xml\n',
  };
}

go deeper

for a junior

Remember the shape: return an object, keys are destinations, values are text or bytes. Know that stdout and stderr are the two reserved keys and that everything else is treated as a file path.

for a middle

Explain that a path key is opened create-and-truncate so each run overwrites, that only a string or an ArrayBuffer is accepted as a value, and that k6 never serialises an object for you.

for a senior

Show that you treat the hook as the only thing that turns a k6 run into an artifact, and that you verify the written path and its permissions rather than assuming a file appeared.

for a principal

Weigh one house convention for what every script returns against per-team formats: the hook lives in the script, so its output travels wherever that script is run.

## The hook and when it runs `handleSummary(data)` is an optional function a k6 script exports. k6 calls it **exactly once per run** — however many VUs, scenarios or iterations the test used — as the last thing the script does: `setup()`, then the scenario functions, then `teardown()`, then `handleSummary()`. k6 runs the call in a **freshly created VU**, which means the script's init context is executed again for that VU. The `data` argument is therefore the only channel carrying the run's aggregated figures into the function; values a module accumulated during the test are not visible there. Exporting the function takes over the end-of-test report. k6 no longer prints its own text block; it prints, writes or discards exactly what you hand back. ## The contract: a map of destination to content k6 expects an object of the form `{ key1: value1, key2: value2, ... }`, and it reads that object literally: - **Keys are destinations, not labels.** The key string decides where the bytes land — nothing else in the return value influences it. - **Values are the payload.** A `string` or an `ArrayBuffer`. k6 does **not** serialise objects for you; if you want JSON you call `JSON.stringify(data)` yourself. - **The map may hold as many entries as you like**, and k6 writes every one of them, so a single run can emit a console line, a JSON file and an XML file together. - **An entry of the wrong type fails that entry**, with `error handling summary object <key>: invalid type ..., it needs to be a string or ArrayBuffer`. - **A non-map return is rejected** with `handleSummary() should return a map with string keys`. - **Returning no file key saves no file.** k6 has no result file it writes on its own at the end of a run. ## Where each key goes | key in the returned object | what k6 does with the value | |---|---| | `stdout` | writes it to the process's standard output | | `stderr` | writes it to the process's standard error | | any other string, e.g. `junit.xml` | opens that path with create + write + truncate and writes the value | A path key may be relative or absolute; a relative one resolves against the working directory k6 was started in. k6 only opens the path — it does not build directories along the way, so a missing parent directory or an unwritable location surfaces as `could not open '<path>'`, grouped under `Could not save some summary information:`. ## Worked example: emitting a JUnit-shaped file ```javascript import { jUnit } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; export const options = { vus: 5, iterations: 10 }; export default function () {} export function handleSummary(data) { return { 'junit.xml': jUnit(data), stdout: 'summary written to junit.xml\n', }; } ``` The helper turns the summary object into an XML `<testsuites>` document; the **key** `junit.xml` is what decides that the document becomes a file with that name. There is no built-in XML writer and no `--out junit` target in k6 — the hook is the whole mechanism. ## The shape of the `data` argument `data` is the aggregated end-of-test object. Which schema you receive is a run-time choice: the `--new-machine-readable-summary` flag, or the `K6_NEW_MACHINE_READABLE_SUMMARY` environment variable, switches k6 to its published machine-readable summary schema for that argument. The **return contract is unaffected** either way — it is still a map of destination to string-or-ArrayBuffer. ## What k6 does not do for you - **It does not write a results file by default.** Nothing at the end of a k6 run leaves an artifact behind unless a key in this map asks for one (or the older `--summary-export=<file>` flag is set on the command line). - **It does not serialise.** An object handed back under a `.json` key is a type error, not a JSON file. - **It does not build directories.** Only the file itself is created, at the path exactly as you spelled it. - **It does not append or rotate.** Each run truncates whatever was at that path and writes over it. - **It does not validate the format against the key.** A key called `junit.xml` holding plain text is written happily — the key is a filename, not a declaration of content type. ## Mistakes that cost people a build 1. Returning the report **as a string** instead of a map. k6 rejects the value and you get no file. 2. Expecting k6 to **append** across runs. The path is opened truncating, so each run replaces the previous file. 3. Assuming a **results file exists anyway** and only the format needed customising. Without a path key, nothing is written. 4. Handing back a plain object for a `.json` key and expecting k6 to stringify it.

  • Can one `handleSummary()` call write a file and print to the console at the same time?
    Yes. The returned map may hold any number of entries and k6 processes all of them, so `{ stdout: line, 'junit.xml': xml }` prints to the terminal and writes the file in the same call.
  • What decides the schema of the `data` object k6 passes to `handleSummary()`?
    The `--new-machine-readable-summary` flag, or `K6_NEW_MACHINE_READABLE_SUMMARY`, switches the argument to k6's machine-readable summary schema. Without it you get the long-standing summary object. The return contract is the same either way.
  • Does k6 create the directory named in a summary file path?
    No. k6 opens the path with create-and-truncate only. A missing parent directory or an unwritable location fails that entry and k6 logs `could not open '<path>'`, while the run's exit code stays as it was.

The returned object is a distribution list rather than a letter. Each key is an address, and k6 delivers that entry's content to it — remove an address and nobody gets a copy.

saying these in an interview costs you the question

  • Thinks k6 writes a results file on its own without the hook
  • Returns a formatted report string instead of a map
  • Expects k6 to append to an existing summary file
  • Believes a --out junit target exists in k6
  • Assumes k6 JSON-serialises a returned object automatically