# ego-browser Element Locator Strategies: XPath, ARIA Roles, Text Matchers, and More

> Discover ego-browser's advanced element locator strategies beyond CSS selectors, including XPath, ARIA roles, and text matchers, to enhance your test automation.

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

---

**`ego-browser` supports over a dozen element locator strategies beyond CSS selectors, including XPath expressions, semantic text matchers, ARIA role queries, and internal scoping filters, all parsed in [`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts).**

The `ego-browser` library (part of the `citrolabs/ego-lite` ecosystem) provides a robust element resolution system that extends far beyond standard CSS selectors. When you call `page.locator()` or any `getBy...` helper, the runtime parses your query string using a sophisticated engine defined in **[`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts)** (lines 47–114) to determine which DOM traversal strategy to execute. Understanding these **element locator strategies** allows you to write more resilient and accessible browser automation scripts.

## Core Locator Syntaxes

The parser in **[`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts)** inspects selector strings for specific prefixes to determine the resolution strategy. If no prefix matches, the engine falls back to standard `querySelectorAll` behavior at line 116.

### XPath Expressions

Use the `xpath=` prefix to evaluate full XPath expressions via `document.evaluate`.

```javascript
await page.locator('xpath=//button[contains(., "Submit")]').click();

```

### Explicit CSS Prefixing

While raw CSS selectors work by default, you can explicitly prefix them with `loc=css:` for clarity or programmatic construction.

### URL-Based Matching

The `loc=href:` strategy targets anchor elements whose resolved `href` attribute matches a specific path or URL.

```javascript
await page.locator('loc=href:/products/123').click();

```

## Semantic and Accessible Locators

These strategies prioritize user-facing attributes and accessibility properties over implementation details, reducing test fragility.

### Text Content Matching

Match elements based on visible text content using either fuzzy or exact matching syntax.

**Fuzzy matching** uses the `loc=text:` prefix:

```javascript
await page.locator('loc=text:Welcome').waitFor();

```

**Exact matching** uses the `text=` syntax:

```javascript
await page.locator('text="Exact title"').click();

```

### Label Association

Queries for `<label>` elements or associated form controls using accessible label text. The `page.getByLabel()` helper constructs a `loc=label:` prefixed string internally.

```javascript
await page.getByLabel('Email address').fill('user@example.com');

```

### Placeholder Attributes

Targets `<input>` or `<textarea>` elements by their placeholder text via the `loc=placeholder:` prefix.

```javascript
await page.getByPlaceholder('Search…').type('ego-browser');

```

### Alt Text and Title Attributes

Match images and interactive elements by descriptive attributes using `loc=alt:` and `loc=title:` prefixes.

```javascript
await page.getByAltText('Company logo').click();
await page.getByTitle('Help').click();

```

### Test ID Attributes

Locate elements by the `data-testid` attribute using the `loc=testid:` prefix, a common pattern for test automation stability.

```javascript
await page.getByTestId('submit-button').click();

```

### ARIA Roles

Select elements by their explicit or implicit ARIA role using `loc=role:`, optionally filtering by accessible name.

```javascript
await page.getByRole('button', { name: /confirm/i }).click();

```

## Advanced Composition Strategies

For complex DOM hierarchies, ego-browser provides internal scoping and filtering mechanisms defined in **[`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts)** that operate on intermediate result sets.

### Internal Scoping

The `internal:scope:` compound selector first resolves a base selector, then searches for child elements within each base context. This is useful for nested component structures.

```javascript
// Find all <section> elements, then within each, find the first <h2>
await page.locator('internal:scope:{"base":"section","child":"loc=css:h2"}').first().click();

```

### Internal Filtering

Apply additional constraints such as `hasText`, `hasNotText`, `has`, or `hasNot` to refine a base selector without additional round-trips.

```javascript
await page.locator('internal:filter:{"base":"div.item","hasText":{"text":"Active"}}').count();

```

### Nth-Element Selection

Retrieve specific indices from a result set using `internal:nth=` or grab the final element with `internal:last` to target specific positions in a list.

## Implementation Architecture

The locator system spans several key files in the `citrolabs/ego-lite` repository:

- **[`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts)**: Core parser that translates prefixed strings into executable DOM query plans (lines 47–114).
- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)**: Exposes public helper methods including `locator()`, `getByRole()`, `getByText()`, and others that construct the prefixed selector strings internally.
- **[`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts)**: Low-level driver interface that receives resolved selector strings and coordinates execution.
- **[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)**: Executes the final query expressions against the DOM, handling the actual DOM traversal and element resolution.

## Summary

- ego-browser supports **12+ distinct locator strategies** ranging from XPath to ARIA roles, all managed via [`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts).
- **Accessibility-first selectors** (label, placeholder, alt text, title) reduce reliance on fragile CSS classes or IDs.
- **Internal scoping and filtering** enable complex hierarchical queries without multiple round-trips to the browser.
- The parser falls back to standard `querySelectorAll` at line 116 when no special prefix is detected, ensuring backward compatibility with standard CSS selectors.

## Frequently Asked Questions

### How do I use XPath selectors in ego-browser?

Prefix your selector string with `xpath=` followed by a valid XPath expression. The engine uses `document.evaluate` to resolve these queries. For example: `await page.locator('xpath=//div[@class="content"]').click();`.

### What is the difference between `loc=text:` and `text=` syntax?

The `loc=text:` prefix performs fuzzy text matching, finding elements that contain the specified text substring. The `text=` syntax (without the `loc=` prefix) performs exact string matching, requiring the element's full text content to match precisely, and is typically used with quoted values like `text="Exact String"`.

### Can I combine multiple locator strategies in a single query?

Yes, using the `internal:scope:` and `internal:filter:` compound selectors. The scoping strategy allows you to nest selectors (finding child elements within parent contexts), while the filtering strategy applies constraints like `hasText` or `hasNot` to refine results from a base selector.

### What happens if my selector prefix is not recognized?

If the parser in [`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts) does not recognize a prefix (lines 47–114), the selector falls back to a standard `querySelectorAll` call at line 116. This means unprefixed strings are treated as standard CSS selectors, maintaining compatibility with traditional browser automation patterns.