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

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 that bridges high-level selector strings and actual DOM nodes. Located in src/element-resolver.ts, it exposes a unified resolveElement() helper (available via 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) 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 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

// 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, 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 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, the public resolveElement helper is exposed through src/helpers.ts. Agents import this helper to access the full resolver functionality with proper error handling and CDP session management.

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 →