Locator Facade Architecture in ego-browser: How Selector Resolution Functions
The locator facade in ego-browser centralizes all element-finding logic behind a unified API that parses diverse selector types—CSS, XPath, role-based locators, and numeric refs—into a structured resolution flow with automatic retry handling for transient failures.
The ego-browser package within the citrolabs/ego-lite repository implements a robust locator facade to abstract the complexity of browser automation. This architecture unifies disparate selector formats into a single pipeline that resolves queries against the DOM or Accessibility Tree. Understanding how this facade processes selectors and manages reference state is essential for building reliable agentic browser interactions.
Core Architecture Components
The facade comprises four coordinated modules that translate high-level locator strings into actionable backend element handles.
Locator Query Parser (src/locator-query.ts)
The Locator Query module handles the initial parsing of raw locator strings. It determines the selector type—whether loc=css:, loc=role:, xpath=, @ref, or plain CSS—and constructs a structured query object. This normalization ensures that downstream components receive a consistent data structure regardless of the original locator syntax.
Element Resolver Engine (src/element-resolver.ts)
The Element Resolver serves as the core resolution engine. It receives structured queries from the Locator Query parser and executes them against the current page context using the Chrome DevTools Protocol (CDP). The resolver invokes DOM.querySelector, DOM.performSearch, or Accessibility.getPartialAXTree depending on the locator type. When resolution fails, it throws an ElementResolutionError that explicitly classifies failures as transient (retryable, such as during page loads) or permanent (invalid selector).
Reference Map Management (src/ref-map.ts and src/ref-state.ts)
The Ref-Map and Ref-State modules maintain the mapping between numeric references (@N) and their underlying backend node IDs. This map is rebuilt automatically on each snapshot. If the resolver encounters a numeric ref that does not exist in the current map, it triggers automatic re-snapshotting to refresh the reference state before attempting resolution again.
Browser Runtime Integration (src/browser-runtime.ts)
The Browser Runtime provides the low-level CDP transport, session management, event buffering, and dialog tracking required by the resolver. While not directly exposed to end users, this layer supplies the foundational connectivity that enables the Element Resolver to query the browser state.
How Selector Resolution Functions
The resolution process follows a strict four-stage pipeline that transforms string locators into element handles.
Input Normalisation
When a public helper receives a locator string—such as loc=css:.button, loc=role:button[name="Close"], @42, or xpath=//div—it first passes the raw input to the resolution pipeline. This stage accepts the various formats that the Locator Query parser recognizes.
Parsing with LocatorQuery
The LocatorQuery class in src/locator-query.ts parses the normalized string into a structured object that explicitly records the locator type and value. This classification determines which resolution strategy the Element Resolver will apply.
Resolution Strategy Execution
The ElementResolver distinguishes between two primary resolution paths:
- Reference Resolution: For numeric refs like
@21, the resolver looks up the value in the currentref-map. If the map is empty, it automatically initiates a fresh snapshot before proceeding. - Dynamic Selector Resolution: For CSS, role-based, href, or XPath selectors, the resolver executes the appropriate CDP query against the active page.
Error Classification and Handling
Upon successful resolution, the engine returns an object handle that subsequent actions can utilize. When resolution fails, the system classifies the error as either transient (triggering retry logic) or permanent (failing immediately). This distinction allows the facade to handle timing issues gracefully while failing fast on malformed selectors.
Public API Integration
The locator facade exposes its functionality through the Helper Context in src/helpers.ts. Public methods such as click(), type(), and waitFor() delegate their selector handling to the Element Resolver, providing a clean API surface while encapsulating the complexity of snapshot management and CDP communication.
Practical Usage Examples
The following examples demonstrate the various locator formats supported by the facade:
// Simple CSS selector – resolved via ElementResolver internally
await click('loc=css:#submit-button')
// Role-based locator – queries the Accessibility Tree
await click('loc=role:button[name="Close"]')
// Using a numeric ref from a previous snapshot
const ref = await $(await get('@21')) // '@21' resolves to a backend node ID
await click(ref)
// XPath selector – executes a DOM XPath query
await click('xpath=//nav//a[contains(@href, "settings")]')
Summary
- The locator facade in
citrolabs/ego-litecentralizes element-finding logic through four core modules: Locator Query, Element Resolver, Ref-Map/Ref-State, and Browser Runtime. - Selector resolution follows a four-stage pipeline: input normalisation, parsing with
LocatorQuery, execution viaElementResolverusing CDP methods likeDOM.querySelectorandAccessibility.getPartialAXTree, and result normalisation with error classification. - Transient errors (e.g., page loading states) trigger automatic retries, while permanent errors (invalid selectors) fail immediately with
ElementResolutionError. - Numeric refs (
@N) are validated against theref-map, with automatic re-snapshotting when references are stale or the map is empty. - Public helpers in
src/helpers.tsprovide the high-level API that internally delegates to the resolution engine.
Frequently Asked Questions
What happens when a numeric reference becomes stale?
When the Element Resolver encounters a numeric ref like @21 that is missing from the current ref-map, it automatically triggers a fresh snapshot via the Ref-State module in src/ref-state.ts. This rebuilds the mapping between numeric refs and backend node IDs, ensuring that stale references never resolve to incorrect elements.
How does the locator facade distinguish between transient and permanent resolution failures?
The Element Resolver in src/element-resolver.ts throws an ElementResolutionError that includes a flag indicating whether the failure is transient or permanent. Transient failures—such as attempting to locate an element before the page has finished loading—allow the calling code to retry, while permanent failures from malformed selectors halt execution immediately.
What selector types are supported by the ego-browser locator facade?
The facade supports CSS selectors (loc=css:), role-based locators (loc=role:), XPath expressions (xpath=), numeric references (@N), and href-based selectors. The Locator Query parser in src/locator-query.ts normalizes these formats into a unified structure for the resolution engine.
How does the facade handle page changes during element resolution?
If the reference map is empty or a specific numeric ref cannot be found, the Element Resolver automatically initiates a re-snapshot operation. This ensures the resolver always queries against the current DOM state, while the transient error classification provides resilience against timing issues during page transitions.
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 →