{"record":{"id":"6c79cb59d9278f07","repo":"grafana/k6","slug":"waiting-for-navigation-w-6c79cb","errorCode":null,"errorMessage":"waiting for navigation: %w","messagePattern":"waiting for navigation: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/frame.go","lineNumber":2186,"sourceCode":"\t\treturn nil, nil //nolint:nilnil\n\t}\n\n\treturn resp, nil\n}\n\nfunc (f *Frame) handleWaitForNavigationTimeoutErrorFn(opts *FrameWaitForNavigationOptions) func(err error) error {\n\treturn func(err error) error {\n\t\tf.log.Debugf(\"Frame:WaitForNavigation\",\n\t\t\t\"fid:%v furl:%s timeoutCtx done: %v\", f.ID(), f.URL(), err)\n\t\tif err != nil {\n\t\t\te := &k6ext.UserFriendlyError{\n\t\t\t\tErr:     err,\n\t\t\t\tTimeout: opts.Timeout,\n\t\t\t}\n\t\t\tif opts.URL != \"\" {\n\t\t\t\treturn fmt.Errorf(\"waiting for navigation to URL matching %q: %w\", opts.URL, e)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"waiting for navigation: %w\", e)\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// WaitForSelector waits for the given selector to match the waiting criteria.\nfunc (f *Frame) WaitForSelector(selector string, popts *FrameWaitForSelectorOptions) (*ElementHandle, error) {\n\thandle, err := f.waitForSelectorRetry(selector, popts, maxRetry)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"waiting for selector %q: %w\", selector, err)\n\t}\n\n\treturn handle, nil\n}\n\n// WaitForTimeout waits the specified amount of milliseconds.\nfunc (f *Frame) WaitForTimeout(timeout int64) {","sourceCodeStart":2168,"sourceCodeEnd":2204,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/frame.go#L2168-L2204","documentation":"Same handler as the URL variant, but emitted when opts.URL is empty: Frame.WaitForNavigation was waiting for ANY navigation and the wait ended in error (usually timeout, wrapped in k6ext.UserFriendlyError with opts.Timeout) before a NavigationEvent arrived. Without a pattern there is nothing to match, so the message carries only the friendly timeout.","triggerScenarios":"page.waitForNavigation() with no url option where the accompanying action never triggers a navigation; the navigation occurs but the frame is detached or the page closes first; timeout too short for the new document's lifecycle.","commonSituations":"Clicking a button that only changes route via SPA history (no document navigation event in some setups); calling waitForNavigation after the navigation already happened; page.close() during the wait.","solutions":["Register the wait before the action and await both: const nav = page.waitForNavigation(); await el.click(); await nav;","If it is an SPA route change, use page.waitForURL instead — a history pushState may not emit a navigation event","Increase the timeout option"],"exampleFix":"// before\nawait page.click('#next');\nawait page.waitForNavigation(); // registered too late, or SPA route change emits nothing\n\n// after\nconst nav = page.waitForNavigation({ timeout: 60_000 });\nawait page.click('#next');\nawait nav;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isNavTimeoutNoURL(e) {\n  return e instanceof Error && /waiting for navigation/.test(e.message);\n}","tryCatchPattern":"try {\n  const nav = page.waitForNavigation({ timeout: 60_000 });\n  await el.click();\n  await nav;\n} catch (e) {\n  if (isNavTimeoutNoURL(e)) { await page.waitForURL('**/next**'); } // SPA fallback\n  else throw e;\n}","preventionTips":["For SPA history routing use waitForURL, not waitForNavigation","Register the wait before the triggering action"],"tags":["browser","wait-for-navigation","timeout","spa"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}