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

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 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 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/package/ego-browser/src/element-resolver.ts), with supporting query-generation code in [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/package/ego-browser/src/helpers.ts).

All Supported Selector Formats in Ego-Lite

The parseLocator function (lines 147–188 in 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 locatorsfindBackendNodeIdByRoleName 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 selectorsbuildLocatorAllJs 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

// 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 or resolveElementObjectId, 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 Parsing and resolution logic for all selector formats, plus AX tree/JS fallback.
[locator-query.ts](file:///locator-query.ts) Builds the queryAllExpression snippet used for CSS, role, text, and href locators.
[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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →