{"record":{"id":"f2d169c55b36bf6e","repo":"CloakHQ/CloakBrowser","slug":"element-lost-after-scrolling-into-view-f2d169","errorCode":null,"errorMessage":"Element lost after scrolling into view","messagePattern":"Element lost after scrolling into view","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"js/src/human/scroll.ts","lineNumber":179,"sourceCode":"  }\n\n  if (Math.random() < cfg.scroll_overshoot_chance) {\n    const overshootPx = Math.round(randRange(cfg.scroll_overshoot_px)) * direction;\n    await smoothWheel(raw, overshootPx, cfg);\n    await sleep(randRange(cfg.scroll_settle_delay));\n\n    const corrections = randIntRange([1, 2]);\n    for (let c = 0; c < corrections; c++) {\n      const corrDelta = Math.round(rand(40, 80)) * -direction;\n      await smoothWheel(raw, corrDelta, cfg);\n      await sleep(rand(100, 250));\n    }\n  }\n\n  await sleep(randRange(cfg.scroll_settle_delay));\n\n  const finalBox = await getBox();\n  if (!finalBox) throw new Error('Element lost after scrolling into view');\n  return { box: finalBox, cursorX, cursorY, didScroll: true };\n}\n\nexport async function scrollToElement(\n  page: Page,\n  raw: RawMouse,\n  selector: string,\n  cursorX: number,\n  cursorY: number,\n  cfg: HumanConfig,\n  timeout: number = 30000,\n): Promise<{ box: SelectorBounds; cursorX: number; cursorY: number; didScroll: boolean }> {\n  return humanScrollIntoView(\n    page,\n    raw,\n    () => getElementBox(page, selector, timeout),\n    cursorX,\n    cursorY,","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/CloakHQ/CloakBrowser/blob/d6bad5de261bedf025280ace1d14e800aee13923/js/src/human/scroll.ts#L161-L197","documentation":"humanScrollIntoView performed its smooth scrolling, waited the configured scroll_settle_delay, then re-measured the element with a final getBox(). If that final lookup returns null, the element that existed before scrolling is gone afterward, so its coordinates can't be returned to the caller. This is the post-scroll analogue of a detached/stale element.","triggerScenarios":"An element that is present initially but removed by the act of scrolling — lazy-loaded lists that virtualize/replace DOM nodes on scroll, infinite feeds that re-render items, overlays that close or re-mount on scroll events, or the page navigating during the settle delay.","commonSituations":"React/Vue virtualized lists (react-window, virtual-scroller) recycling the target row's DOM node on scroll; lazy images/components re-mounting after scroll triggers an intersection observer; SPA route change fired by a scroll listener; scroll_settle_delay too short so re-render races the final measurement.","solutions":["Increase cfg.scroll_settle_delay so lazy re-renders complete before the final box lookup.","Target a stable container ancestor instead of the recycled node (e.g. the list item wrapper that isn't virtualized), then navigate to the child after scrolling.","Wait for the element to re-stabilize and retry the humanized action (catch this error and re-run once).","If re-mounting is inherent (virtualization), use locator.scrollIntoViewIfNeeded + a fresh locator resolution for the click instead of the humanized scroll path."],"exampleFix":"// before\nconst cfg = { ...defaults, scroll_settle_delay: 50 };\nawait humanClick(page, 'li.item-42', cfg); // node recycled mid-scroll\n\n// after\nconst cfg = { ...defaults, scroll_settle_delay: 400 };\nawait page.locator('li.item-42').waitFor({ state: 'attached' });\nawait humanClick(page, 'li.item-42', cfg);","handlingStrategy":"retry","validationCode":"const cfg = { ...defaults, scroll_settle_delay: 400 };\nawait page.locator(selector).waitFor({ state: 'attached' });\nawait humanClick(page, selector, cfg);","typeGuard":"null","tryCatchPattern":"try {\n  await humanClick(page, selector, cfg);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Element lost after scrolling into view') {\n    await page.locator(selector).waitFor({ state: 'visible', timeout: 2000 });\n    await humanClick(page, selector, cfg); // element re-mounted; retry once\n  } else throw e;\n}","preventionTips":["Raise scroll_settle_delay on pages with lazy/virtualized rendering.","Target stable container elements instead of virtualized/recycled nodes.","Scroll with scrollIntoViewIfNeeded and re-resolve the locator before clicking when re-mounts are expected."],"tags":["playwright","scroll","stale-element","virtualization","race-condition"],"backgroundTag":"stale-element-reference","analyzedSha":"d6bad5de261bedf025280ace1d14e800aee13923","analyzedAt":"2026-08-28T14:13:12.918Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}