# When Does ego‑lite Throw a Permanent Element Resolution Error for Multi‑Match Selectors?

> Discover when ego-lite throws an ElementResolutionError for multi-match selectors. Learn how to avoid this error by providing an nth index for disambiguation.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-26

---

**`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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```typescript
// 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:

```typescript
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"`**:

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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):**

```javascript
// Matches multiple buttons; no nth specified
await ego.click('css:button');
// ElementResolutionError: Locator css:button matched 2 elements (permanent)

```

**With index (resolves successfully):**

```javascript
// 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:

```javascript
// No ambiguity; targets node ID 21 specifically
await ego.click('@21');

```

## Summary

- **Permanent errors occur** in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) when `count > 1 && locator.nth === undefined`.
- **Key functions:** `resolveLocatorObjectId`, `resolveLocatorCenter`, and `findUniqueBackendNodeIdByRoleName` all enforce this constraint.
- **Error classification** is determined by `matchCountKind`, which flags any match count greater than one as permanent.
- **Resolution** requires adding an `nth` index (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.