CloakHQ/CloakBrowser · error · UnsupportedHumanizeSelectorError

UnsupportedHumanizeSelectorError

Error message

UnsupportedHumanizeSelectorError: {selector}

What it means

UnsupportedHumanizeSelectorError mapped from StealthStatus.Unsupported: the stealth DOM engine cannot resolve this selector shape, so the humanized locator path cannot run for it.

Solutions

  1. Convert the selector to plain CSS or XPath understood by the stealth DOM engine
  2. Use a stable data-testid CSS selector instead of Playwright pseudo-classes
  3. Fall back to the non-humanized Playwright path for exotic selectors

Example fix

# before
await page.locator("button:has-text('Submit')").human_click()

# after
await page.locator("button[type='submit']").human_click()
Defensive patterns

Strategy: type-guard

Validate before calling

bool supported = selector.All(c => char.IsLetterOrDigit(c)) && !selector.Contains(":has-text") && !selector.Contains(">>");

Type guard

static bool IsStealthSupportedSelector(string s) => !s.Contains(":") && !s.Contains(">>") && !s.StartsWith("text=");

Try / catch

catch (UnsupportedHumanizeSelectorError) { fall back to plain CSS equivalent or native Playwright path }

Prevention

When it happens

Trigger: Using selector syntax the isolated-world snapshot doesn't support — e.g. Playwright-specific engine selectors (:has-text, :visible, >> chained engines, internal: selectors) rather than plain CSS/XPath.

Common situations: Copying selectors from Playwright traces/codegen (which emit :text= and >> chains) into humanized locator calls.

Related errors


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

Appendix: source

Thrown at dotnet/src/CloakBrowser/Wrappers/LocatorHumanizer.cs:21

namespace CloakBrowser.Wrappers;

/// <summary>Humanized locator actions. Direct page.Locator selectors use the canonical isolated DOM world; unknown locator shapes retain the legacy Playwright path.</summary>
internal static class LocatorHumanizer
{
    private static double RemainingMs(double deadline) => Actionability.RemainingMs(deadline);

    private static async Task<IsolatedWorld> WorldAsync(HumanCursor cursor) =>
        await cursor.GetStealthAsync().ConfigureAwait(false) ?? throw new StealthWorldUnavailableError();

    internal static async Task<StealthSnapshot> SnapshotAsync(HumanCursor cursor, string selector)
    {
        var (status, snapshot) = await StealthDom.SnapshotAsync(await WorldAsync(cursor).ConfigureAwait(false), selector).ConfigureAwait(false);
        return status switch
        {
            StealthStatus.Ok when snapshot != null => snapshot.Value,
            StealthStatus.NotFound => throw new ElementNotAttachedError(selector),
            StealthStatus.Unsupported => throw new UnsupportedHumanizeSelectorError(selector),
            _ => throw new StealthEvaluationError(selector),
        };
    }

    internal static async Task EnsureActionableAsync(HumanCursor cursor, string selector,
        IReadOnlySet<string> checks, double timeout, bool force)
    {
        var world = await WorldAsync(cursor).ConfigureAwait(false);
        await Actionability.EnsureActionableAsync(cursor.Page, selector, checks, timeout, force, world).ConfigureAwait(false);
    }

    private static async Task<BoundingBox?> GetBoxAsync(
        IsolatedWorld world, string selector, double timeoutMs)
    {
        double deadline = System.Environment.TickCount64 + System.Math.Max(0, timeoutMs);
        var result = await StealthDom.BoxAsync(world, selector).ConfigureAwait(false);
        while ((result.Status == StealthStatus.NotFound || result.Status == StealthStatus.EvaluationFailed)
            && System.Environment.TickCount64 < deadline)

View on GitHub (pinned to d6bad5de26)