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

> Learn to locate elements on a page using CSS roles text or labels with ego-lites unified loc selector syntax for efficient web testing.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-21

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) file contains factory functions that construct these locator strings programmatically, while [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/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.

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) constructs these strings.
- **`@<numeric-id>`**: References a previously captured element snapshot directly via [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts), bypassing selector resolution entirely.

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts).

All high-level driver methods documented in [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/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:

```javascript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)** module centralizes parsing and converts all locator types into CDP queries.
- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.