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
- Re-resolve the element (fresh snapshot for a new targetId/gen) immediately before dispatching input, in a tight retry loop.
- Use a stable selector attribute (data-testid) so resolution is deterministic and reduce delay between resolution and dispatch.
- 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
- Keep the resolution-to-dispatch window as short as possible in dynamic pages.
- Re-validate target identity right before every input dispatch.
- Retry the whole action when SPAs swap nodes; treat stale as 'redownload the target', not a hard failure.
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
- Isolated-world DOM evaluation failed for ${selector}
- Element ${selector} failed attached check: element not found
- Element ${selector} failed visible check: element is not vis
- Element ${selector} failed enabled check: element is disable
- Element ${selector} failed editable check: element is not ed
AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28).
Data as JSON: /api/errors/107c6ff3b29ff492.
Report an issue: GitHub.