How to Locate Elements on a Page Using CSS, Roles, Text, or Labels in ego-lite

ego-lite provides a unified loc= selector syntax that lets you locate elements using CSS selectors, ARIA roles, visible text, labels, or href values, all processed through a centralized resolver in src/element-resolver.ts.

Locating elements reliably is the foundation of robust browser automation. The citrolabs/ego-lite framework simplifies this by offering multiple location strategies through a consistent string-based API. Whether you need to target a button by its visible text, a form input by its associated label, or a navigation link by its ARIA role, ego-lite handles the complexity via a single loc= prefix syntax.

The Unified loc= Selector Syntax

ego-lite abstracts DOM queries behind a locator string format. When you pass a selector to high-level driver helpers like click(), type(), or waitFor(), the framework first checks for the loc= prefix (or alternative prefixes like text= and @). If no prefix is detected, the string is treated as a raw CSS selector or snapshot reference.

The src/helpers.ts file contains factory functions that construct these locator strings programmatically, while src/element-resolver.ts handles the actual DOM resolution.

CSS Selectors

Use the loc=css: prefix to target elements with standard CSS selector syntax. This strategy passes directly to the Chrome DevTools Protocol (CDP) query engine.

// Target a button with class 'submit'
await click('loc=css:button.submit');

// Target an element by ID
await type('loc=css:#username', 'admin');

The helper function makeCssLocator in src/helpers.ts constructs these strings, ensuring consistent formatting across your test suite.

ARIA Roles

For accessibility-driven automation, use loc=role: followed by the role name and optional accessible name in brackets. This queries the browser's accessibility tree rather than the DOM, making tests resilient to visual restructuring.

// Click a button with role='button' and accessible name 'Submit'
await click('loc=role:button[Submit]');

// Target a generic button by role alone
await click('loc=role:button');

According to the source code in src/helpers.ts, the makeRoleLocator function generates these strings (e.g., `loc=role:${role}${name}`), allowing you to build role-based locators programmatically when the accessible name is dynamic.

Visible Text Matching

You can locate elements by their rendered text content using either the text= shortcut or the explicit loc=text: prefix. This performs an exact string match against the element's visible text.

// Click element displaying exactly "Log In"
await click('text=Log In');

// Alternative syntax
await click('loc=text:Log In');

If no prefix is provided and the string does not match CSS selector patterns, src/element-resolver.ts falls back to treating the value as a text query.

Label-Based Location

Form controls are often best identified by their associated <label> text. The loc=label: prefix queries the accessibility tree to find the input element associated with the specified label text.

// Type into the input associated with the label "Email"
await type('loc=label:Email', 'user@example.com');

// Select a checkbox labeled "I agree to terms"
await click('loc=label:I agree to terms');

This strategy is implemented in src/element-resolver.ts, which maps the label string back to the corresponding form control via accessibility tree relationships.

Href and Snapshot References

Two additional strategies handle special cases:

  • loc=href:<url>: Locates anchor elements whose href attribute contains the specified substring. The makeHrefLocator helper in src/helpers.ts constructs these strings.
  • @<numeric-id>: References a previously captured element snapshot directly via src/ref-map.ts, bypassing selector resolution entirely.
// Click a link pointing to /checkout
await click('loc=href:/checkout');

// Reference a previously stored element (snapshot ID 42)
await click('@42');

Internal Resolution Pipeline

When a driver method receives a selector, the resolution flow executes through specific modules:

  1. Prefix Detection: The resolver in src/element-resolver.ts (specifically around line 733 in the resolveSelector routine) inspects the string for loc=, text=, @, or raw CSS patterns.
  2. CDP Translation: Valid locators are translated into Chrome DevTools Protocol queries that return matching DOM node IDs.
  3. Error Classification: Resolution failures are categorized as transient (retryable, such as elements not yet attached) or permanent (invalid selectors), enabling intelligent wait loops in src/driver/waits.ts.

All high-level driver methods documented in src/driver/locator.ts accept this unified selector format, ensuring consistency whether you are clicking, typing, or waiting for elements.

Practical Implementation Examples

The following patterns demonstrate how to combine multiple location strategies within a single automation script:

// 1. Submit a form using role-based location
await click('loc=role:button[Submit Order]');

// 2. Fill credentials using label-based location for readability
await type('loc=label:Username', 'testuser');
await type('loc=label:Password', 'secret123');

// 3. Navigate using href substring
await click('loc:href:/dashboard');

// 4. Handle dynamic content by visible text
await click('text=Accept Cookies');

// 5. Fallback to precise CSS for complex styling checks
await waitFor('loc=css:.notification.success');

Each call ultimately invokes the same low-level evaluation engine in src/element-resolver.ts, ensuring consistent behavior across different location strategies.

Summary

  • ego-lite uses a unified loc= prefix syntax to support multiple element location strategies through one API.
  • CSS selectors use loc=css:, ARIA roles use loc=role:, visible text uses text= or loc=text:, and form labels use loc=label:.
  • The src/element-resolver.ts module centralizes parsing and converts all locator types into CDP queries.
  • src/helpers.ts provides factory functions like makeRoleLocator and makeHrefLocator to build selectors programmatically.
  • Error handling distinguishes between transient and permanent failures to support robust retry logic in wait operations.

Frequently Asked Questions

What is the difference between text= and loc=text:?

Both syntaxes perform an exact match on visible text content. The text= prefix is a shorthand convenience, while loc=text: follows the explicit locator convention used by other strategies. Internally, src/element-resolver.ts normalizes both formats into the same text query operation.

How does role-based selection handle accessible names?

The loc=role: syntax optionally accepts an accessible name in square brackets immediately following the role (e.g., loc=role:button[Submit]). If you omit the brackets, the selector matches any element with that role regardless of its accessible name. The makeRoleLocator function in src/helpers.ts handles the string interpolation when building these locators dynamically.

Can I combine multiple locator strategies in one selector?

No, each selector string must use a single strategy. However, you can chain operations or use fallback logic in your automation code. If you need complex logic (e.g., "find a button by role within a specific CSS container"), you would first locate the container via loc=css:, then use relative navigation or snapshot references (@<id>) to scope subsequent queries.

What happens if an element is not found?

The resolver classifies errors as either transient or permanent. Transient errors (e.g., element not yet in DOM) trigger automatic retries when used with wait utilities in src/driver/waits.ts. Permanent errors (e.g., invalid syntax in loc=css:) throw immediately. This distinction allows your scripts to poll for elements during page transitions without failing on timing issues.

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 →