CloakHQ/CloakBrowser · error · ElementTargetChangedError

Element '{selector}' failed target_identity check: selector

Error message

Element '{selector}' failed target_identity check: selector resolved to a different element before input dispatch

What it means

ElementTargetChangedError is thrown inside the identity-aware CheckPointerEventsAsync loop when StealthDom.ValidateAsync returns StealthStatus.Stale: the selector now resolves to a different element (or generation) than the one identified by targetId/gen at resolution time. This guards against input being dispatched at coordinates computed for a node that the page has since swapped.

Source

Thrown at dotnet/src/CloakBrowser/Human/Actionability.cs:355

        double y,
        double timeoutMs = 5000,
        IsolatedWorld? stealth = null)
    {
        if (stealth == null)
            throw new StealthWorldUnavailableError();

        double deadline = NowMs() + timeoutMs;
        int attempt = 0;

        while (true)
        {
            var (status, hit, covering, _) = await StealthDom.ValidateAsync(
                stealth, selector, targetId, gen, x, y).ConfigureAwait(false);

            if (status == StealthStatus.Unsupported)
                throw new UnsupportedHumanizeSelectorError(selector);
            if (status == StealthStatus.Stale)
                throw new ElementTargetChangedError(selector);
            if (status == StealthStatus.NotFound)
                throw new ElementNotAttachedError(selector);
            if (status == StealthStatus.EvaluationFailed)
            {
                if (NowMs() >= deadline)
                    throw new StealthEvaluationError(selector);
            }
            else if (status == StealthStatus.Ok && hit)
            {
                return;
            }
            else if (status == StealthStatus.Ok)
            {
                if (NowMs() >= deadline)
                    throw new ElementNotReceivingEventsError(selector, covering);
            }
            else
            {

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Re-resolve the element (fresh snapshot for a new targetId/gen) immediately before dispatching input, in a tight retry loop.
  2. Use a stable selector attribute (data-testid) so resolution is deterministic and reduce delay between resolution and dispatch.
  3. If replacement is expected and the new node is equivalent, re-run the full humanized action against the fresh target.

Example fix

// before
var snap = await StealthDom.SnapshotAsync(world, sel);
await Task.Delay(1500); // DOM swaps the node meanwhile
await Actionability.CheckPointerEventsAsync(page, sel, snap.Value.TargetId, snap.Value.Gen, x, y, stealth: world);

// after
var snap = await StealthDom.SnapshotAsync(world, sel);
await Actionability.CheckPointerEventsAsync(page, sel, snap.Value.TargetId, snap.Value.Gen, x, y, stealth: world); // validate immediately
Defensive patterns

Strategy: retry

Validate before calling

var (status, _, _, _) = await StealthDom.ValidateAsync(world, sel, targetId, gen, x, y);
if (status == StealthStatus.Stale) { /* re-snapshot and use fresh TargetId/Gen before dispatch */ }

Try / catch

catch (ElementTargetChangedError)
{
    var snap = await StealthDom.SnapshotAsync(world, sel);
    if (snap.status == StealthStatus.Ok)
        await Actionability.CheckPointerEventsAsync(page, sel, snap.Value.TargetId, snap.Value.Gen, x, y, timeoutMs, world);
}

Prevention

When it happens

Trigger: Between resolving the element (capturing targetId and gen) and revalidating the click point, an SPA re-render replaces the DOM node so ValidateAsync flags the target as stale; list re-ordering, virtualization, or A/B swaps that mount a new node for the same selector.

Common situations: React/Vue keyed re-renders that recreate nodes, infinite-scroll lists shifting elements, click-then-pipeline flows that pause (delays, retries) long enough for the page to mutate, slow humanized typing letting the DOM churn underneath.

Related errors


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