CloakHQ/CloakBrowser · error · Error

Viewport size not available

Error message

Viewport size not available

What it means

After resolving the viewport (either from Playwright or the isolated world), humanScrollIntoView requires a truthy object with a positive height. If both sources fail to provide usable dimensions, this generic Error is thrown because subsequent visibility math is impossible. It is a final sanity check, not an element error.

Source

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

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. Ensure the browser window has a real size: launch with explicit viewport, args like --window-size=1280,800, or a properly configured xvfb display (1920x1080x24).
  2. If you mock Page in tests, make viewportSize() return realistic dimensions or skip humanized paths in unit tests.
  3. Check that nothing (window manager, CI runner) minimizes the window during the run.
  4. Fall back to non-humanized scrollIntoViewIfNeeded when viewport detection is unreliable in your environment.

Example fix

# before (CI)
xvfb-run -a --server-args="-screen 0 0x0x16" npm test  # zero-height screen

# after
xvfb-run -a --server-args="-screen 0 1920x1080x24" npm test
Defensive patterns

Strategy: validation

Validate before calling

const vp = page.viewportSize();
if (!vp || !vp.height) throw new Error('Set a real viewport or fix the display size');
await scrollToElement(page, raw, selector);

Type guard

function hasRealViewport(page: Page): boolean {
  const vp = page.viewportSize();
  return !!vp && vp.height > 0;
}

Try / catch

try {
  await scrollToElement(page, raw, selector);
} catch (e) {
  if (e instanceof Error && e.message === 'Viewport size not available') {
    await page.setViewportSize({ width: 1280, height: 720 });
    await scrollToElement(page, raw, selector);
  } else throw e;
}

Prevention

When it happens

Trigger: page.viewportSize() returns a zero-height/empty object (or null) and the world fallback also returns an unusable viewport — e.g. a window reduced to zero height, a stubbed/mocked viewportSize in tests, or an embedded/webview context reporting 0.

Common situations: Headful windows minimized or sized to zero height; running under xvfb with a broken screen geometry; mocked Page objects in unit tests returning {width:0,height:0}; kiosk/webview embeds with no real viewport.

Related errors


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