# Understanding the ego-browser page.locator API and Comparison to Playwright

> Explore the ego-browser page.locator API, a Playwright-compatible facade for element selection and interaction in ego-lite. Discover its advantages and compare it to Playwright.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: comparison
- Published: 2026-08-15

---

**The ego-browser `page.locator` API is a Playwright-compatible facade that provides element selection, auto-waiting, and interaction methods within the ego-lite runtime, enhanced with snapshot-stable references and CDP-based execution.**

The **ego-browser** package (citrolabs/ego-lite) implements a familiar automation interface for agents running inside the ego-lite environment. Rather than requiring a separate Playwright installation, `ego-browser` exposes a `page` object through [[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) that replicates Playwright's **Locator API** while adding ego-lite-specific capabilities like stable snapshot references and learned site skills.

## How page.locator Works Under the Hood

When an agent calls `page.locator()`, the request flows through several architectural layers:

### Selector Parsing and Resolution

The entry point in [[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) handles multiple selector types:

- **CSS selectors**: Standard CSS syntax (`button.primary`)
- **XPath expressions**: `xpath=//div[@class='item']`
- **Role-based**: `getByRole` with accessible name matching
- **Text-based**: `getByText` for visible string matching
- **Stable references**: `@21` format for snapshot-persistent element IDs

The resolver builds CDP-compatible JavaScript expressions via helpers in [`src/driver/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator-query.ts), then executes them against the browser's DOM.

### Locator Object Creation

The [[`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/locator.ts) module creates locator objects that wrap resolved element sets. These objects implement:

- **Strict mode**: Exactly one element must match, or the call throws
- **Auto-waiting**: Implicit retries with re-snapshot on `StaleElementReference`-equivalent failures
- **Method chaining**: `first()`, `nth()`, `last()`, `filter()` for narrowing selections

### State Management

[[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) maintains the runtime snapshot and reference map, enabling locators to persist across multiple agent script rounds without re-querying the DOM.

## API Surface: page.locator Methods

The canonical API signatures live in [[`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts), which also powers the runtime `help()` documentation. Key method categories include:

**Element Selection**
- `page.locator(selector)` — CSS/XPath/stable ref entry point
- `getByRole(role, options)` — ARIA role with name filtering
- `getByText(text, options)` — Visible text matching (exact or substring)
- `getByPlaceholder(text)` — Placeholder attribute matching
- `getByLabel(text)` — Label text matching
- `getByTestId(testId)` — `data-testid` attribute matching

**Selection Refinement**
- `first()` — First matching element
- `last()` — Last matching element
- `nth(index)` — Zero-indexed element access
- `filter(options)` — Chainable filtering by text, has-child, or state

**Actions**
- `click(options)` — Mouse click with position/modifier options
- `fill(value)` — Clear and type into input fields
- `press(key)` — Keyboard key press
- `hover()` — Mouse hover
- `scrollIntoViewIfNeeded()` — Ensure visibility

**Assertions & Inspection**
- `isVisible()` — Visibility check with auto-wait
- `isEnabled()` — Enabled state check
- `isChecked()` — Checkbox/radio state
- `textContent()` — Retrieve text content
- `getAttribute(name)` — Attribute value retrieval
- `evaluate(pageFunction, arg)` — Execute JavaScript in element context
- `evaluateHandle(pageFunction, arg)` — Return JSHandle for complex objects
- `boundingBox()` — Element position and dimensions

**Waiting**
- `waitFor(options)` — Wait for visibility, hidden, or detached states
- `waitForSelector(selector, options)` — Wait for selector presence

## Comparison: ego-browser vs. Playwright

| Aspect | ego-browser `page.locator` | Playwright |
|--------|---------------------------|------------|
| **API Compatibility** | Full surface compatibility—code written for Playwright runs unchanged | Reference implementation |
| **Selector Extensions** | Adds `@ref` stable references and `loc=` prefix for snapshot-based resolution | Standard CSS, XPath, text, role, test-id only |
| **Execution Context** | Inside ego-lite runtime with CDP bridge via `ego.sendCDPMessage` | Direct Chrome/Chromium process control |
| **State Persistence** | Snapshot and ref map persist across agent rounds in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) | Fresh page state per script session |
| **Site Skills** | Pluggable site-specific locator augmentations via skill manifests | No built-in mechanism |
| **Auto-waiting** | Strict locators with retry after re-snapshot on transient failures | Strict locators with built-in waiting |
| **Error Recovery** | Automatic DOM re-snapshot and element re-resolution | Standard retry with configurable timeout |

## Practical Code Examples

### Basic Element Interaction

```javascript
// Click a button by CSS selector
await page.locator('button[type="submit"]').click();

// Fill a form field
await page.locator('#username').fill('myuser@example.com');

```

### Role-Based and Text-Based Locators

```javascript
// Find button by accessible name
await page.locator.getByRole('button', { name: 'Sign in', exact: true }).click();

// Match partial text (fuzzy)
await page.locator.getByText('Welcome back').waitFor({ state: 'visible' });

// Combine role with name filter
await page.locator.getByRole('link', { name: /Pricing/i }).click();

```

### Chaining and Filtering

```javascript
// Narrow list to items with specific text, then pick third match
await page
  .locator('.product-card')
  .filter({ hasText: 'In Stock' })
  .nth(2)
  .click();

// Filter by presence of child element
await page
  .locator('article')
  .filter({ has: page.locator('.badge--new') })
  .first()
  .hover();

```

### Stable Snapshot References

```javascript
// Capture snapshot with stable refs
const snapshot = await page.snapshot();
console.log(snapshot.refs); // { "21": { selector: "...", element }, ... }

// Re-use ref across script rounds (survives DOM mutations)
await page.locator('@21').fill('Updated value');
await page.locator('@21').press('Enter');

```

### JavaScript Evaluation

```javascript
// Extract computed styles
const color = await page.locator('.highlight')
  .evaluate(el => getComputedStyle(el).backgroundColor);

// Return complex object via handle
const handle = await page.locator('#data-table')
  .evaluateHandle(el => el.dataset);
const json = await handle.jsonValue();

```

### Wait Patterns

```javascript
// Wait for element to appear
await page.locator('.toast-notification').waitFor({ state: 'visible' });

// Wait for element to disappear
await page.locator('.loading-spinner').waitFor({ state: 'hidden' });

// Wait with timeout override
await page.locator('.slow-element').waitFor({ 
  state: 'visible', 
  timeout: 10000 
});

```

## Key Source Files

| File | Purpose |
|------|---------|
| [[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Exports `page` facade and re-exports all locator functionality |
| [[`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/locator.ts) | Core locator implementation with action methods |
| [[`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts) | API signatures and documentation strings |
| [[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) | Selector parsing and element resolution logic |
| [[`src/driver/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator-query.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/locator-query.ts) | CDP query expression generation |
| [[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Snapshot, ref map, and CDP session management |

## Summary

- **ego-browser `page.locator`** provides Playwright-compatible element selection and interaction within the ego-lite runtime
- **Architecture**: Facade ([`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)) → Resolver ([`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)) → Driver ([`driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/locator.ts)) with state managed in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)
- **Key extensions**: `@ref` stable references, snapshot persistence, and site-skill augmentation beyond standard Playwright
- **Full method compatibility**: All major Playwright locator methods implemented with identical signatures per [`format.ts`](https://github.com/citrolabs/ego-lite/blob/main/format.ts)
- **Execution model**: CDP-based via `ego.sendCDPMessage` rather than direct browser control

## Frequently Asked Questions

### Can I migrate existing Playwright scripts to ego-browser without changes?

Most Playwright scripts run unchanged in ego-browser. The API surface in [`format.ts`](https://github.com/citrolabs/ego-lite/blob/main/format.ts) maintains signature compatibility. You may need to adjust for ego-lite's snapshot behavior—specifically, the `@ref` system can replace fragile CSS selectors in dynamic applications.

### How do stable references (`@ref`) differ from Playwright's default behavior?

Playwright locators re-query the DOM on every action. ego-browser's `@ref` IDs from [[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) persist across agent rounds through the snapshot mechanism, surviving many DOM mutations that would break standard selectors. Relevant resolution logic lives in [[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts).

### Why does ego-browser implement its own locator layer instead of using Playwright directly?

The ego-lite architecture requires sandboxed execution within a controlled runtime. The custom implementation in [[`driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/locator.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/locator.ts) bridges to Chrome via ego-lite's CDP message transport (`ego.sendCDPMessage`) rather than spawning independent browser processes, enabling tighter integration with ego-lite's state management and site-skill system.

### Where are the API documentation strings defined?

All public method signatures and descriptions reside in [[`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts). These strings power the runtime `help()` function available to agents debugging their selector strategies.