grafana/k6 · error

navigating %s to history entry %d: %w

Error message

navigating %s to history entry %d: %w

What it means

GoBackForward polls the page URL after navigating to a history entry (a 50ms ticker) because back/forward-cache restorations do not re-fire lifecycle events. If the wait is aborted — timeout exceeded, page context canceled, or the page's own context done — the error is wrapped here; a DeadlineExceeded is first converted to a k6ext.UserFriendlyError carrying the configured timeout.

Source

Thrown at internal/js/modules/k6/browser/common/page.go:1659

	targetURL := entries[targetIndex].URL

	timeoutCtx, timeoutCancelFn := context.WithTimeout(p.ctx, opts.Timeout)
	defer timeoutCancelFn()

	navAction := page.NavigateToHistoryEntry(historyEntryID)
	if err := navAction.Do(cdp.WithExecutor(p.ctx, p.session)); err != nil {
		return nil, fmt.Errorf("navigating to history entry %d: %w", historyEntryID, err)
	}

	wrapTimeoutError := func(err error) error {
		if errors.Is(err, context.DeadlineExceeded) {
			err = &k6ext.UserFriendlyError{
				Err:     err,
				Timeout: opts.Timeout,
			}
		}
		p.logger.Debugf("Page:GoBackForward", "timeoutCtx done: %v", err)
		return fmt.Errorf("navigating %s to history entry %d: %w", direction, historyEntryID, err)
	}

	// Poll for URL change, don't rely on lifecycle events since bfcache
	// restorations don't re-fire them.
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-p.ctx.Done():
			return nil, ContextErr(p.ctx)
		case <-timeoutCtx.Done():
			return nil, wrapTimeoutError(ContextErr(timeoutCtx))
		case <-ticker.C:
			mainFrame := p.frameManager.MainFrame()
			if mainFrame == nil {
				continue
			}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Increase the timeout option: page.goBack({ timeout: 30000 })
  2. Verify there is actually a different URL to go back to (check history via the response of goBack, which is null when there is none)
  3. Avoid goBack when the previous entry has the same URL — the URL-change poll cannot distinguish it from failure
  4. Close or abort-check the page before navigating if teardown races it

Example fix

// before
const resp = await page.goBack({ timeout: 1000 }); // too short under load

// after
const resp = await page.goBack({ timeout: 30000 });
Defensive patterns

Strategy: retry

Validate before calling

const historyResp = await page.goBack({ timeout: 30000 });
if (historyResp === null) console.warn('no history entry to navigate to');

Try / catch

try {
  const res = await page.goBack({ timeout: 30000 });
} catch (e) {
  if (/navigating (back|forward) to history entry/.test(e.message) && /timed out|timeout/i.test(e.message)) {
    // URL never changed: same-URL entry or slow page — verify flow instead of retrying blindly
  } else throw e;
}

Prevention

When it happens

Trigger: page.goBack({timeout: N}) or goForward() where the URL never changes (history entry has the same URL, bfcache restore keeps the URL, or a hash-only change), so the poll runs until the timeout. Also fires when the page closes or the script context is canceled mid-poll.

Common situations: Navigating back to a same-URL entry; short timeout option (the default may be too small for slow pages under load); calling goBack() with no history entry change expected; k6 iteration aborting during the wait.

Related errors


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