# How the ego-lite Element Resolver Handles CSS, XPath, loc=, and @N Locators

> Discover how ego-lite's element resolver efficiently handles CSS, XPath, loc=, and @N locators. Learn how it translates various selectors into DOM elements using specialized strategies and CDP.

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

---

**The ego-lite element resolver translates CSS selectors, XPath expressions, location shortcuts (`loc=`), and snapshot references (`@N`) into concrete DOM elements by parsing selector prefixes and dispatching to specialized resolution strategies that execute within the browser context via Chrome DevTools Protocol (CDP).**

The element resolver is the core service in [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) that bridges high-level selector strings and actual DOM nodes. Located in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts), it exposes a unified `resolveElement()` helper (available via [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)) that agents call regardless of the underlying locator syntax, abstracting away the complexity of browser automation protocols.

## Supported Locator Types and Resolution Strategies

The resolver recognizes four distinct locator categories, each handled by a dedicated code path that ultimately executes JavaScript within the page context through CDP.

### CSS Selectors (Implicit and Explicit)

**CSS selectors** are the default resolution strategy. The resolver accepts either an explicit `css=` prefix or a bare string, treating both as standard CSS selectors. It invokes `document.querySelectorAll` via `cdp().js` and returns the matching element handle.

### XPath Expressions

Selectors prefixed with `xpath=` trigger **XPath resolution**. The resolver evaluates the expression using `document.evaluate` inside the page context (again via CDP) and extracts the first matching node. This supports complex queries that CSS cannot express, such as text-based matching or ancestor traversal.

### Location Shortcuts (loc=)

The **`loc=`** syntax provides semantic shortcuts that route to different lookup mechanisms based on the sub-prefix:

- **`css:`** — Delegates to the standard CSS resolution path after stripping the prefix.
- **`role:`** — **Accessibility-first lookup** that queries the Accessibility Tree (AX) to find elements by their ARIA role and optional accessible name, avoiding fragile structural selectors.
- **`href:`** — Locates anchor elements (`<a>`) whose `href` attribute matches the supplied value.

### Snapshot References (@N)

**`@N` syntax** (e.g., `@42`) refers to numeric backend node IDs captured in the last browser snapshot. The resolver consults the **RefMap** ([`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts)) to translate the reference into a live element handle. If the RefMap is empty (for example, after navigation), the resolver automatically triggers a fresh snapshot before retrying the lookup.

## Resolution Flow and Implementation Details

The [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) implementation follows a strict dispatch pattern:

1. **Parsing** — The resolver checks for prefixes (`css=`, `xpath=`, `loc=`, `@`). If none are present, it assumes a plain CSS selector.
2. **Dispatch** — Based on the parsed type, it calls the appropriate internal helper:
   - **CSS/XPath** — Executes `cdp().js('document.querySelectorAll(...)')` or `document.evaluate(...)`.
   - **`loc=`** — Parses the sub-type and routes to CSS, AX lookup, or attribute search.
   - **`@N`** — Queries the RefMap; triggers snapshot refresh if needed.
3. **Error Classification** — Failed lookups throw `ElementResolutionError` with a `transient` or `permanent` flag. **Transient** errors (e.g., temporary page load states) signal the wait helpers to retry, while **permanent** errors (e.g., invalid selector syntax) halt execution immediately.

## Code Examples

```javascript
// CSS selector (explicit or implicit)
const btn = await resolveElement('css=button.primary')
// equivalent implicit form
const btn2 = await resolveElement('button.primary')

// XPath selector
const header = await resolveElement('xpath=//h1[contains(text(),"Welcome")]')

// "loc=" shortcuts
// Role-based lookup using the Accessibility Tree
const submitBtn = await resolveElement('loc=role:button[name="Submit"]')

// Href-based lookup finds <a> with matching href attribute
const docsLink = await resolveElement('loc=href:/docs/intro')

// Reference to a node captured in a previous snapshot
const savedNode = await resolveElement('@42')

```

## Why This Architecture Matters

**Unified API** — Agents call a single `resolveElement()` method regardless of selector complexity, reducing boilerplate and cognitive load.

**Accessibility-First Design** — The `loc=role:` form encourages stable, semantic selectors based on ARIA roles rather than brittle CSS paths that break when the DOM structure changes.

**Snapshot Synchronization** — The `@N` syntax and RefMap ([`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts), [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts)) provide a fast, deterministic way to reuse previously identified nodes. The automatic re-snapshot mechanism ensures references remain valid across navigation events without manual intervention.

## Summary

- The element resolver in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) supports **CSS**, **XPath**, **`loc=`**, and **`@N`** locator types through prefix detection and strategy dispatch.
- **CSS** and **XPath** queries execute via CDP using `document.querySelectorAll` and `document.evaluate` respectively.
- **`loc=`** shortcuts route to CSS paths, Accessibility Tree lookups (`role:`), or attribute matching (`href:`).
- **`@N`** references map to backend node IDs stored in the RefMap, with automatic snapshot refresh on cache misses.
- Errors are classified as **transient** (retryable) or **permanent** (fatal) via `ElementResolutionError`, enabling intelligent wait loops.

## Frequently Asked Questions

### What happens if an @N reference is not found in the RefMap?

If the reference is missing from the RefMap (typically because the page navigated since the last snapshot), the resolver automatically triggers a fresh snapshot to rebuild the reference map before retrying the lookup. This ensures `@N` syntax remains reliable across page transitions.

### How does the resolver choose between transient and permanent errors?

The resolver throws `ElementResolutionError` with a `transient` flag when the failure appears temporary, such as when the page is still loading or the element has not yet appeared. It marks errors as `permanent` when the selector syntax is invalid or the element cannot possibly exist in the current DOM, allowing wait helpers to retry only appropriate failures.

### Can I use role-based selectors with additional attributes?

Yes. When using `loc=role:`, you can include a name qualifier in brackets, such as `loc=role:button[name="Submit"]`. The resolver queries the Accessibility Tree for an element with the specified role and accessible name, combining semantic stability with precise targeting.

### Where is the resolveElement function exported for agent use?

While the core logic lives in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts), the public `resolveElement` helper is exposed through [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). Agents import this helper to access the full resolver functionality with proper error handling and CDP session management.