{"record":{"id":"7892133519ce91bc","repo":"grafana/k6","slug":"iteration-ended-before-page-on-handler-completed-e","errorCode":null,"errorMessage":"iteration ended before page.on handler completed executing","messagePattern":"iteration ended before page\\.on handler completed executing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/browser/page_mapping.go","lineNumber":808,"sourceCode":"\t\tpageEvent, ok := pageEvents[eventName]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unknown page on event: %q\", eventName)\n\t\t}\n\n\t\tctx := vu.Context()\n\t\ttq := vu.get(ctx, p.TargetID())\n\n\t\treturn p.On(eventName, func(event common.PageEvent) error {\n\t\t\twait := queueTask(ctx, tq, func() (sobek.Value, error) {\n\t\t\t\t_, err := handle(sobek.Undefined(), vu.Runtime().ToValue(pageEvent.mapp(vu, event)))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"executing page.on('%s') handler: %w\", eventName, err)\n\t\t\t\t}\n\t\t\t\treturn nil, nil\n\t\t\t})\n\t\t\tif pageEvent.wait {\n\t\t\t\tif _, err := wait(); errors.Is(err, context.Canceled) {\n\t\t\t\t\treturn errors.New(\"iteration ended before page.on handler completed executing\")\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\nfunc parseWaitForFunctionArgs(\n\tctx context.Context, timeout time.Duration, pageFunc, opts sobek.Value, gargs ...sobek.Value,\n) (string, *common.FrameWaitForFunctionOptions, []any, error) {\n\tpopts := common.NewFrameWaitForFunctionOptions(timeout)\n\terr := popts.Parse(ctx, opts)\n\tif err != nil {\n\t\treturn \"\", nil, nil, fmt.Errorf(\"parsing waitForFunction options: %w\", err)\n\t}\n\n\tjs := pageFunc.ToString().String()\n\t_, isCallable := sobek.AssertFunction(pageFunc)","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/browser/page_mapping.go#L790-L826","documentation":"k6 runs page.on(...) handler bodies on the iteration's task queue. Only the 'metric' event is registered with wait: true (page_mapping.go:784), meaning k6 blocks until the queued handler finishes. If the VU iteration's context is canceled first — duration elapsed, scenario ended — wait() returns context.Canceled and k6 converts it to this error so the incomplete handler is reported instead of silently dropped.","triggerScenarios":"A page.on('metric', ...) handler whose queued task is still pending when the iteration ends: the metric event fires during teardown, or the handler does slow work (awaits, processing many entries) while the scenario's duration/iterations run out.","commonSituations":"Fixed-duration scenarios where a navigation or final request races shutdown; heavy aggregation inside the metric handler; handlers still registered while page.close()/browser.close() runs; tight iteration budgets in constant-arrival-rate scenarios.","solutions":["Keep page.on('metric') handlers short and synchronous — collect data, don't await anything","Give the scenario headroom (slightly longer duration or a small end-of-test wait) so the last metric events flush","Remove listeners before teardown if you no longer need them, so late events are not queued at all","If the race is benign at shutdown, treat this specific message as non-fatal in your results analysis"],"exampleFix":"// before\npage.on('metric', async (m) => {\n  await sendToServer(m); // slow await inside handler\n});\n\n// after\nconst buffer = [];\npage.on('metric', (m) => {\n  buffer.push(m); // synchronous; flushed later\n});\n// flush buffer at a controlled point before the iteration ends","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"page.on('metric', (m) => {\n  try {\n    collect(m); // keep it synchronous and fast\n  } catch (e) {\n    // handler errors surface as \"executing page.on('metric') handler: ...\"\n    console.error('metric handler failed:', e.message);\n  }\n});","preventionTips":["Only the 'metric' event blocks on handler completion — keep those handlers synchronous (no sleep, no awaits, no network)","Design metric handlers to push into a buffer and flush at a controlled point before the iteration ends","Add duration headroom to browser scenarios so final metric events are not cut off by teardown","Unregister heavy listeners before page.close()/browser.close()"],"tags":["browser","page-on","metric","events","lifecycle","async"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}