grafana/k6 · error
waiting for load state %q: %w
Error message
waiting for load state %q: %w
What it means
Thrown by Frame.WaitForLoadState when the timeout context expires before the requested lifecycle event (EventFrameAddLifecycle matching your state) is observed, unless it already fired (hasLifecycleEventFired short-circuit). The %w wraps ContextErr(timeoutCtx), typically context.DeadlineExceeded. It means the page genuinely never signalled that lifecycle state within the wait timeout.
Source
Thrown at internal/js/modules/k6/browser/common/frame.go:2063
timeoutCtx,
f,
[]string{EventFrameAddLifecycle},
func(data any) bool {
if le, ok := data.(FrameLifecycleEvent); ok {
return le.Event == waitUntil
}
return false
})
defer lifecycleEventCancel()
if f.hasLifecycleEventFired(waitUntil) {
return nil
}
select {
case <-lifecycleEvent:
case <-timeoutCtx.Done():
return fmt.Errorf("waiting for load state %q: %w", state, ContextErr(timeoutCtx))
}
return nil
}
// WaitForNavigation waits for the given navigation lifecycle event to happen.
// RegExMatcher should be non-nil to be able to test against a URL pattern in the options.
//
//nolint:funlen
func (f *Frame) WaitForNavigation(opts *FrameWaitForNavigationOptions, rm RegExMatcher) (*Response, error) {
f.log.Debugf("Frame:WaitForNavigation",
"fid:%s furl:%s url:%s", f.ID(), f.URL(), opts.URL)
defer f.log.Debugf("Frame:WaitForNavigation:return",
"fid:%s furl:%s", f.ID(), f.URL())
timeoutCtx, timeoutCancel := context.WithTimeout(f.ctx, opts.Timeout)
// Create URL matcher based on the patternView on GitHub (pinned to 93accf6570)
Solutions
- Prefer 'domcontentloaded' or 'load' instead of 'networkidle' for pages with live connections
- Increase the timeout: page.waitForLoadState('load', { timeout: 60000 }) or the browser-level default timeout
- If 'networkidle' is required, block the offending hosts (browser.setBlockedURLs / context options) so the network can go idle
Example fix
// before
page.waitForLoadState('networkidle'); // times out: app holds a websocket open
// after
page.waitForLoadState('load', { timeout: 60_000 }); Defensive patterns
Strategy: retry
Type guard
function isLoadStateTimeout(e) {
return e instanceof Error && /waiting for load state/.test(e.message);
} Try / catch
try {
await page.waitForLoadState('load', { timeout: 60_000 });
} catch (e) {
if (isLoadStateTimeout(e)) {
await page.waitForLoadState('domcontentloaded'); // degrade gracefully
} else throw e;
} Prevention
- Avoid 'networkidle' on streaming/websocket apps
- Set explicit generous timeouts for slow environments
- Block hosts that keep the network busy if idle detection is required
When it happens
Trigger: Waiting for 'networkidle' on a page with persistent connections (WebSockets, SSE, analytics heartbeats) that keep >2 connections open, so networkidle is never emitted; waiting for 'load' on a page with a hanging subresource; a timeout option smaller than the page's real load time.
Common situations: SPAs and chat/streaming apps that never reach networkidle; throttled CI networks making load exceed the default 30s; waiting after navigation already completed but for the wrong state.
Related errors
- browser.on promise rejected: %w
- unmarshaling wait for load state %q: %w
- iteration ended before page.on handler completed executing
- cannot close context as none is active in browser
- ErrFatal
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/609c22f3693715da.
Report an issue: GitHub.