CloakHQ/CloakBrowser · error · InvalidOperationException

Viewport size not available

Error message

Viewport size not available

What it means

InvalidOperationException from HumanScrollIntoViewAsync when neither page.ViewportSize nor the live window dimensions (via GetLiveWindowSizeAsync) yield a usable viewport height (null or Height == 0). Without a viewport height the scroller cannot compute zones or distances.

Source

Thrown at dotnet/src/CloakBrowser/Human/HumanScroll.cs:107

    /// overshoot behavior.
    /// </summary>
    /// <returns>(box, cursorX, cursorY, didScroll) - didScroll is false when the
    /// element was already in the viewport.</returns>
    public static async Task<ScrollResult> HumanScrollIntoViewAsync(
        IRawScrollPage page,
        IRawMouse raw,
        Func<Task<BoundingBox?>> getBox,
        double cursorX, double cursorY,
        HumanConfig cfg)
    {
        var viewport = page.ViewportSize;
        if (viewport == null)
            // Headed launches default to no_viewport so the page tracks the real OS
            // window; ViewportSize is then null. Fall back to the live window
            // dimensions so humanize works headed (the stealth-relevant mode).
            viewport = await page.GetLiveWindowSizeAsync().ConfigureAwait(false);
        if (viewport == null || viewport.Value.Height == 0)
            throw new InvalidOperationException("Viewport size not available");

        int viewportHeight = viewport.Value.Height;
        int viewportWidth = viewport.Value.Width;

        var box = await getBox().ConfigureAwait(false);
        if (box == null)
            throw new InvalidOperationException("Element not found while scrolling into view");

        if (IsInViewport(box.Value, viewportHeight, cfg))
            return new ScrollResult(box.Value, cursorX, cursorY, false);

        // Already fully visible but off-center, with the page pinned at the boundary
        // in the needed direction: scrolling can't help, so don't waste the budget.
        bool fullyVisible = box.Value.Y >= 0 && box.Value.Y + box.Value.Height <= viewportHeight;
        if (fullyVisible)
        {
            double zoneMid = viewportHeight * (cfg.ScrollTargetZone.Min + cfg.ScrollTargetZone.Max) / 2;
            bool needUp = box.Value.Y + box.Value.Height / 2 < zoneMid;

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Ensure the page has fully navigated to real content before humanized scrolling
  2. For headless runs, set an explicit Viewport in BrowserContextOptions so ViewportSize is populated
  3. Check the page isn't closing (IsClosed) before issuing scroll calls
  4. Verify the stealth scripts are installed (they provide the live-dimension fallback)

Example fix

// before
var ctx = await browser.NewContextAsync(); // NoViewport default in headed mode

// after
var ctx = await browser.NewContextAsync(new BrowserContextOptions { Viewport = new(1280, 800) });
Defensive patterns

Strategy: validation

Validate before calling

if (page.ViewportSize is null or { Height: 0 }) await page.SetViewportSizeAsync(1280, 800);

Try / catch

catch (InvalidOperationException e) when (e.Message == "Viewport size not available") { /* set viewport, retry */ }

Prevention

When it happens

Trigger: Calling humanized scroll-into-view when the page has no ViewportSize (headed no_viewport mode) AND the stealth-world viewport probe also returns null or a zero-height object — e.g. navigation happening concurrently, or the stealth isolated world not being installed.

Common situations: Headed launches with NoViewport = true where the page is closing/closed, about:blank with no layout, race between navigation and humanize calls, or a stripped-down stealth script that doesn't expose the viewport probe.

Related errors


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