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

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/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/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, then executes them against the browser's DOM.

Locator Object Creation

The [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/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/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 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

// 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

// 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

// 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

// 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

// 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

// 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/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/package/ego-browser/src/driver/locator.ts) Core locator implementation with action methods
[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/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/package/ego-browser/src/driver/locator-query.ts) CDP query expression generation
[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) → Resolver (element-resolver.ts) → Driver (driver/locator.ts) with state managed in 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
  • 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 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/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/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/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/package/ego-browser/src/format.ts). These strings power the runtime help() function available to agents debugging their selector strategies.

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 →