Element Resolver and Locator Architecture in ego‑lite: A Complete Technical Guide

The ego‑lite element resolver translates user-supplied selectors into concrete DOM targets through a layered architecture that separates parsing, resolution logic, and Chrome DevTools Protocol (CDP) interaction, with built‑in fallback mechanisms and transient vs. permanent error classification.

The element resolver and locator system in ego‑lite is the bridge between high‑level automation commands and actual browser DOM manipulation. Designed for the citrolabs/ego-lite repository, this module handles everything from CSS selectors to accessibility‑tree queries, ensuring robust element discovery across frames and dynamic page states.

High‑Level Architecture Overview

The resolver operates through six distinct phases, each handled by dedicated functions in src/element-resolver.ts:

Phase Responsibility Entry Point
Parse Convert raw strings to structured locator objects parseLocator (lines 214–258)
Reference resolution Map @123 refs to stored metadata parseRefresolveElementCenter/resolveElementObjectId
Role‑based fallback Query AX tree when refs are stale findBackendNodeIdByRoleName (lines 496–511)
Locator execution Run generated JavaScript in page context buildLocatorFindJs, buildLocatorCountJs, buildLocatorCenterJs
CDP interaction Retrieve box models or object IDs DOM.getBoxModel, DOM.resolveNode, Runtime.evaluate
Error handling Classify failures for retry logic ElementResolutionError (lines 4–10)

This design decouples what to find from how to find it, enabling extensible locator types without modifying core resolution logic.

The Parsing Layer: From Strings to Structured Locators

The parseLocator function (lines 214–258) recognizes multiple locator prefixes and produces a uniform object structure:

  • CSS selectors: css:button.primary
  • XPath expressions: xpath=//div[@class='modal']
  • Text content: text:Submit
  • Accessibility roles: role:button[name="OK"]
  • Href targets: href:/dashboard
  • Nth/last indexing: css:li:nth(3) or css:li:last

Parsed output follows the pattern { kind, value, nth?, name?, raw }. For example, role:button[name="Submit"] becomes:

{
  kind: "role",
  role: "button",
  name: "Submit",
  raw: "role:button[name=\"Submit\"]"
}

This normalization allows downstream resolution code to handle all locator types through a single interface.

Reference Handling: The @ref System

Numeric refs (@123) provide stable identifiers across page mutations. The resolution flow:

  1. parseRef extracts the numeric ID from the @ prefix
  2. The resolver queries refMap (maintained in src/ref-map.ts) for stored metadata including:
    • Stored backendNodeId from previous snapshots
    • Accessibility role and name for fallback lookup
  3. If the backendNodeId is valid, the resolver issues DOM.getBoxModel or DOM.resolveNode
  4. If stale, the system falls back to accessibility role lookup using the stored role/name pair

Key functions in this path:

  • resolveElementCenter (lines 63–119) — returns {x, y, sessionId} for pointer actions
  • resolveElementObjectId (lines 149–215) — returns CDP objectId for DOM operations

Role‑Based Accessibility Tree Lookup

When refs fail or selectors explicitly use role:, the resolver queries Chromium's Accessibility (AX) tree via Accessibility.getFullAXTree:

  • findBackendNodeIdsByRoleName (lines 324–366): Walks AX nodes, filters by role and optional name, returns array of backend DOM node IDs
  • findBackendNodeIdByRoleName (lines 496–511): Selects nth or last match from the filtered set
  • findUniqueBackendNodeIdByRoleName (lines 669–677): Asserts exactly one match, throws otherwise

This AX‑driven approach is resilient to DOM restructuring since roles and accessible names remain stable across visual redesigns.

JavaScript Builder Pattern for Dynamic Execution

For non‑ref, non‑role locators, the resolver generates and executes JavaScript snippets via Runtime.evaluate. Three builders handle common operations:

Builder Purpose Lines
buildLocatorFindJs Locate single element, return its center or object reference 514–547
buildLocatorCountJs Count matching elements for existence/visibility checks 555–575
buildLocatorCenterJs Compute precise center coordinates accounting for transforms 778–795

These builders leverage queryAllExpression from src/locator-query.js to support the full locator syntax within generated page scripts.

Central Coordination: resolveElementCenter and resolveElementObjectId

Both entry points implement identical resolution priority:

  1. Attempt ref resolution if input starts with @
  2. Parse and execute locator via JS builders for standard selectors
  3. Fall back to role lookup if initial attempts fail
// Example: Resolve click coordinates from a CSS locator
const center = await resolveElementCenter(
  cdpClient,           // BrowserRuntime CDP wrapper
  sessionId,           // Target session (handles iframe context)
  refMap,              // Snapshot reference map
  'css:button.primary' // Locator string
);
// Returns: { x: 342, y: 217, sessionId: '...' }
// Example: Obtain object ID for complex DOM operations
const { objectId } = await resolveElementObjectId(
  cdpClient,
  sessionId,
  refMap,
  '@42'  // Reference from previous snapshot
);
// objectId enables DOM.getAttributes, DOM.setAttributeValue, etc.

Error Classification: Transient vs. Permanent Failures

The ElementResolutionError class (lines 4–10) powers ego‑lite's retry semantics:

  • transient — Element not yet rendered, multiple ambiguous matches, or frame context switching. Callers should retry with appropriate delays.
  • permanent — Invalid selector syntax, zero matches on stable page, or irrecoverable CDP errors. Retry would not succeed.

The helper selectorResolutionError (lines 52–61) standardizes error creation across all resolution paths:

throw new ElementResolutionError(
  'transient',
  `Element not found: ${locator.raw}`
);

Cross‑Frame Resolution with Session Management

Elements within iframes require context‑aware CDP sessions. The resolveFrameSession helper (lines 400–448):

  • Maps frame IDs to dedicated iframeSessions
  • Selects the correct session before issuing DOM.* or Runtime.* calls
  • Ensures isolation between parent page and iframe execution contexts

This enables seamless interaction with embedded content without manual frame switching.

Key Implementation Files

File Role in Element Resolution
src/element-resolver.ts Core parsing, resolution orchestration, and CDP interaction
src/locator-query.js DOM query expression builder for JS snippet generation
src/ref-map.ts Reference storage and metadata management
src/helpers.ts Public API exposing center(), objectId(), and related helpers
src/browser-runtime.ts CDP session lifecycle and low‑level command dispatch

Summary

  • The element resolver and locator system in ego‑lite separates concerns across parsing, reference management, AX‑tree querying, and dynamic script execution
  • Six resolution phases handle everything from CSS selectors to numeric refs, with automatic fallback to accessibility roles
  • ElementResolutionError enables intelligent retry logic through transient/permanent classification
  • Cross‑frame operations work transparently via session resolution in resolveFrameSession
  • The architecture supports extensibility: new locator prefixes require only parser updates and corresponding JS builder logic

Frequently Asked Questions

What locator types does ego‑lite support?

ego‑lite supports CSS selectors (css:), XPath (xpath=), text content (text:), accessibility roles (role:), href targets (href:), label references (label:), and numeric snapshot refs (@N). Each parses to a uniform object consumed by the resolution pipeline.

How does ego‑lite handle stale element references?

When a stored backendNodeId becomes invalid, the resolver automatically falls back to role‑based accessibility tree lookup using the cached role and name metadata from the original snapshot. This AX fallback provides resilience against DOM restructuring.

What triggers a transient vs. permanent error?

Transient errors occur when elements are not yet rendered, multiple matches exist, or frame contexts are switching—situations where retry may succeed. Permanent errors indicate invalid selector syntax, guaranteed zero matches, or irrecoverable protocol failures where retry would not help.

Can ego‑lite interact with elements inside iframes?

Yes. The resolveFrameSession helper maintains a mapping of frame IDs to dedicated CDP sessions. When resolving locators targeting iframe content, the system automatically selects the correct session before executing DOM or Runtime commands.

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 →