{"record":{"id":"c5f29661924745ab","repo":"grafana/k6","slug":"unpacking-scroll-offset-w","errorCode":null,"errorMessage":"unpacking scroll offset: %w","messagePattern":"unpacking scroll offset: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/screenshotter.go","lineNumber":329,"sourceCode":"\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"node is either not visible or not an HTMLElement: %w\", err)\n\t\t}\n\t\tif bbox.Width <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"node has 0 width\")\n\t\t}\n\t\tif bbox.Height <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"node has 0 height\")\n\t\t}\n\t}\n\n\tscrollOffset, err := h.Evaluate(`() => { return {x: window.scrollX, y: window.scrollY};}`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"evaluating scroll offset: %w\", err)\n\t}\n\n\tvar returnVal Position\n\tif err := convert(scrollOffset, &returnVal); err != nil {\n\t\treturn nil, fmt.Errorf(\"unpacking scroll offset: %w\", err)\n\t}\n\n\tdocumentRect := bbox\n\tdocumentRect.X += returnVal.X\n\tdocumentRect.Y += returnVal.Y\n\n\tbuf, err := s.screenshot(\n\t\th.frame.page.session,\n\t\tdocumentRect.enclosingIntRect(),\n\t\tnil, // viewportRect\n\t\tformat,\n\t\topts.OmitBackground,\n\t\topts.Quality,\n\t\topts.Path,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/screenshotter.go#L311-L347","documentation":"The final conversion in elementHandle.screenshot(): the evaluated scroll offset {x: window.scrollX, y: window.scrollY} is converted into a Position struct via the JSON round-trip helper, and that conversion failed — the evaluated value did not deserialize into {x,y} numeric fields.","triggerScenarios":"The scrollOffset evaluate succeeded but returned a non-numeric/oddly-shaped value (a string, undefined, or an object whose x/y are not numbers — e.g. a page script overriding window.scrollX/scrollY with non-numeric values).","commonSituations":"Anti-automation or analytics scripts monkey-patching window.scrollX/scrollY; unusual document types; extremely rare in normal pages since these are built-in number properties.","solutions":["Screenshot with page.screenshot({clip: boundingBox}) instead of element screenshot to bypass the scroll-offset math.","If you control the page, remove scripts that override window.scrollX/scrollY.","Scroll to top first (page.evaluate('window.scrollTo(0,0)')) so offsets are plain zeros.","Upgrade k6 and report a grafana/k6 issue if it reproduces on a vanilla page."],"exampleFix":"// before\nawait page.$('#el').then(el => el.screenshot({ path: 'e.png' })); // scrollX patched to a string\n\n// after\nawait page.evaluate('window.scrollTo(0, 0)');\nconst bb = await page.locator('#el').boundingBox();\nawait page.screenshot({ path: 'e.png', clip: bb });","handlingStrategy":"try-catch","validationCode":"await page.evaluate('window.scrollTo(0, 0)'); // make scroll offsets plain zeros before element capture","typeGuard":"function isFinitePosition(v) {\n  return v != null && typeof v === 'object'\n    && Number.isFinite(Number(v.x)) && Number.isFinite(Number(v.y));\n}","tryCatchPattern":"try { await el.screenshot({ path: 'e.png' }); }\ncatch (e) {\n  if (String(e).includes('unpacking scroll offset')) {\n    const bb = await page.locator(sel).boundingBox();\n    await page.screenshot({ path: 'e.png', clip: bb }); // bypass scroll-offset math\n  } else throw e;\n}","preventionTips":["Scroll to top before element screenshots.","Avoid pages that monkey-patch window.scrollX/scrollY.","Fall back to page.screenshot({clip}) when element capture keeps failing."],"tags":["k6","browser","screenshot","element-handle","json-conversion","scroll"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}