# How Ego-Lite's Element Resolver Handles Different Selector Formats

> Discover how ego-lite's element resolver expertly handles various selector formats including css xpath role text href and snapshots resolving them efficiently via CDP accessibility tree or injected JavaScript.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-07-31

---

**Ego-Lite's element resolver in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) parses selector strings prefixed with `css:`, `xpath:`, `role:`, `text:`, `href:`, or `@` snapshot references into structured locator objects, then resolves them via Chrome DevTools Protocol (CDP) calls, accessibility tree queries, or injected JavaScript depending on the format.**

The `citrolabs/ego-lite` repository implements a browser automation framework that unifies diverse element selection strategies behind a single declarative API. Understanding **how ego-lite's element resolver handles different selector formats** enables developers to write resilient automation scripts that remain stable across DOM mutations and iframe boundaries.

## Supported Selector Formats

Ego-Lite's resolver recognizes multiple locator syntaxes through a unified parsing layer. Each format is identified by specific prefixes or patterns that trigger distinct resolution strategies.

### Snapshot References (@)

When a selector starts with `@`, the resolver treats it as a snapshot reference handled by `parseRef`. According to the source code at lines [70‑104](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L70-L104), this extracts a numeric ID that indexes into the current ref-map (managed in [`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts)). The resolver locates the associated CDP session—including elements inside iframes—and attempts direct resolution via the stored `backendNodeId`. If direct resolution fails, it falls back to role and name lookup mechanisms.

### CSS and XPath Selectors

The resolver supports standard web selectors through explicit prefixes:

- **`css:`** – Plain CSS selectors that map to `{ kind: "css", selector }`
- **`xpath:`** – XPath expressions mapped to `{ kind: "xpath", xpath }`

As implemented in `parseLocator` (lines [147‑188](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L147-L188)), these prefixes trigger JavaScript injection using `queryAllExpression` from [`locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator-query.ts). When no prefix is detected, the parser falls back to CSS selector interpretation (lines [212‑217](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L212-L217)), optionally handling `nth` indices for positional selection.

### Text-Based and Attribute Locators

Text matching supports both fuzzy and exact modalities:

- **`text:`** or **`text=`** – Substring matching with optional **`exact:`** modifier
- **Attribute shortcuts** – `label:`, `placeholder:`, `alt:`, `title:`, and `testid:` all resolve to the generic text-like kind

The resolution pipeline uses `buildLocatorAllJs` (lines [610‑632](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L610-L632)) to scan the DOM for visible text and relevant attributes, then filters results through `textMatchJs` (line [696](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L696)) based on exact or partial matching criteria.

### ARIA Role-Based Selectors

**`role:`** selectors provide stable identification independent of DOM structure, as implemented at lines [201‑211](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L201-L211). These support optional **`name=`** filters for accessible names and **`nth`** indices for positional selection. The resolver queries the Chromium Accessibility tree via `Accessibility.getFullAXTree` and locates nodes using `findBackendNodeIdByRoleName` (starting at line [496](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L496)).

### Hyperlink Matching

**`href:`** selectors filter anchor elements based on URL path or full URL matching. The implementation in `hrefElementsJs` (line [997](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L997)) generates JavaScript that evaluates link destinations against the specified pattern, enabling reliable selection of navigation elements regardless of their display text.

## The Resolution Pipeline

After parsing, the resolver executes format-specific resolution strategies through two primary entry points: `resolveElementCenter` (for coordinates) and `resolveElementObjectId` (for CDP object references).

### CDP and JavaScript Resolution Paths

**Role-based locators** bypass the DOM entirely, using `Accessibility.getFullAXTree` to obtain backend node IDs directly from the browser's accessibility layer. This approach remains stable even when CSS classes or DOM hierarchy change.

**CSS, XPath, and href locators** execute within the page context via `Runtime.evaluate`. CSS selectors use normalized `queryAllExpression` calls with index selection (`nth` or `last`), while XPath expressions invoke `document.evaluate`. The resolver retrieves geometry through `DOM.getBoxModel` for accessibility-derived nodes or `getBoundingClientRect()` for DOM-queried elements (lines [63‑146](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L63-L146)).

**Object ID resolution** (lines [149‑238](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L149-L238)) returns CDP `objectId` values via `DOM.resolveNode` for accessibility nodes or direct JavaScript evaluation for DOM elements, enabling subsequent CDP operations like `DOM.setAttribute` or `Input.dispatchMouseEvent`.

## Error Handling and Recovery

All resolution failures surface as `ElementResolutionError` instances with explicit classification. The `matchCountKind` logic (lines [46‑50](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L46-L50)) categorizes errors as **`"transient"`** (retryable, such as timing issues) or **`"permanent"`** (non-recoverable, such as selector syntax errors or absent elements). This distinction allows calling code in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) to implement appropriate retry strategies for flaky network conditions or animation states.

## Practical Code Examples

The user-facing API in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) leverages these resolver capabilities:

```typescript
// CSS selector with positional index
await click('loc=css:.submit-button;nth=2');

// XPath expression
await click('xpath://button[@type="submit"]');

// Text matching with exact modifier
await click('text=exact:Submit Order');

// ARIA role with name filter and index
await click('role:button[name="Close"];nth=0');

// Hyperlink by path
await click('href:/settings/account');

// Snapshot reference from previous capture
await click('@12');

```

Each helper delegates to `resolveElementCenter` or `resolveElementObjectId`, which handle the underlying CDP session management, iframe traversal, and selector format detection automatically.

## Summary

- **Ego-Lite's element resolver** in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) provides a unified interface for multiple selector syntaxes through prefix-based parsing.
- **Snapshot references** (`@`) resolve via `parseRef` and the ref-map system, supporting iframe-aware element restoration.
- **Role-based selectors** query the accessibility tree via CDP's `Accessibility.getFullAXTree`, offering stability against DOM changes.
- **CSS, XPath, and text selectors** execute as injected JavaScript, with text matching supporting attribute scans and exact/fuzzy modes.
- **Error classification** distinguishes between transient (retryable) and permanent failures, enabling robust automation retry logic.

## Frequently Asked Questions

### How does ego-lite handle element selection inside iframes?

The resolver maintains session awareness through the ref-map and `parseRef` logic (lines [70‑104](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts#L70-L104)). When resolving snapshot references or executing JavaScript queries, it targets the correct CDP session associated with the frame containing the element, allowing seamless interaction with cross-frame DOM structures.

### What is the difference between `text:` and `text=` prefixes in ego-lite?

Both prefixes initiate text-based matching parsed by `parseLocator`, but `text=` is the standard assignment syntax while `text:` maintains consistency with other prefix patterns like `css:` and `role:`. Both support the **`exact:`** modifier to switch from substring matching to full-string equality checks.

### How does the resolver handle ambiguous selectors that match multiple elements?

The resolver accepts an **`nth`** parameter (zero-based index) to disambiguate multiple matches. When `nth` is specified, the resolver selects the corresponding element from the matched set. Without `nth`, multiple matches trigger an `ElementResolutionError` classified according to `matchCountKind`, typically marking the failure as permanent to prevent unintended interactions.

### Can ego-lite resolve elements using complex XPath expressions?

Yes. The `xpath:` prefix triggers XPath evaluation via `document.evaluate` within the target page context. The resolver supports standard XPath 1.0 syntax and returns the matched element's coordinates or object ID, subject to the same `nth` indexing and error handling rules as CSS selectors.