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:
getByRolewith accessible name matching - Text-based:
getByTextfor visible string matching - Stable references:
@21format 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 pointgetByRole(role, options)— ARIA role with name filteringgetByText(text, options)— Visible text matching (exact or substring)getByPlaceholder(text)— Placeholder attribute matchinggetByLabel(text)— Label text matchinggetByTestId(testId)—data-testidattribute matching
Selection Refinement
first()— First matching elementlast()— Last matching elementnth(index)— Zero-indexed element accessfilter(options)— Chainable filtering by text, has-child, or state
Actions
click(options)— Mouse click with position/modifier optionsfill(value)— Clear and type into input fieldspress(key)— Keyboard key presshover()— Mouse hoverscrollIntoViewIfNeeded()— Ensure visibility
Assertions & Inspection
isVisible()— Visibility check with auto-waitisEnabled()— Enabled state checkisChecked()— Checkbox/radio statetextContent()— Retrieve text contentgetAttribute(name)— Attribute value retrievalevaluate(pageFunction, arg)— Execute JavaScript in element contextevaluateHandle(pageFunction, arg)— Return JSHandle for complex objectsboundingBox()— Element position and dimensions
Waiting
waitFor(options)— Wait for visibility, hidden, or detached stateswaitForSelector(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.locatorprovides 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 instate.ts - Key extensions:
@refstable 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.sendCDPMessagerather 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →