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 |
parseRef → resolveElementCenter/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)orcss: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:
parseRefextracts the numeric ID from the@prefix- The resolver queries
refMap(maintained insrc/ref-map.ts) for stored metadata including:- Stored
backendNodeIdfrom previous snapshots - Accessibility
roleandnamefor fallback lookup
- Stored
- If the
backendNodeIdis valid, the resolver issuesDOM.getBoxModelorDOM.resolveNode - 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 actionsresolveElementObjectId(lines 149–215) — returns CDPobjectIdfor 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 IDsfindBackendNodeIdByRoleName(lines 496–511): Selects nth or last match from the filtered setfindUniqueBackendNodeIdByRoleName(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:
- Attempt ref resolution if input starts with
@ - Parse and execute locator via JS builders for standard selectors
- 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.*orRuntime.*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
ElementResolutionErrorenables 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →