# How Ego-Lite Resolves Elements Using Different Selector Types

> Discover how Ego-Lite resolves elements with CSS, XPath, and accessibility selectors. Learn about its unified parseLocator function for efficient element resolution.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-24

---

**Ego-Lite resolves elements through a unified `parseLocator` function in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) that normalizes selector strings into typed locator objects, then routes them to specialized resolution strategies including CSS/XPath evaluation, accessibility tree traversal, and attribute-based DOM walking.**

The *citrolabs/ego-lite* repository provides a browser-automation harness that abstracts element location behind a single string argument. This article examines how the system parses these strings and dispatches them to the appropriate resolution mechanism based on the selector type.

## The Core Resolution Pipeline

The resolution flow follows a strict pipeline: **raw selector → parseLocator → selector-specific JS builder → CDP evaluation → element center/objectId**. 

All resolution logic resides in [`package/ego-browser/src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts). The entry point, `parseLocator(input)` (lines 1514–1610), examines the raw string and returns a normalized **locator object** containing the selector's kind (`css`, `xpath`, `role`, `text`, `label`, `placeholder`, `alt`, `title`, `testid`, `href`, `query`) and optional **nth** index modifiers (`internal:nth=` or `internal:last;`).

## Selector Parsing with `parseLocator`

When you pass a selector like `role:button[name=Confirm]` or `css:button.submit`, the `parseLocator` function identifies the prefix and payload. It handles:

- **Explicit indices**: The `internal:nth=n` syntax selects the nth matching element, while `internal:last` selects the final match.
- **Exact matching**: Flags like `exact:` trigger strict equality checks rather than substring matching.
- **Ref lookups**: Strings starting with `@N` trigger reference resolution against the internal ref map.

The parsed locator object serves as the routing token for the rest of the resolution pipeline.

## Role-Based Resolution via the Accessibility Tree

**Role-based selectors** leverage Chrome's Accessibility tree rather than the DOM. Ego-Lite uses the CDP command `Accessibility.getFullAXTree` to retrieve the full accessibility tree, then passes it to `findBackendNodeIdsByRoleName` (lines 2424–2505).

This helper walks the AX nodes, matching both the requested **role** and **name** (accessible name). If the locator specifies an `nth` index, it selects the corresponding match from the results; otherwise, it requires a unique match and throws a `ElementResolutionError` if multiple elements qualify.

## CSS and XPath Selector Evaluation

For **CSS selectors** and **XPath expressions**, Ego-Lite delegates to the generic DOM query helper `queryAllExpression` from [`locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator-query.ts).

- **CSS**: The `buildLocatorFindJs` function (lines 1515–1520) prefixes the string with `loc=css:` and evaluates it within the browser context.
- **XPath**: The XPath branch (lines 1531–1535) generates a native `document.evaluate` call targeting `XPathResult.ORDERED_NODE_SNAPSHOT_TYPE`.

Both strategies return a list of matching elements that subsequent filtering logic can narrow by index.

## Text and Attribute-Based Selectors

Selectors like `text`, `label`, `placeholder`, `alt`, `title`, and `testid` generate specialized JavaScript helpers that execute in the browser context:

- **`textElementsJs`**: Walks the DOM to extract inner text, normalizes whitespace, and performs exact or "contains" matching.
- **`labelElementsJs`**: Targets `<label>` elements and their associated form controls.
- **`attributeElementsJs`**: Generic helper for HTML attributes like `alt`, `title`, and `data-testid`.

These functions (lines 1451–1484) filter results to avoid nested matches—preventing a parent container from shadowing its child text elements.

## Specialized Selectors: Href and Raw Queries

**Href selectors** collect all `<a>` elements with an `href` attribute and compare the resolved URL (including path, query, and hash) against the supplied value. The `hrefElementsJs` helper (lines 1657–1665) handles this comparison.

**Raw query selectors** bypass the standard parsing logic. Strings prefixed with `internal:scope:` or `internal:filter:` route directly to `queryAllExpression`, allowing complex chained queries against specific DOM scopes.

## Resolving to Element Centers and Object IDs

Once a locator identifies candidate elements, the system resolves them to actionable references through two public helpers:

- **`resolveElementCenter`**: Calls `DOM.getBoxModel` via Chrome DevTools Protocol to retrieve the element's bounding box, then calculates the center coordinates `{x, y}`.
- **`resolveElementObjectId`**: Uses `DOM.resolveNode` to obtain a CDP object ID for further interactions like `click` or `type`.

If the locator specifies a **ref** (`@N`), the resolver first checks the ref map in [`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts). If the backend node is stale, it falls back to role/name lookup.

## Error Handling and Retry Semantics

Throughout the resolution flow, functions throw `ElementResolutionError` with a **kind** classification:

- **Transient**: Indicates the element may appear with a retry (e.g., element not yet rendered). Powers the `waitForSelector` helper.
- **Permanent**: Indicates a selector logic problem (e.g., multiple matches found when uniqueness is required).

This distinction allows higher-level automation scripts to distinguish between timing issues and selector defects.

## Code Examples

### Resolving a CSS Selector (Center Coordinates)

```typescript
// Returns {x, y, sessionId} for the center of the submit button
const { x, y } = await js('css:button.submit');

```

*Behind the scenes*: `parseLocator` identifies `kind: "css"` → `buildLocatorCenterJs` → `queryAllExpression` → `DOM.getBoxModel`.

### Resolving a Role-Based Element (Object ID)

```typescript
// Find the first button with role="button" and accessible name "Confirm"
const { objectId } = await js('role:button[name=Confirm]');

```

*Behind the scenes*: `parseLocator` yields `{kind:"role", role:"button", name:"Confirm"}` → `findBackendNodeIdByRoleName` → `DOM.resolveNode`.

### Using a Text Selector with Exact Match

```typescript
// Click the element whose visible text exactly matches "Log In"
await click('text:exact:Log In');

```

*Behind the scenes*: `parseLocator` creates `{kind:"text", text:"Log In", exact:true}` → `textElementsJs` → filtered element list → first element's objectId.

### Selecting by Href (Last Match)

```typescript
// Retrieve centre of the last link pointing to "/settings"
const pos = await js('href:/settings;internal:last');

```

*Behind the scenes*: `parseLocator` → `{kind:"href", href:"/settings", nth:"last"}` → `hrefElementsJs` → selects the final matching `<a>` element.

## Summary

- **Unified entry point**: All selectors pass through `parseLocator` in [`package/ego-browser/src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) for normalization.
- **Multiple resolution strategies**: The system routes to accessibility tree traversal, DOM querying, or attribute walking based on the selector kind.
- **Index support**: The `internal:nth=` and `internal:last` modifiers allow precise element selection from matched sets.
- **CDP integration**: Resolution ultimately yields either center coordinates via `DOM.getBoxModel` or object references via `DOM.resolveNode`.
- **Robust error handling**: `ElementResolutionError` distinguishes between transient (retryable) and permanent (selector logic) failures.

## Frequently Asked Questions

### How does Ego-Lite handle ambiguous selectors that match multiple elements?

According to the source code in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts), Ego-Lite throws a permanent `ElementResolutionError` when a selector matches multiple elements but no `nth` index is specified. To select a specific match, append `internal:nth=n` (0-indexed) or `internal:last` to the selector string.

### What is the difference between `resolveElementCenter` and `resolveElementObjectId`?

`resolveElementCenter` returns the `{x, y}` coordinates of an element's center by calling CDP's `DOM.getBoxModel`, suitable for mouse interactions. `resolveElementObjectId` returns a CDP RemoteObject ID via `DOM.resolveNode`, required for invoking element methods or accessing properties directly.

### Can Ego-Lite resolve elements using ARIA roles instead of CSS classes?

Yes. Role-based selectors use the accessibility tree via `Accessibility.getFullAXTree` and `findBackendNodeIdsByRoleName`. Use the syntax `role:button[name=Submit]` to match elements by their computed role and accessible name, which is more resilient than CSS selectors when DOM structure changes.

### Where does Ego-Lite store references to previously resolved elements?

The system maintains an internal ref map in [`package/ego-browser/src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ref-map.ts). When you use a ref selector like `@1`, the resolver looks up the backend node ID in this map before falling back to fresh resolution if the reference has become stale.