{"record":{"id":"1e2d8ba3e9f540d7","repo":"grafana/k6","slug":"scrolling-element-into-view-w","errorCode":null,"errorMessage":"scrolling element into view: %w","messagePattern":"scrolling element into view: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/element_handle.go","lineNumber":1407,"sourceCode":"\t)\n\tdefer span.End()\n\n\tspan.SetAttributes(attribute.String(\"screenshot.path\", opts.Path))\n\n\ts := newScreenshotter(spanCtx, sp, h.logger)\n\tbuf, err := s.screenshotElement(h, opts)\n\tif err != nil {\n\t\treturn nil, spanRecordErrorf(span, \"taking screenshot of elementHandle: %w\", err)\n\t}\n\n\treturn buf, err\n}\n\n// ScrollIntoViewIfNeeded scrolls element into view if needed.\nfunc (h *ElementHandle) ScrollIntoViewIfNeeded(opts *ElementHandleBaseOptions) error {\n\terr := h.waitAndScrollIntoViewIfNeeded(h.ctx, opts.Force, opts.NoWaitAfter, opts.Timeout)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scrolling element into view: %w\", err)\n\t}\n\n\tapplySlowMo(h.ctx)\n\n\treturn nil\n}\n\n// SelectOption selects the options matching the given values.\nfunc (h *ElementHandle) SelectOption(values []any, opts *ElementHandleBaseOptions) ([]string, error) {\n\tselectOption := func(apiCtx context.Context, handle *ElementHandle) (any, error) {\n\t\treturn handle.selectOption(apiCtx, values)\n\t}\n\tselectOptionAction := h.newAction(\n\t\t[]string{}, selectOption, opts.Force, withRetry, opts.NoWaitAfter, opts.Timeout,\n\t)\n\tselectedOptions, err := call(h.ctx, selectOptionAction, opts.Timeout)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"selecting options: %w\", err)","sourceCodeStart":1389,"sourceCodeEnd":1425,"githubUrl":"https://github.com/grafana/k6/blob/01ffac6f245854c1b8adc6a69857c76a15f90022/internal/js/modules/k6/browser/common/element_handle.go#L1389-L1425","documentation":"Thrown by the k6 browser module's ElementHandle.scrollIntoViewIfNeeded(). It wraps the failure of the internal waitAndScrollIntoViewIfNeeded action, which first waits for the element to be in the 'visible' and 'stable' states (with retry), then evaluates element.scrollIntoViewIfNeeded(true) in the page. It fails when the element never becomes actionable before the timeout, is hidden or detached, or the in-page evaluation errors out.","triggerScenarios":"Calling elementHandle.scrollIntoViewIfNeeded() on an element that is display:none or visibility:hidden; on an element still animating so the 'stable' check never passes; on a handle whose node was removed by an SPA rerender; with the default timeout (30s) too short on slow CI machines.","commonSituations":"SPA frameworks (React/Vue) re-rendering and detaching the node mid-action; CSS transitions keeping the element 'unstable'; pages with lazy-rendered content below the fold; CI environments where CDP round-trips are slow and the default timeout expires.","solutions":["Wait for the element to be actionable first: await el.waitForElementState('visible', { timeout: '10s' }) or use a locator/page.waitForSelector before scrolling.","Re-query the handle after navigation or rerender: const el = await page.$(selector) again, since old handles go stale.","Pass a larger timeout: el.scrollIntoViewIfNeeded({ timeout: '60s' }) or raise the browser-level default via options timeouts in the k6 browser options.","Disable CSS animations/transitions in the page under test so the 'stable' check passes immediately.","Pass { force: true } to skip the actionability wait if you know the element exists but never reports stable."],"exampleFix":"// before\nconst el = await page.$('#footer');\nawait el.scrollIntoViewIfNeeded(); // fails: element hidden/unstable until late render\n\n// after\nconst el = await page.waitForSelector('#footer', { state: 'visible', timeout: '30s' });\nawait el.scrollIntoViewIfNeeded({ timeout: '30s' });","handlingStrategy":"try-catch","validationCode":"const visible = await el.isVisible().catch(() => false);\nif (visible) {\n  await el.scrollIntoViewIfNeeded({ timeout: '30s' });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await el.scrollIntoViewIfNeeded({ timeout: '30s' });\n} catch (e) {\n  if (String(e).includes('timed out') || String(e).includes('not attached')) {\n    el = await page.waitForSelector(sel, { state: 'visible', timeout: '30s' });\n    await el.scrollIntoViewIfNeeded({ timeout: '30s' });\n  } else { throw e; }\n}","preventionTips":["Always acquire handles via waitForSelector with a state, never bare page.$ right before an action.","Do not cache ElementHandles across clicks that trigger rerenders or navigation.","Set an explicit timeout on every action instead of relying on the 30s default in CI."],"tags":["k6","browser","element-handle","actionability","timeout"],"backgroundTag":"browser-action-timeout","analyzedSha":"01ffac6f245854c1b8adc6a69857c76a15f90022","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}