When Does ego‑lite Throw a Permanent Element Resolution Error for Multi‑Match Selectors?
ego‑lite raises a permanent ElementResolutionError whenever a selector matches multiple elements and no explicit nth index is provided to disambiguate the target.
In the citrolabs/ego-lite browser automation framework, ambiguous selectors are treated as permanent failures rather than transient issues. The engine cannot determine which element you intended to interact with, and retrying the operation will not resolve the underlying ambiguity.
Scenarios That Trigger a Permanent ElementResolutionError
The core resolution logic lives in src/element-resolver.ts, where multiple code paths enforce strict uniqueness requirements. A permanent element resolution error occurs specifically when the selector evaluation returns a count greater than one and the locator lacks an nth property.
Multi-Match Without Index in resolveLocatorObjectId
Inside resolveLocatorObjectId (lines 49‑55), the engine awaits the match count from the browser runtime. If count > 1 && locator.nth === undefined, the resolver immediately throws:
// src/element-resolver.ts
if (count > 1 && locator.nth === undefined) {
throw new ElementResolutionError(
`Locator ${locatorStr} matched ${count} elements`,
"permanent"
);
}
This prevents the framework from arbitrarily selecting the first match and ensures your automation remains deterministic.
Center Resolution Ambiguity in resolveLocatorCenter
The same validation appears in resolveLocatorCenter (lines 49‑55). When calculating the visual center of an element for actions like clicks, the engine must resolve a single backend node ID. Multiple matches without an index trigger the identical permanent error path, as the runtime cannot compute a unique coordinate set.
Role-Based Locator Collisions
Accessibility-based selectors face identical constraints in findUniqueBackendNodeIdByRoleName (lines 82‑87). When a role locator such as role:button[name="Submit"] returns multiple AX nodes, the framework raises:
throw new ElementResolutionError(
`Locator role:${role}[name=${...}] matched ${matches.length} elements`,
"permanent"
);
How ego‑lite Determines Error Type
The helper function matchCountKind (lines 46‑49) inspects error messages to classify resolution failures. If the message contains matched <n> where n > 1, it returns "permanent":
// src/element-resolver.ts
function matchCountKind(message: string): "permanent" | "transient" {
const match = message.match(/matched (\d+) elements?/);
if (match && parseInt(match[1]) > 1) return "permanent";
return "transient";
}
This classification affects retry logic elsewhere in the codebase (see src/driver/waits.ts). Permanent errors halt execution immediately, while transient errors (stale elements, single-match failures) may trigger automated retries.
Resolving Multi‑Match Errors
You eliminate the permanent error by supplying an explicit index to narrow the selector to a single element.
Without index (throws error):
// Matches multiple buttons; no nth specified
await ego.click('css:button');
// ElementResolutionError: Locator css:button matched 2 elements (permanent)
With index (resolves successfully):
// Select first match (index 0)
await ego.click('css:button[0]');
// Select last match
await ego.click('css:button[last]');
Using unique references:
Ref IDs (backend node IDs) bypass the multi-match check entirely because they point to specific DOM nodes:
// No ambiguity; targets node ID 21 specifically
await ego.click('@21');
Summary
- Permanent errors occur in
src/element-resolver.tswhencount > 1 && locator.nth === undefined. - Key functions:
resolveLocatorObjectId,resolveLocatorCenter, andfindUniqueBackendNodeIdByRoleNameall enforce this constraint. - Error classification is determined by
matchCountKind, which flags any match count greater than one as permanent. - Resolution requires adding an
nthindex (e.g.,[0],[last]) or using a unique ref ID to eliminate ambiguity.
Frequently Asked Questions
What is the difference between permanent and transient ElementResolutionError in ego-lite?
A permanent ElementResolutionError indicates the selector is structurally ambiguous (multiple matches without an index) and will never succeed without code changes. A transient error indicates a temporary condition, such as a stale element reference or a single element not yet ready, which might resolve on retry.
How do I fix a "matched X elements" permanent error in ego-lite?
Add an explicit nth index to your locator string to specify which match you want. Use [0] for the first element, [1] for the second, or [last] for the final match. Alternatively, refine your CSS or role selector to match only one unique element.
Does ego-lite automatically pick the first element when multiple matches exist?
No. Unlike some automation frameworks that default to the first match, ego‑lite treats multi-match scenarios as fatal errors unless you explicitly provide an nth parameter. This design prevents flaky tests caused by accidental selection of the wrong element.
Can role-based locators trigger permanent element resolution errors?
Yes. Role-based locators evaluated by findUniqueBackendNodeIdByRoleName throw permanent errors when multiple elements share the same role and accessible name. You must either add an index or narrow the selector criteria to resolve a single accessibility node.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →