# Ego-Lite Page Facade API and Fluent Locator Interface: A Complete Guide

> Discover Ego-Lite's page facade API and fluent locator interface for robust browser automation. Learn how this Playwright-style approach ensures resilient element interaction.

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

---

**Ego‑Lite provides a Playwright‑style page façade injected as `page` into agent scripts, offering browser automation methods paired with a strict, auto‑waiting fluent locator API for resilient element interaction.**

The **ego‑lite** repository (citrolabs/ego-lite) exposes a high‑level browser automation layer designed for AI agents. The **page façade API** wraps Chrome DevTools Protocol (CDP) calls into familiar Playwright‑like methods, while the **fluent locator interface** eliminates timing bugs through automatic waiting and strict single‑element matching. This architecture lets agent scripts remain concise yet robust against dynamic page changes.

---

## Understanding the Page Facade API

The page façade is registered in the runtime's helper context under the name **`page`**. According to the `FACADE_HELP` documentation in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts), it provides navigation, element location, waiting utilities, and direct interaction methods. The full reference displays when calling `help('page')` from within an agent script.

### Core Navigation Methods

| Method | Description |
|--------|-------------|
| `await page.goto(url)` | Navigate to a given URL |
| `await page.waitForURL(url, options)` | Wait until the page reaches a specific URL pattern |
| `await page.url()` | Returns the current page URL asynchronously |

### Element Location Methods

Ego‑lite supports multiple strategies for finding DOM elements, all returning **locator objects**:

- `page.locator(selector)` – CSS selector or XPath
- `page.getByText(text)` – Semantic text matching
- `page.getByLabel(text)` – Find by associated label
- `page.getByPlaceholder(text)` – Find by placeholder attribute
- `page.getByTestId(testId)` – Find by `data-testid` attribute

These methods are defined in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) within the `FACADE_HELP` structure at lines 810‑820.

### Waiting and Interaction Utilities

```javascript
// Wait for page events and states
await page.waitForEvent(event);
await page.waitForLoadState(state, options);
await page.waitForRequest(predicate);
await page.waitForResponse(predicate);

// Direct page-level interactions
await page.evaluate(expression);
await page.screenshot(options);
await page.screencast.start(options);
await page.screencast.stop();

// Input device simulation
await page.keyboard.press(key);
await page.keyboard.type(text);
await page.mouse.click(x, y);

```

---

## How Locators Work as a Fluent Interface

Locators in ego‑lite are **strict, auto‑waiting façades** that enable method chaining. The implementation lives in [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts), where each call returns either the same locator instance or a derived one with refined scope.

### Locator Creation and Resolution Pipeline

The resolution process follows four stages as implemented in the source:

1. **Resolution** – `page.locator(selector)` instantiates a `Locator` object that delegates to [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) for mapping selectors to unique `backendNodeId` values.
2. **Strictness** – Each locator enforces a single DOM match. Multiple matches trigger an error until disambiguated with `first()`, `nth(index)`, or `last()`.
3. **Auto‑waiting** – Before any action, the façade internally calls `await locator.waitFor({ state: "visible" })` (or method‑appropriate state).
4. **Fluent chaining** – Method calls compile into optimized CDP command sequences, minimizing round‑trips to the browser.

### Chainable Locator Methods

| Category | Methods |
|----------|---------|
| **Refinement** | `locator(selector)`, `filter({ hasText, has })`, `getByRole()`, `getByText()` |
| **Position selection** | `first()`, `nth(index)`, `last()` |
| **Interactions** | `click()`, `hover()`, `dragTo(target)`, `fill(value)`, `clear()`, `press(key)`, `check()`, `selectOption(value)` |
| **Property retrieval** | `textContent()`, `innerText()`, `innerHTML()`, `isVisible()`, `isEnabled()`, `getAttribute(name)` |
| **Utilities** | `screenshot()`, `count()`, `evaluate(fn, arg)`, `evaluateAll(fn, arg)`, `waitFor(options)` |

The chaining logic is documented under the "locator" entry of `FACADE_HELP` in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) at lines 811‑822.

---

## Practical Code Examples

### Basic Navigation and Element Interaction

```javascript
// Navigate and verify URL
await page.goto('https://example.com');
console.log('Current URL:', await page.url());

// Fill a form using semantic locators
await page.locator('form#login')
          .getByLabel('Email')
          .fill('user@example.com');

await page.locator('form#login')
          .getByLabel('Password')
          .type('s3cr3t');

```

### Fluent Filtering and Position Selection

```javascript
// Click first button with exact "Submit" text
await page.locator('button')
          .filter({ hasText: 'Submit' })
          .first()
          .click();

// Use nth() for zero-based index selection
await page.locator('.item')
          .nth(2)  // third item
          .hover();

```

### Event Waiting with Locator Actions

```javascript
// Wait for download after clicking a link
const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.locator('a[href$=".pdf"]').click(),
]);
await download.saveAs('/tmp/report.pdf');

```

---

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Defines `page` façade and `FACADE_HELP` documentation |
| [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts) | Implements locator chaining, auto‑waiting, and action execution |
| [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) | Maps selectors to `backendNodeId` and handles resolution errors |
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | Provides CDP transport layer for all façade operations |

These files together create the high‑level API surface while maintaining efficient, reliable CDP communication.

---

## Summary

- **Page façade API** – Injected as `page`, provides Playwright‑compatible navigation, location, waiting, and interaction methods documented in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts).
- **Fluent locator interface** – Returned by `page.locator()` and semantic getters, offering chainable refinement, strict single‑element matching, and automatic waiting.
- **Auto‑waiting behavior** – Every locator action internally waits for element stability, eliminating explicit sleep calls.
- **Code efficiency** – Method chains compile to optimized CDP sequences, reducing browser round‑trips.
- **Strictness by default** – Forces explicit disambiguation when selectors match multiple elements.

---

## Frequently Asked Questions

### What is the difference between `page.locator()` and `page.getByText()`?

Both return locator objects, but `page.locator()` accepts raw CSS selectors or XPath expressions, while `page.getByText()` uses semantic text matching. Internally, `getByText` constructs a more resilient selector that handles visibility and exact/partial matching rules. Choose `locator()` for precision and `getByText()` for maintainability when text content is stable.

### Why does my locator throw "strict mode violation"?

Ego‑lite locators enforce strict single‑element matching as implemented in [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts). If your selector matches multiple DOM nodes, you must disambiguate using `first()`, `nth(index)`, or `last()`, or refine with `filter()`. This design prevents accidental interactions with the wrong element and surfaces ambiguous selections immediately.

### How does auto‑waiting work under the hood?

Before executing any action, the locator calls `waitFor({ state: "visible" })` or a method‑appropriate state. This delegates to [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) to poll the element's presence and stability via CDP. The polling continues until the condition is met or a timeout occurs, making scripts resilient to network delays and JavaScript‑driven rendering without explicit waits.

### Can I use async operations inside `locator.evaluate()`?

Yes. The `evaluate` and `evaluateAll` methods accept functions that may return Promises. The locator awaits resolution before returning, allowing complex browser‑side logic with asynchronous dependencies. Note that the function runs in the page context, not the Node.js runtime, so it cannot access variables from your script scope without explicit argument passing.