grafana/k6 · error
iteration ended before page.on handler completed executing
Error message
iteration ended before page.on handler completed executing
What it means
k6 wraps every page.on(handler) callback in the iteration's task queue; for events registered with wait=true — which in this mapping is only the 'metric' event (page_mapping.go:784) — k6 blocks until the queued handler finishes. If the VU iteration context is canceled first (default function returned, scenario/duration ended, test interrupted), the wait returns context.Canceled and k6 reports this error (page_mapping.go:808). It means your page.on handler did not complete within the iteration's lifetime.
Solutions
- Make the page.on('metric') handler synchronous and fast — collect into an array or push to a sync sink, no awaits
- Await every browser call that emits metrics (await page.goto(...), await page.close()) before returning from the default function
- Use page.waitForEvent(...) when you need to synchronize on an event instead of doing async work inside page.on
- Upgrade k6 — task-queue draining around page.on has been improved across releases; retest on the latest version
Example fix
// before
page.on('metric', async (m) => { await pushToInflux(m); }); // still pending at iteration end
// after
const buffered = [];
page.on('metric', (m) => { buffered.push(m); }); // synchronous; flushed in teardown Defensive patterns
Strategy: validation
Validate before calling
// structure page.on handlers so they cannot outlive the iteration:
const buffered = [];
page.on('metric', (m) => {
buffered.push(m); // synchronous only — no awaits, no fire-and-forget I/O
});
// flush `buffered` in teardown, after all browser work completed Prevention
- Keep page.on('metric') handlers synchronous; never await inside them
- Await every browser call that can emit metrics before returning from the default function
- Use page.waitForEvent when you need to synchronize on an event instead of async work in page.on
- Avoid single-iteration scenarios that end immediately after browser activity
When it happens
Trigger: Registering page.on('metric', handler) where the handler is queued behind other tasks or performs async work, then ending the iteration before it drains: returning from the default export right after browser work, an async handler (or one that offloads data), very short scenarios (--iterations 1), or aborting the run.
Common situations: Metric-export handlers that fire-and-forget to an external sink; teardown-time metric events arriving after the last action; CI jobs that cancel k6 mid-run; teams migrating from console.log-style handlers to async exporters.
Related errors
- 99
- adding init script to browser context
- already belongs to the same execution context
- an error occurred communicating with k6 Cloud
- an error occurred communicating with the provisioning API
AI-assisted analysis of grafana/k6@8d06114777 (2026-08-18).
Data as JSON: /api/errors/7892133519ce91bc.
Report an issue: GitHub.
Appendix: source
Thrown at internal/js/modules/k6/browser/browser/page_mapping.go:809
pageEvent, ok := pageEvents[eventName]
if !ok {
return fmt.Errorf("unknown page on event: %q", eventName)
}
ctx := vu.Context()
tq := vu.get(ctx, p.TargetID())
return p.On(eventName, func(event common.PageEvent) error {
wait := queueTask(ctx, tq, func() (sobek.Value, error) {
_, err := handle(sobek.Undefined(), vu.Runtime().ToValue(pageEvent.mapp(vu, event)))
if err != nil {
return nil, fmt.Errorf("executing page.on('%s') handler: %w", eventName, err)
}
return nil, nil
})
if pageEvent.wait {
if _, err := wait(); errors.Is(err, context.Canceled) {
return errors.New("iteration ended before page.on handler completed executing")
}
}
return nil
})
}
}
func parseWaitForFunctionArgs(
rt *sobek.Runtime, timeout time.Duration, pageFunc, opts sobek.Value, gargs ...sobek.Value,
) (string, *common.FrameWaitForFunctionOptions, []any, error) {
popts, err := parseFrameWaitForFunctionOptions(rt, opts, timeout)
if err != nil {
return "", nil, nil, fmt.Errorf("parsing waitForFunction options: %w", err)
}
js := pageFunc.ToString().String()
_, isCallable := sobek.AssertFunction(pageFunc)
if !isCallable {View on GitHub (pinned to 8d06114777)