CloakHQ/CloakBrowser · error · Error

Element lost after scrolling into view

Error message

Element lost after scrolling into view

What it means

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.

Source

Thrown at js/src/human/scroll.ts:179

  }

  if (Math.random() < cfg.scroll_overshoot_chance) {
    const overshootPx = Math.round(randRange(cfg.scroll_overshoot_px)) * direction;
    await smoothWheel(raw, overshootPx, cfg);
    await sleep(randRange(cfg.scroll_settle_delay));

    const corrections = randIntRange([1, 2]);
    for (let c = 0; c < corrections; c++) {
      const corrDelta = Math.round(rand(40, 80)) * -direction;
      await smoothWheel(raw, corrDelta, cfg);
      await sleep(rand(100, 250));
    }
  }

  await sleep(randRange(cfg.scroll_settle_delay));

  const finalBox = await getBox();
  if (!finalBox) throw new Error('Element lost after scrolling into view');
  return { box: finalBox, cursorX, cursorY, didScroll: true };
}

export async function scrollToElement(
  page: Page,
  raw: RawMouse,
  selector: string,
  cursorX: number,
  cursorY: number,
  cfg: HumanConfig,
  timeout: number = 30000,
): Promise<{ box: SelectorBounds; cursorX: number; cursorY: number; didScroll: boolean }> {
  return humanScrollIntoView(
    page,
    raw,
    () => getElementBox(page, selector, timeout),
    cursorX,
    cursorY,

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Increase cfg.scroll_settle_delay so lazy re-renders complete before the final box lookup.
  2. 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.
  3. Wait for the element to re-stabilize and retry the humanized action (catch this error and re-run once).
  4. If re-mounting is inherent (virtualization), use locator.scrollIntoViewIfNeeded + a fresh locator resolution for the click instead of the humanized scroll path.

Example fix

// before
const cfg = { ...defaults, scroll_settle_delay: 50 };
await humanClick(page, 'li.item-42', cfg); // node recycled mid-scroll

// after
const cfg = { ...defaults, scroll_settle_delay: 400 };
await page.locator('li.item-42').waitFor({ state: 'attached' });
await humanClick(page, 'li.item-42', cfg);
Defensive patterns

Strategy: retry

Validate before calling

const cfg = { ...defaults, scroll_settle_delay: 400 };
await page.locator(selector).waitFor({ state: 'attached' });
await humanClick(page, selector, cfg);

Type guard

null

Try / catch

try {
  await humanClick(page, selector, cfg);
} catch (e) {
  if (e instanceof Error && e.message === 'Element lost after scrolling into view') {
    await page.locator(selector).waitFor({ state: 'visible', timeout: 2000 });
    await humanClick(page, selector, cfg); // element re-mounted; retry once
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/f2d169c55b36bf6e. Report an issue: GitHub.