CloakHQ/CloakBrowser · error · Error

Element not found while scrolling into view

Error message

Element not found while scrolling into view

What it means

Inside humanScrollIntoView, getBox() resolves the element's bounding box (via the isolated-world snapshot). If it returns null at the initial lookup stage, the element could not be found/measured at all, so scrolling cannot proceed. This fires before any scrolling happens — it's the 'element doesn't exist (or has no box) at action time' case.

Source

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

  getBox: () => Promise<T | null>,
  cursorX: number,
  cursorY: number,
  cfg: HumanConfig,
): Promise<{ box: T; cursorX: number; cursorY: number; didScroll: boolean }> {
  let viewport = page.viewportSize();
  if (!viewport) {
    const world = getWorld(page);
    if (!world) throw new StealthWorldUnavailableError();
    try {
      viewport = await world.evaluate(VIEWPORT_JS);
    } catch {
      throw new StealthEvaluationError('<viewport>');
    }
  }
  if (!viewport || !viewport.height) throw new Error('Viewport size not available');

  let box = await getBox();
  if (!box) throw new Error('Element not found while scrolling into view');

  if (isInViewport(box, viewport.height, cfg)) {
    return { box, cursorX, cursorY, didScroll: false };
  }

  const fullyVisible = box.y >= 0 && box.y + box.height <= viewport.height;
  if (fullyVisible) {
    const zoneMid = viewport.height * (cfg.scroll_target_zone[0] + cfg.scroll_target_zone[1]) / 2;
    const needUp = box.y + box.height / 2 < zoneMid;
    const { y, maxY } = await readScrollState(page);
    if (needUp ? y <= 0 : y >= maxY) {
      return { box, cursorX, cursorY, didScroll: false };
    }
  }

  const scrollAreaX = Math.round(viewport.width * rand(0.3, 0.7));
  const scrollAreaY = Math.round(viewport.height * rand(0.3, 0.7));
  await humanMove(raw, cursorX, cursorY, scrollAreaX, scrollAreaY, cfg);

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Wait for the element to be actionable first: await page.locator(sel).waitFor({ state: 'visible' }) or use non-humanized scrollIntoViewIfNeeded to force reveal before the human action.
  2. Make the container visible (open the accordion/tab/dropdown) before acting on its children.
  3. If the element is in an iframe, switch page context to that frame — the snapshot only searches the main world's document.
  4. Tighten the selector so it matches the visible instance (e.g. add .first() or a visibility-scoped CSS class).

Example fix

// before
await humanClick(page, 'text=Accept Cookies'); // cookie banner not rendered yet

// after
await page.locator('text=Accept Cookies').waitFor({ state: 'visible' });
await humanClick(page, 'text=Accept Cookies');
Defensive patterns

Strategy: validation

Validate before calling

const loc = page.locator(selector).first();
await loc.waitFor({ state: 'visible' });
await humanClick(page, selector); // element now has a layout box

Type guard

async function elementHasBox(page: Page, selector: string): Promise<boolean> {
  try { return (await page.locator(selector).first().boundingBox()) !== null; }
  catch { return false; }
}

Try / catch

try {
  await humanClick(page, selector);
} catch (e) {
  if (e instanceof Error && e.message === 'Element not found while scrolling into view') {
    await page.locator(selector).first().waitFor({ state: 'visible' });
    await humanClick(page, selector);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling a humanized click/press or scrollToElement on a selector whose element is detached, display:none/hidden (zero, no layout box), inside a collapsed container, or when the snapshot resolves to nothing because the element hasn't rendered yet.

Common situations: Acting before an element is actually rendered (SPA hydration, lazy list virtualization, hidden tabs/accordions); element re-rendered between query and action; selectors matching only in a different frame; display:none until a CSS class toggles.

Related errors


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