# Locator Facade Architecture in ego-browser: How Selector Resolution Functions

> Discover ego-browser's locator facade architecture and understand how selector resolution handles CSS, XPath, rolre locators, and numeric refs with automatic retries.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: architecture
- Published: 2026-07-26

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) and [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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 current `ref-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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```javascript
// Simple CSS selector – resolved via ElementResolver internally
await click('loc=css:#submit-button')

```

```javascript
// Role-based locator – queries the Accessibility Tree
await click('loc=role:button[name="Close"]')

```

```javascript
// Using a numeric ref from a previous snapshot
const ref = await $(await get('@21'))   // '@21' resolves to a backend node ID
await click(ref)

```

```javascript
// XPath selector – executes a DOM XPath query
await click('xpath=//nav//a[contains(@href, "settings")]')

```

## Summary

- The **locator facade** in `citrolabs/ego-lite` centralizes 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 via `ElementResolver` using CDP methods like `DOM.querySelector` and `Accessibility.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 the `ref-map`, with automatic re-snapshotting when references are stale or the map is empty.
- Public helpers in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) provide 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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.