skip to content

How do you make a k6 scenario run an exported function other than the default one?

level: middleimportance: should knowfreq 56%

answer

  1. a name, not a reference
  2. string value on the scenario
  3. defaults to 'default'
  4. validated before the run starts

basics

~20 s

Set that k6 scenario's exec key to the name of the exported function, as a string: exec: 'addToCart'. It defaults to 'default', and k6 rejects the configuration before the run starts if no export by that name exists.

solid answer

~40 s

Every k6 scenario takes a generic `exec` property whose value is the **name** of an exported function, written as a string -- `exec: 'addToCart'`, not `exec: addToCart`. It defaults to `"default"`, which is why a script with one default export never has to set it. k6 collects every exported value that is a function when it loads the script (skipping `options`), and validates each scenario's `exec` against that set before the run begins; an unresolved name fails the configuration with `executor <scenario>: function '<name>' not found in exports` and nothing runs. The named function is called exactly like `default` would be, including receiving whatever `setup()` returned, so in k6 v2 a script can be built entirely from named exports with no default export at all.

code

javascript · 31 lines
javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE = 'https://quickpizza.grafana.com';
const headers = {
  'Content-Type': 'application/json',
  Authorization: 'Token abcdef0123456789',
};

export const options = {
  scenarios: {
    browsers: { executor: 'constant-vus', vus: 5, duration: '30s', exec: 'browse' },
    buyers: { executor: 'constant-vus', vus: 1, duration: '30s', exec: 'addToCart' },
  },
};

export function browse() {
  const res = http.get(`${BASE}/`);
  check(res, { 'menu page loaded': (r) => r.status === 200 });
  sleep(1);
}

export function addToCart() {
  const res = http.post(
    `${BASE}/api/pizza`,
    JSON.stringify({ maxCaloriesPerSlice: 1000, mustBeVegetarian: false }),
    { headers }
  );
  check(res, { 'pizza added to cart': (r) => r.status === 200 });
  sleep(1);
}

go deeper

for a junior

Know that a scenario can run a function other than the default one, and that you point at it by writing its name in quotes under the exec key.

for a middle

Explain that exec is a name looked up among the script's exported functions, that it defaults to default, and that k6 validates it before the run rather than at the first iteration.

for a senior

Show judgment about when splitting a journey across exported functions is right, and be ready to read the not-found-in-exports error back to a colleague and say what caused it.

for a principal

Weigh one script with several exec targets against several scripts. The single-file shape keeps shared helpers honest but couples release cycles; decide which cost your organisation would rather carry.

## The `exec` key Every k6 scenario has a generic `exec` property. Its value is a **string**: the name of a function the script exports. Its default is `"default"`, which is why a script with a single default export and no `exec` anywhere works without you ever writing the key. ```javascript export const options = { scenarios: { buyers: { executor: 'constant-vus', vus: 5, duration: '1m', exec: 'addToCart' }, }, }; export function addToCart() { // this is the VU code for the `buyers` scenario } ``` Two things about that value are worth stating precisely, because both are commonly got wrong: - it is a **name**, not a reference -- you write `exec: 'addToCart'`, in quotes, not `exec: addToCart`; - it names an **export**, not a file -- k6 looks the name up in the exports of the script it is already running, and never loads a second file for it. ## What counts as an exported function When k6 loads the script it walks the module's exported names and keeps every one whose value is a function. The `options` export is deliberately excluded, since it is configuration. Everything else -- `default`, `setup`, `teardown`, `handleSummary`, and any function you export under a name of your own -- ends up in that set and is therefore a legal `exec` target. If the set is empty, k6 stops with `no exported functions in script` before any traffic is generated. Because the lookup runs against the **main module's own exports**, a function defined in an imported file is not reachable by name unless the main script re-exports it. Writing `export { addToCart } from './cart.js';` puts the name into the main module's export list and makes it a valid `exec` target; importing it without re-exporting does not. ## When the name does not resolve k6 validates every scenario's `exec` against that set **before the run starts**, not lazily at the first iteration. A name that does not resolve produces a configuration error naming both the scenario and the missing function: ``` executor buyers: function 'addToCart' not found in exports ``` Nothing runs. This is a deliberate design choice: a typo in `exec` is caught in the first second rather than after a ramp has already been paid for. Note that it fires for a misspelling, for a function you forgot to `export`, and for an export that exists but is not callable -- a string or an object under that name is not a function, so it is not in the set. | What you write | What k6 calls | |---|---| | no `scenarios` block at all | the export named `default` | | a scenario with `exec` omitted | the export named `default` | | `exec: 'addToCart'` | the export named `addToCart` | | `exec: 'addToCart'` with no such export | nothing -- the run is rejected before it starts | ## Splitting a journey across two functions The key earns its keep when one script has to drive two different behaviours at once. Suppose a browse-then-add-to-cart journey should not run as a single mix: you want many users browsing and only a few adding to the cart. Export two functions and give each scenario its own `exec`: ```javascript export const options = { scenarios: { browsers: { executor: 'constant-vus', vus: 10, duration: '1m', exec: 'browse' }, buyers: { executor: 'constant-vus', vus: 2, duration: '1m', exec: 'addToCart' }, }, }; export function browse() { /* ... */ } export function addToCart() { /* ... */ } ``` Each scenario now runs iterations of its own function, and each iteration still means exactly what it meant before: one complete call of the named function. ## Details that follow from it 1. **Several scenarios may share one `exec`.** It is only a name lookup, so pointing two scenarios at the same exported function is legal and needs no change to the function itself. 2. **The named function gets the same argument as `default`.** k6 calls whichever function the scenario names with the value `setup()` returned, so `export function addToCart(data) { ... }` receives exactly what `export default function (data) { ... }` would. 3. **A default export is not required.** If every scenario names a function explicitly, the script needs no `default` export at all. 4. **The word "default" appears twice, meaning different things.** The implicit scenario k6 derives when you use plain root options is also called `default`. The scenario name and the function name are independent; one being `default` does not force the other to be.

  • Can two k6 scenarios point their `exec` at the same function?
    Yes. `exec` is only a name lookup, so several scenarios may name the same exported function. Each one schedules its own iterations of it and the function itself needs no change; nothing about the code has to know which scenario invoked it.
  • Does a function named by `exec` receive the same argument as `default`?
    Yes. k6 calls whichever exported function the scenario names with the value `setup()` returned, exactly as it calls `default`. So `export function addToCart(data) { ... }` gets the same `data`, and `undefined` when the script has no `setup()`.
  • What exactly does k6 do when `exec` names a function that is not exported?
    It fails configuration validation before any virtual user starts, with `executor <scenario>: function '<name>' not found in exports`. The check happens up front rather than at the first iteration, so a typo costs a second rather than a whole ramp.

saying these in an interview costs you the question

  • Writes exec as a bare identifier instead of a quoted string
  • Thinks exec names a second script file to load
  • Expects k6 to fall back to the default export when exec is wrong
  • Believes every k6 script must have a default export
  • Confuses the scenario named default with the function named default