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 pattern

View on GitHub (pinned to 93accf6570)

Solutions

  1. Prefer 'domcontentloaded' or 'load' instead of 'networkidle' for pages with live connections
  2. Increase the timeout: page.waitForLoadState('load', { timeout: 60000 }) or the browser-level default timeout
  3. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/609c22f3693715da. Report an issue: GitHub.