CloakHQ/CloakBrowser · error · StealthEvaluationError

<viewport>

Error message

<viewport>

What it means

StealthEvaluationError thrown by GetLiveWindowSizeAsync when evaluating the stealth viewport JS in the isolated world fails to return an object with width/height properties — i.e. the probe result is null, not JSON, or malformed.

Source

Thrown at dotnet/src/CloakBrowser/Human/PlaywrightAdapters.cs:102

    }

    public (int Width, int Height)? ViewportSize
    {
        get
        {
            var vs = _page.ViewportSize;
            return vs == null ? null : (vs.Width, vs.Height);
        }
    }

    public async Task<(int Width, int Height)?> GetLiveWindowSizeAsync()
    {
        var world = await _getStealthAsync().ConfigureAwait(false);
        var value = await world.EvaluateAsync(StealthDom.ViewportJs).ConfigureAwait(false);
        if (value == null || value.Value.ValueKind != System.Text.Json.JsonValueKind.Object
            || !value.Value.TryGetProperty("width", out var width)
            || !value.Value.TryGetProperty("height", out var height))
            throw new StealthEvaluationError("<viewport>");

        int resolvedWidth = (int)width.GetDouble();
        int resolvedHeight = (int)height.GetDouble();
        if (resolvedWidth <= 0 || resolvedHeight <= 0)
            throw new StealthEvaluationError("<viewport>");
        return (resolvedWidth, resolvedHeight);
    }

    public async Task<(double Y, double MaxY)?> GetScrollStateAsync()
    {
        var world = await _getStealthAsync().ConfigureAwait(false);
        var value = await world.EvaluateAsync(
            "(() => { const e = document.scrollingElement || document.documentElement;" +
            " return { y: window.scrollY, maxY: Math.max(0, e.scrollHeight - e.clientHeight) }; })()")
            .ConfigureAwait(false);
        if (value == null || value.Value.ValueKind != System.Text.Json.JsonValueKind.Object
            || !value.Value.TryGetProperty("y", out var y)
            || !value.Value.TryGetProperty("maxY", out var maxY))

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Await page.WaitForLoadStateAsync (DOMContentLoaded/NetworkIdle) before humanize calls
  2. Check page.IsClosed and that the stealth world is available before calling
  3. Let CloakBrowser create the context so stealth scripts are guaranteed installed
  4. Retry once after a short delay if a navigation race is suspected

Example fix

// before
await page.GoToAsync(url);
await page.HumanScrollIntoViewAsync(sel);

// after
await page.GoToAsync(url);
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
await page.HumanScrollIntoViewAsync(sel);
Defensive patterns

Strategy: retry

Validate before calling

await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
if (page.IsClosed) return;

Try / catch

catch (StealthEvaluationError e) when (e.Message == "<viewport>") { await Task.Delay(250); retry once; }

Prevention

When it happens

Trigger: Calling APIs that need live window size (scroll-into-view fallback in headed no_viewport mode) when the stealth world's EvaluateAsync returns null or a non-object — page mid-navigation, page crashed, or stealth scripts not injected.

Common situations: Calling humanize APIs right after GoToAsync before load completes, on a crashed page, or when the isolated world/stealth scripts were not installed (custom context setup bypassing CloakBrowser defaults).

Related errors


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