CloakHQ/CloakBrowser · error · StealthEvaluationError

Isolated-world DOM evaluation failed for "<viewport>"

Error message

Isolated-world DOM evaluation failed for "<viewport>"

What it means

When page.viewportSize() is unavailable, humanScrollIntoView evaluates VIEWPORT_JS inside the isolated world to get {width,height}. If that evaluate call throws (context destroyed, navigation, world teardown), the error is normalized to StealthEvaluationError('<viewport>'). The viewport probe itself failed, not the element lookup.

Source

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

  }
}

export async function humanScrollIntoView<T extends ElementBounds>(
  page: Page,
  raw: RawMouse,
  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 };

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Set an explicit context viewport (newContext({ viewport: {...} })) so the world-based viewport probe is skipped entirely.
  2. Await a stable state (waitForLoadState / element visible) before the humanized action.
  3. Retry the action once the page settles if it occasionally races navigation.
  4. Ensure the page isn't being closed/navigated by other code while the action runs.

Example fix

// before
const context = await browser.newContext(); // headful, no viewport
await humanClick(page, 'text=Buy');

// after
const context = await browser.newContext({ viewport: { width: 1366, height: 768 } });
await humanClick(page, 'text=Buy');
Defensive patterns

Strategy: retry

Validate before calling

if (!page.viewportSize()) await page.waitForLoadState('domcontentloaded');
await scrollToElement(page, raw, selector);
// strongest prevention: newContext({ viewport: { width: 1280, height: 720 } })

Type guard

null

Try / catch

try {
  await humanClick(page, selector);
} catch (e) {
  if (e instanceof StealthEvaluationError && e.message.includes('<viewport>')) {
    await page.waitForLoadState('load');
    await humanClick(page, selector);
  } else throw e;
}

Prevention

When it happens

Trigger: page.viewportSize() === null AND world.evaluate(VIEWPORT_JS) rejects — typically a headful page that navigates/reloads at the moment of the humanized action, or an isolated world whose execution context was invalidated.

Common situations: Headful runs on SPAs that navigate during clicks; humanized clicks fired immediately after page.goto before load completes; concurrent actions tearing down the world; flaky CDP connectivity in grid/docker setups.

Related errors


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