How the `page.locator()` Method Works in Ego‑Lite: A Complete Guide to Element Selection

The page.locator() method in ego-lite returns a strict, auto‑waiting locator object that bundles helper methods for element interaction, forwarding calls to underlying Chrome DevTools Protocol (CDP) driver functions while automatically handling retries and transient errors.

In the citrolabs/ego-lite browser automation framework, page.locator() serves as the primary entry point for finding and interacting with DOM elements. This method creates a robust abstraction over raw CDP queries, providing Playwright‑style stability through automatic waiting mechanisms implemented in src/driver/locator.ts. Understanding how this locator system resolves selectors and manages element resolution is essential for writing reliable browser automation scripts.

The Locator Factory in src/helpers.ts

According to the citrolabs/ego-lite source code, the implementation lives in src/helpers.ts, where the createLocator factory function constructs the locator façade. When you invoke page.locator(), it instantiates an object that stores the selector string unchanged in its selector property and exposes a rich set of helper methods.

This façade pattern separates the high‑level API from the low‑level driver logic. Rather than executing CDP commands directly, methods like click(), innerText(), and evaluate() forward their calls to specialized driver functions:

// Conceptual representation from src/helpers.ts
click: (options = {}) => pointer.click(selector, options),
innerText: () => locator.innerText(selector),
evaluate: (fn, arg) => locator.evaluateLocator(selector, fn, arg)

The actual element resolution and interaction logic resides in src/driver/locator.ts, which handles both standard CSS/XPath queries and accessibility‑role queries via queryRoleBackendNodeIds in src/element-resolver.ts.

Supported Selector Syntax

The page.locator() method accepts several selector forms, which are parsed and resolved according to their prefix or format:

  • CSS selectors: Standard CSS syntax like 'button.submit' or '#nav > ul'
  • Snapshot references (@): Direct node references like '@21' that resolve to specific backend node IDs
  • loc= shortcuts: Prefixed shortcuts including CSS, XPath, text, and role queries (e.g., 'loc=css:.nav', 'loc=role:button[name="Submit"]')
  • XPath expressions: Prefixed with xpath= (e.g., 'xpath=//div[@id="main"]')

When using role‑based selectors, src/element-resolver.ts translates these into accessibility tree queries through the queryRoleBackendNodeIds function, enabling semantic element location independent of CSS structure.

Auto‑Waiting and Retry Mechanics

A key feature of ego‑lite's locators is the auto‑waiting behavior. The driver functions readElement and readOptionalElement in src/driver/locator.ts implement retry loops that catch ElementResolutionError instances of kind transient.

This mechanism ensures that if an element is not immediately available in the DOM, the engine will retry the query until the element appears or the default timeout expires. This provides Playwright‑style stability without manual sleep statements, handling race conditions between page updates and element access automatically.

Chaining and Narrowing Locators

Locators are immutable and composable. The façade exposes methods that return new locator instances with augmented selectors, enabling expressive query building:

  • first(), last(), nth(index): Narrow to specific elements in a collection using internal selectors like internal:nth=2
  • locator(child): Scope queries to descendants using internal:scope:{base, child} syntax
  • filter(options): Subset elements based on text content or other criteria
  • getByRole(), getByText(): Semantic shortcuts that construct appropriate loc= selectors

Each chained method invokes createLocator with a transformed selector string, maintaining the separation between query construction and execution.

Snapshot‑Aware References

When working with snapshot references (selectors starting with @), the locator system optimizes resolution. For example, the count() method short‑circuits the normal query process by resolving the handle directly, ensuring that snapshot references always resolve to exactly one element without unnecessary DOM traversal.

Practical Usage Examples

The following examples demonstrate common patterns when working with page.locator() in ego‑lite:

// Basic CSS locator – click the submit button
await page.locator('button[type=submit]').click();

// Locate by accessible role with an exact name, then fill a text input
await page
  .locator()
  .getByRole('textbox', { name: 'Email', exact: true })
  .fill('user@example.com');

// Chain selectors: first paragraph inside a specific container
const firstParagraph = await page
  .locator('#article')
  .locator('p')
  .first()
  .innerText();

// Use a snapshot reference (e.g., @21) to retrieve text content
const refText = await page.locator('@21').textContent();

// Filter a set of elements that contain specific text
await page
  .locator('.list-item')
  .filter({ hasText: 'Active' })
  .count();

Summary

  • page.locator() in src/helpers.ts creates a strict, auto‑waiting locator façade via the createLocator factory.
  • The method accepts CSS selectors, snapshot references (@), loc= shortcuts, and XPath expressions, storing them unchanged in the selector property.
  • Underlying driver functions in src/driver/locator.ts handle CDP queries and accessibility‑role resolution via src/element-resolver.ts.
  • Auto‑waiting is implemented through retry loops in readElement and readOptionalElement that catch transient ElementResolutionError instances.
  • Locators support method chaining (first(), nth(), filter(), locator()) which returns new instances with augmented internal selectors.
  • Snapshot references receive optimized handling where count() resolves handles directly rather than performing DOM queries.

Frequently Asked Questions

How does page.locator() handle elements that aren't immediately present in the DOM?

According to the source code in src/driver/locator.ts, the method implements auto‑waiting through retry loops in functions like readElement and readOptionalElement. These functions catch ElementResolutionError instances marked as transient and retry the query until the element appears or the timeout expires, providing automatic synchronization without manual sleeps.

What is the difference between loc=role: selectors and standard CSS selectors?

Standard CSS selectors query the DOM tree directly, while loc=role: selectors trigger accessibility tree queries via queryRoleBackendNodeIds in src/element-resolver.ts. This allows you to locate elements by their semantic role and accessible name rather than CSS attributes, making tests more resilient to layout changes.

Can I chain page.locator() methods to narrow down element selection?

Yes, the locator façade exposes chainable methods like first(), nth(index), last(), and filter() that each return a new locator instance created by createLocator in src/helpers.ts. These methods augment the selector string with internal prefixes (e.g., internal:nth=2), enabling composable queries that resolve to specific elements within a broader selection.

Where is the page.locator() method signature documented?

The TypeScript signatures and help() API documentation for page.locator are defined in src/format.ts, which catalogs the method's various overloads and supported selector formats for IDE assistance and runtime help systems.

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 →