CloakHQ/CloakBrowser · error · ElementNotAttachedError

ElementNotAttachedError: {selector}

Error message

ElementNotAttachedError: {selector}

What it means

ElementNotAttachedError mapped from StealthStatus.NotFound in LocatorHumanizer.SnapshotAsync: the stealth DOM snapshot for the selector found no matching element, so the humanized locator action (check, uncheck, click, focus...) aborts.

Source

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

using Microsoft.Playwright;

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)

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Wait for the element to be attached/visible before the humanized action (locator.WaitForAsync)
  2. Verify the selector matches (count == 1) with a debug query
  3. Handle auth/redirects so the expected page is actually loaded
  4. Retry after content settles in dynamic SPAs

Example fix

// before
await page.Locator("#agree").HumanCheckAsync();

// after
await page.Locator("#agree").WaitForAsync(new() { State = WaitForSelectorState.Visible });
await page.Locator("#agree").HumanCheckAsync();
Defensive patterns

Strategy: try-catch

Validate before calling

await page.Locator(selector).WaitForAsync(new() { State = WaitForSelectorState.Attached });

Try / catch

catch (ElementNotAttachedError) { await page.WaitForSelectorAsync(selector); retry once; }

Prevention

When it happens

Trigger: Humanized Check/Uncheck/move on a selector with no attached match within the snapshot — element not yet rendered, wrong selector, or element removed before the call.

Common situations: SPA late rendering, selector typos, elements behind auth/redirects so the page content differs from expectation, virtualized lists.

Related errors


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