# What Selector Formats Does the Ego-Lite Element Resolver Support? A Complete Guide

> Explore the unified selector language supported by ego-lite element resolver including CSS XPath ARIA text href attributes and snapshot references. Learn how parseLocator resolves elements.

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

---

**TLDR: The ego-lite element resolver in `citrolabs/ego-lite` supports a unified selector language covering CSS, XPath, ARIA role, text, href, attribute-based lookups, and snapshot references — all parsed by `parseLocator` in [`package/ego-browser/src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) and resolved via CDP calls or in-page JavaScript.**

The `citrolabs/ego-lite` repository implements an intelligent browser automation layer where the core [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) module translates selector strings into concrete DOM elements or their geometric coordinates (centers). It provides agents with a **single, declarative API** that abstracts away raw Chrome DevTools Protocol (CDP) calls, iframe handling, and accessibility tree queries. Instead of maintaining multiple resolution mechanisms, every selector format routes through one parser and one set of resolution strategies, making it easy to switch between CSS and role-based queries without rewriting test code.

## Understanding the Selector Resolution Pipeline

The resolver follows a two-phase flow. First, it checks if the input is a **snapshot reference** (starting with `@`). If not, it parses the string into a structured locator object and then dispatches to the appropriate resolution implementation. Both paths funnel into the same geometry or object-id outputs.

The complete logic lives in [[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts), with supporting query-generation code in [[`locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator-query.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/locator-query.ts) and user-facing helpers in [[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts).

## All Supported Selector Formats in Ego-Lite

The `parseLocator` function (lines 147–188 in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)) recognizes these prefixes and maps them to an internal locator record with a `kind` field:

| Format | Prefix | Locator Kind | Example |
|--------|--------|--------------|---------|
| CSS selector | `css:` | `"css"` | `css:.submit-button` |
| XPath expression | `xpath:` | `"xpath"` | `xpath://div[@id='main']/button` |
| Hyperlink path | `href:` | `"href"` | `href:/settings/account` |
| Text match | `text:` or `text=` | `"text"` (with optional `exact`) | `text=Login` |
| Label / ARIA label | `label:` | text-like | `label:Username` |
| Placeholder | `placeholder:` | text-like | `placeholder:Enter email` |
| Alt text | `alt:` | text-like | `alt:Product logo` |
| Title attribute | `title:` | text-like | `title:Close dialog` |
| Test ID | `testid:` | text-like | `testid:user-menu` |
| ARIA role | `role:` | `"role"` (with optional `name=` and `nth`) | `role:button[name="Close"]` |
| Plain CSS (fallback) | none | `"css"` | `.card button` |
| Snapshot reference | `@` | ref lookup | `@42` |

### The `loc=` Prefix Group

All locator prefixes (except the bare `@` ref) can be explicitly wrapped in `loc=`. So `loc=css:.submit-button` is equivalent to `css:.submit-button`. The parser strips `loc=`, then re-parses the remainder using the rules above. If no known prefix is present, the string is treated as a raw CSS selector.

### Inside the Locator Parser

For a **css-only** string like `css:.submit-button`, output is `{ kind: "css", selector: ".submit-button" }`. When you append `nth=2`, the parser keeps it in the locator object for later index selection. XPath similarly produces `{ kind: "xpath", xpath: "//div" }`. The **text collect family** (`text`, `label`, `placeholder`, `alt`, `title`, `testid`) all collapse into separate kinds internally but share the same DOM-scanning logic, which walks elements and matches by their visible text or attribute values.

ARIA role locators accept an optional name filter and index, for example `role:button[navbar]` or `role:button;nth=1`. The name filter is not a CSS attribute selector but a separate key that is passed to the accessibility-tree query.

## How Each Selector Kind Is Resolved to a DOM Element

Once the parser produces a structured locator, the resolver branches into one of four resolution pathways:

- **Role-based locators** – `findBackendNodeIdByRoleName` queries the CDP Accessibility tree using `Accessibility.getFullAXTree` to find the element by role and optional name. This keeps the DOM structure changes from breaking the resolution, at the cost of an extra protocol round-trip.
- **CSS / XPath / href** – The resolver has a SSR JavaScript snippet (`queryAllExpression`) that is executed in the page. CSS selection normalizes the selector text and picks the target by index (`nth` or `last`). XPath uses `document.evaluate` directly. Href selectors filter `<a>` elements using an attribute comparison against the supplied path or full URL.
- **Text-based selectors** – `buildLocatorAllJs` builds a script that recursively scans the DOM for elements whose `innerText`, `label`, `placeholder`, `alt`, `title`, or `data-testid` matches the queried text. Then `textMatchJs` applies exact or fuzzy matching.
- **Snapshot references** – If the ref maps to a known node, the resolver tries to return the stored `backendNodeId`; if not, it falls back to a role/name query on the DOM.

### Example Resolutions in Code

```ts
// Click a button by CSS
await click('loc=css:.submit-button');

// Click the 3rd occurrence of a text match (zero-based)
await click('text=Next;nth=2');

// Use an ARIA role selector with a name filter
await click('role:button[name="Close"]');

// Click a link pointing to a specific path
await click('href:/settings/account');

// Use a snapshot reference from a prior snapshot
await click('@12'); // where 12 is the snapshot ref

```

These helpers (`click`, `type`, `await waitForSelector`) internally call [`resolveElementCenter`](file:///element-resolver.ts#L63-L146) or [`resolveElementObjectId`](file:///element-resolver.ts#L149-L238), which delegate to all of these resolution strategies.

## Geometry and Object-ID Extraction for Every Kind

After a concrete element or AX-node is found, ego-lite must generate either a screen coordinate or a CDP `objectId`:

**Center extraction (`resolveElementCenter`)** uses `DOM.getBoxModel` for refs and role-based nodes. For CSS/XPath/text locators, it injects a JS snippet that calls `getBoundingClientRect()` on the matched element and returns the center point `{x, y}` along with the correct `sessionId` (which is frame-aware).

**Object ID extraction (`resolveElementObjectId`)** likewise either calls `DOM.resolveNode` when a backend node is known, or returns the direct element reference from the page-side finder JS.

Because every selector format returns the same two output types, conditionals in the higher-level helper routines remain uniform — you can swap `css:` for `role:` and the only thing that changes is how the DOM element is found.

## Error Classification for Unresolvable Selectors

Unsuccessful lookups can fail with either a `transient` error or a `permanent` error, both wrapped in `ElementResolutionError`. The `matchCountKind` helper (lines 46–50) infers this by counting how many matches the query returned: zero matches usually means a transient failure (the page may still be loading), while multiple matches or explicit structural problems are considered `permanent`. This distinction is used by the retry logic in the agent loop.

## Key Source Files

| File | Role |
|------|------|
| [`element -resolver.ts`](file:///element-resolver.ts) | Parsing and resolution logic for all selector formats, plus AX tree/JS fallback. |
| [[`locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator-query.ts)](file:///locator-query.ts) | Builds the `queryAllExpression` snippet used for CSS, role, text, and href locators. |
| [[`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts)](file:///ref-map.ts) | Stores snapshot refs returned by `snapshot()` so `@N` can be resolved later. |

## Summary

- Ego-lite supports 11 selector formats: `css`, `xpath`, `role`, `text`, `label`, `placeholder`, `alt`, `title`, `testid`, ` href`, and snapshot refs (`@N`).
- A single parser — `parseLocator` — maps every string to a structured locator kind, and a uniform resolution layer then dispatches to CSS, XPath, text-scanning, or AX-tree lookups.
- Center and objectId extraction is shared across all kinds, so swapping between CSS and role-based commands never changes your automation logic.
- Error classification (`transient` vs `permanent`) provides retry semantics at the agent layer.

## Frequently Asked Questions

### Does ego-lite support CSS selectors and XPath in one entry point?

Yes — the `loc` string format accepts `css:` and `xpath:` and also bare selectors. For example, `css:.card` and `loc=css:.card` both work, while `xpath://form/button` is parsed as an XPath kind. The resolver generates the correct in-page JavaScript (`querySelector` vs `document.evaluate`) per kind.

### How does ego-lite resolve a bare string without any prefix?

If no recognized prefix (like `css:` or `role:`) appears and the string is not a `@` reference, the parser treats the input as a raw CSS selector. This means `click('.submit')` works directly.

### What is the `nth` parameter for with text selectors?

The `nth` parameter is a zero-based index that selects a specific matching element when multiple ones match. It works with CSS, text, role, and href locators. For example, `text=Next      `Next;nth=2` selects the third element whose text matches "Next".

### Can role locators be combined with a name filter?

Yes, role selectors accept a `name=` filter. `role:button[name="Close"]` means: find a button whose accessible name is "Close" (as exposed in the browser accessibility tree). The naster tree filter is applied at the AX node level, not via DOM attributes, which helps stable resolution even if the DOM structure changes.