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

  1. Make the page.on('metric') handler synchronous and fast — collect into an array or push to a sync sink, no awaits
  2. Await every browser call that emits metrics (await page.goto(...), await page.close()) before returning from the default function
  3. Use page.waitForEvent(...) when you need to synchronize on an event instead of doing async work inside page.on
  4. 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

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


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)