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

Ego-Lite's element resolver in 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, this extracts a numeric ID that indexes into the current ref-map (managed in 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), these prefixes trigger JavaScript injection using queryAllExpression from locator-query.ts. When no prefix is detected, the parser falls back to CSS selector interpretation (lines 212‑217), 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 shortcutslabel:, placeholder:, alt:, title:, and testid: all resolve to the generic text-like kind

The resolution pipeline uses buildLocatorAllJs (lines 610‑632) to scan the DOM for visible text and relevant attributes, then filters results through textMatchJs (line 696) 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. 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).

href: selectors filter anchor elements based on URL path or full URL matching. The implementation in hrefElementsJs (line 997) 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).

Object ID resolution (lines 149‑238) 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) 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 to implement appropriate retry strategies for flaky network conditions or animation states.

Practical Code Examples

The user-facing API in helpers.ts leverages these resolver capabilities:

// 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 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). 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.

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 →