skip to content

In k6, what does `exec.test.options` give you that the exported `options` object does not?

level: middleimportance: nice to knowfreq 38%

answer

  1. declared versus effective
  2. consolidated and derived
  3. shortcut keys are gone from it
  4. throws in the init context

basics

~20 s

exec.test.options, from the k6/execution module, is the consolidated and derived configuration the run is actually using - command-line overrides included - as a read-only object. The exported options object is only what the script declared.

solid answer

~40 s

The object you write as `export const options = { ... }` is a declaration; `exec.test.options`, from the `k6/execution` module, is what the run ended up with. It is **consolidated**, so anything supplied on the `k6 run` command line is already folded in, and **derived**, so the shortcut keys have been turned into a `scenarios` map - `vus`, `duration`, `stages` and `iterations` are not present on it at all, and you read `exec.test.options.scenarios.default.stages[0].target` instead. Options nobody set read back as `null` rather than being absent. The object is read-only, and it is not available in the init context: reading it at the module's top level throws, because there is no consolidated configuration yet.

code

javascript · 12 lines
javascript
import exec from 'k6/execution';

export const options = {
  vus: 50,
  duration: '10m',
};

export default function () {
  if (exec.vu.idInTest === 1 && exec.vu.iterationInInstance === 0) {
    console.log(JSON.stringify(exec.test.options.scenarios.default));
  }
}

go deeper

for a junior

Know the module name, k6/execution, and that exec.test.options shows the settings the run is using rather than the ones the file declares.

for a middle

Explain consolidated versus derived: command-line overrides are folded in, and the shortcut keys have become a scenarios entry named default, so those four are no longer on the object.

for a senior

Use it to make a run self-describing - log the derived scenario so the output records the configuration k6 executed - and know it throws in init because there is no consolidated configuration yet.

for a principal

Decide whether scripts should read their own configuration at all. It removes a second source of truth for the run's size, but a script that branches on its configuration is harder to reason about than one that does not.

## Two objects, easy to confuse A k6 script deals with two configuration objects that look alike and mean different things. - The one you write, `export const options = { ... }`, is a **declaration**. It is a literal in your source file and nothing rewrites it at run time. - The one you read, `exec.test.options` from the `k6/execution` module, is the **effective configuration** - what k6 concluded, after reading everything it was given, that this run should be. If nothing overrides the script, the two look similar. The moment anything does, they diverge, and only the second one describes what actually happened. ## What "consolidated" means `exec.test.options` reflects the values the run is genuinely using, including anything supplied on the `k6 run` command line. Run a script that declares `vus: 50` as `k6 run --vus 5 script.js` and the exported literal still says 50 - it is a literal in a file - while `exec.test.options` reports the configuration built from 5. That makes it the only honest answer, from inside the script, to "what was this run configured with?". Options nobody set anywhere read back as `null` rather than being missing, so `exec.test.options.paused` is `null` on a run that never touched `paused`. Three properties follow from it being the effective configuration rather than your literal: - A command-line override **never edits your source**, so reading the exported object back would tell you nothing; the two are separate values that happen to agree most of the time. - The read is a **run-time** operation. The values it reports were settled after everything k6 was given had already been read, which is precisely why it cannot be answered earlier. - The object is **read-only**. It reports the configuration; it does not let a script change one. Assigning into it will not resize a running test, and a script that wants a different size has to be launched differently. ## What "derived" means k6 does not keep your shorthand. Before the run starts it turns `vus`, `duration`, `stages` and `iterations` into a single entry in the `scenarios` map, keyed `default`. `exec.test.options` shows the result of that conversion, and those four shortcut keys are **removed from it**: ```javascript import exec from 'k6/execution'; export const options = { stages: [ { duration: '5s', target: 100 }, { duration: '5s', target: 50 }, ], }; export default function () { console.log(exec.test.options.stages); // undefined console.log(exec.test.options.scenarios.default.stages[0].target); // 100 } ``` This trips people up constantly. Reaching for `exec.test.options.vus` on a script that declared `vus` returns `undefined`, not the number - the value is there, one level down, inside the derived `default` scenario. ## Where you can read it, and where you cannot | | exported `options` | `exec.test.options` | |---|---|---| | what it is | what the script declares | what the run is using | | includes command-line overrides | no | yes | | shortcut keys present | yes, as you wrote them | no, derived into `scenarios` | | options nobody set | absent | `null` | | writable | yes, it is your object | no, read-only | | readable in the init context | it *is* the init-time value | no, it throws there | The init-context restriction is the practical one. Reading `exec.test.options` at the module's top level throws, with a message saying that getting test options in the init context is not supported - reasonably, because init runs before there is a consolidated configuration to report. Read it from inside the default function instead, or from any function the running test calls. ## What it is actually good for Three uses come up in real work: 1. **Recording the effective configuration in the run's own output.** One `console.log(JSON.stringify(exec.test.options.scenarios))` on the first iteration puts the workload k6 really executed into the same log stream as the results, so the artifact describes itself instead of depending on whoever remembers the command line. 2. **Branching on the configuration the run was given.** A script can read the derived scenario and adjust what it does - a smaller data set when the run is small - without a second source of truth for the size. 3. **Catching a setting that never took.** If something you believe you configured comes back `null`, it did not reach k6. That is a faster diagnosis than inferring it from the results. The habit worth building is simple: when a k6 run behaves as though it were configured differently from the script, do not re-read the script - print `exec.test.options` and look at what the run was given. The script tells you what you asked for, and in a pipeline where flags are supplied by a job definition somebody else maintains, that is rarely the same thing as what happened.

  • Why does `exec.test.options.vus` come back undefined on a k6 script that declared `vus`?
    Because `exec.test.options` is the derived view. k6 converts `vus`, `duration`, `stages` and `iterations` into one entry in the `scenarios` map before the run starts, and removes those four keys from the object it exposes. The value is still there, under `exec.test.options.scenarios.default`.
  • What happens if you read `exec.test.options` at the top level of a k6 script?
    It throws, with a message saying that getting test options in the init context is not supported. Init runs before k6 has a consolidated configuration to report, so there is nothing to return. Move the read into the default function, or into any function the running test calls.

The exported options object is the order you wrote down; exec.test.options is the receipt printed after the substitutions. Re-reading the order tells you what you asked for, not what you got.

saying these in an interview costs you the question

  • Expects exec.test.options.vus to return the declared number
  • Reads exec.test.options at the top level of the script
  • Thinks a CLI override rewrites the exported options literal
  • Assumes exec.test.options can be written to reconfigure a run
  • Treats an option nobody set as absent rather than null