# Locator Strategies Supported by ego-browser and Their Classification in element-resolver.ts

> Discover the 12 locator strategies in ego-browser including CSS, text, and XPath. Learn how element-resolver.ts classifies them for DOM or Accessibility-Tree resolution.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: api-reference
- Published: 2026-08-02

---

**ego-browser supports 12 distinct locator strategies including CSS, text, XPath, role-based accessibility queries, and specialized attribute matchers—all parsed and classified in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) before routing to DOM or Accessibility-Tree resolution paths.**

The `ego-browser` package from `citrolabs/ego-lite` provides a Playwright-compatible locator engine with a flexible syntax for finding elements on web pages. Understanding how [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) parses and classifies these locators is essential for writing reliable browser automation scripts. This guide covers all supported strategies and their internal classification logic.

## Complete List of Locator Strategies

The locator language supports explicit prefixes, implicit fallbacks, and accessibility-first role queries. Here is the full taxonomy used by `parseLocator` in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts).

### Query Locators (Internal)

**Syntax:** `internal:scope:...` or `internal:filter:...` (any string starting with `internal:`)

These are reserved for the test harness itself. The parser assigns `kind: "query"` and routes through `queryAllExpression` for direct DOM evaluation.

```typescript
// Internal harness query
await page.locator('internal:scope=div#app >> internal:filter=text=Submit');

```

### CSS Locators

**Syntax:** `css:<selector>` or plain CSS selectors like `div.foo`

The most common strategy. [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) assigns `kind: "css"` and either prefixes with `loc=css:` for `queryAllExpression` or passes directly to `document.querySelectorAll`.

```typescript
// Explicit CSS prefix
await page.locator('css:button.primary');

// Implicit CSS (no prefix needed)
await page.locator('.header nav ul li');

```

### Href Locators

**Syntax:** `href:<url-path>`

Matches anchor elements (`<a>`) whose `href` attribute contains or equals the specified path. Classified as `kind: "href"` and resolved via `hrefElementsJs`.

```typescript
// Exact path match
await page.locator('href:/checkout').click();

// Partial path
await page.locator('href:/products/').click();

```

### Text Locators

**Syntax:** `text:<text>` or `text=<text>`

Finds elements by their visible text content. Uses `textElementsJs` or `textMatchJs` for fuzzy or exact matching depending on the delimiter (`:` vs `=`).

```typescript
// Partial text match
await page.locator('text:Continue').click();

// Exact text match
await page.locator('text=Add to Cart').click();

```

### Label Locators

**Syntax:** `label:<text>`

Matches `<label>` elements or any element with `aria-label`/`aria-labelledby` matching the text. Resolved through `labelElementsJs` after `kind: "label"` classification.

```typescript
// Find by accessible label
await page.locator('label:Email address').fill('user@example.com');

```

### Placeholder Locators

**Syntax:** `placeholder:<text>`

Targets `<input>` or `<textarea>` elements with matching `placeholder` attribute. Classified as `kind: "placeholder"` and resolved via `attributeElementsJs`.

```typescript
// Find input by placeholder text
await page.locator('placeholder:Search products...').type('laptop');

```

### Alt Attribute Locators

**Syntax:** `alt:<text>`

Matches `<img>` elements or `<input type="image">` with matching `alt` text. Uses `kind: "alt"` and `attributeElementsJs`.

```typescript
// Find image by alt text
await page.locator('alt:Company Logo').screenshot();

```

### Title Attribute Locators

**Syntax:** `title:<text>`

Finds elements with matching `title` attribute. Classified as `kind: "title"`.

```typescript
// Find element by tooltip title
await page.locator('title:View Details').hover();

```

### Test ID Locators

**Syntax:** `testid:<text>`

Matches elements with `data-testid` attribute—preferred for test stability. Assigned `kind: "testid"`.

```typescript
// Preferred test locator
await page.locator('testid:submit-button').click();

```

### XPath Locators

**Syntax:** `xpath:<expression>` or `xpath=<expression>`

Evaluates XPath expressions against the document. Classified as `kind: "xpath"` and executed via `document.evaluate`.

```typescript
// Complex XPath with predicates
await page.locator('xpath=//div[contains(@class,"modal")]//button[not(@disabled)]').click();

```

### Role Locators (Accessibility-First)

**Syntax:** `role:<roleName>[name=<accessibleName>]`

The most significant classification in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts). Role locators trigger **Accessibility-Tree resolution** rather than DOM queries, making them resilient to markup changes.

```typescript
// Role with accessible name
await page.locator('role:button[name=Close]').click();

// Role without name (any button)
await page.locator('role:navigation');

```

### Fallback/Implicit Locators

**Syntax:** Any string not matching above prefixes, containing `/` or `=`

Bare strings become CSS selectors (if `nth` modifier present) or pass through for runtime evaluation. The parser detects `xpath=` substring to route as XPath.

```typescript
// Implicit CSS
await page.locator('form > input[type="email"]');

// Implicit XPath
await page.locator('xpath=//table//tr[td="Total"]');

```

## How element-resolver.ts Classifies Locators

The classification pipeline in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) follows a strict two-phase process: **parsing** (`parseLocator`, lines 14-84) and **resolution** (multiple resolver functions).

### Phase 1: parseLocator Assignment

The `parseLocator` function inspects the input string and returns a structured object with:

- `kind`: One of the 12 classifier values
- `value`: The selector body
- `nth` or `last`: Optional index modifiers
- `name`: For role locators, the accessible name filter

Key extraction logic handles prefix matching in priority order, with internal queries checked first and fallbacks evaluated last.

### Phase 2: Resolution Path Routing

The resolver functions (`resolveLocatorCenter`, `resolveLocatorObjectId`, etc.) branch immediately on `locator.kind`:

**Role locators (exclusive AX path):**

```typescript
// Lines 50-67 and 104-111 in element-resolver.ts
if (locator.kind === "role") {
  // Accessibility-Tree lookup via Chrome DevTools Protocol
  const backendNodeId = await findBackendNodeIdByRoleName(
    locator.value,  // role name
    locator.name    // accessible name filter
  );
  // ...
}

```

**All other locators (DOM/JavaScript path):**

Fall through to `queryAllExpression` in [`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts) or specialized helpers:

| Helper Function | Locator Kinds | Source |
|-----------------|---------------|--------|
| `textElementsJs` | `text` | Runtime string |
| `labelElementsJs` | `label` | Runtime string |
| `hrefElementsJs` | `href` | Runtime string |
| `attributeElementsJs` | `placeholder`, `alt`, `title`, `testid` | Runtime string |
| `queryAllExpression` | `css`, `query`, `xpath` | [`locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator-query.ts) |

### Error Classification for Retry Behavior

[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) wraps failures in `ElementResolutionError` with granularity from `matchCountKind` (lines 46-50):

- **"transient"** errors: Zero matches or ambiguous matches—trigger automatic retry with timeout
- **"permanent"** errors: Invalid syntax or structural impossibility—fail immediately

This classification enables robust waiting semantics without explicit `waitFor` calls.

## Practical Code Examples

Combining multiple strategies in a single script:

```typescript
import { ego } from 'ego-browser';

const browser = await ego.launch();
const page = await browser.newPage();

// 1. CSS: Standard element selection
await page.locator('css:#login-form').waitFor();

// 2. Text: User-visible content
await page.locator('text=Accept Cookies').click();

// 3. Role: Accessibility-based (most stable)
await page.locator('role:button[name=Sign In]').click();

// 4. Test ID: Data attribute for testability
await page.locator('testid:product-card-123').screenshot();

// 5. XPath: Complex structural queries
const price = await page.locator('xpath=//tr[th="Total"]/td').textContent();

// 6. Href: Navigation link verification
await expect(page.locator('href:/dashboard')).toBeVisible();

// 7. Placeholder: Form field by hint text
await page.locator('placeholder:Enter discount code').fill('SAVE20');

// 8. Chaining with nth modifier
await page.locator('css:.item >> nth=2').click();

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) | Core parser (`parseLocator`) and resolver with role/DOM branching |
| [`src/locator-query.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/locator-query.ts) | JavaScript snippet generation for DOM queries |
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Public API façade (`page.locator`, `page.getByRole`) |
| [`src/driver/locator.js`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.js) | Playwright-compatible low-level implementation |

## Summary

- **ego-browser exposes 12 locator strategies** ranging from standard CSS to accessibility-first role queries and specialized attribute matchers
- **[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) classifies all locators by `kind`** during `parseLocator` execution (lines 14-84)
- **Role locators receive special treatment**: routed to Accessibility-Tree resolution via `findBackendNodeIdByRoleName`, while all others use DOM-based JavaScript evaluation
- **Error classification (`transient` vs `permanent`)** drives automatic retry behavior without explicit waits
- **Test ID and Role locators** provide the most stable selectors for resilient automation

## Frequently Asked Questions

### What is the most stable locator strategy for ego-browser?

**Role-based locators (`role:<name>`) are most stable** because they query the browser's Accessibility Tree rather than DOM structure. They survive refactoring as long as accessibility semantics remain intact. Test ID locators (`testid:<value>`) are the best DOM-based alternative when you control the application markup.

### How does ego-browser handle ambiguous matches?

When `parseLocator` extracts an element but multiple matches exist, [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) applies the `matchCountKind` logic (lines 46-50). If the error is classified **"transient"** (typical for zero or multiple matches), the resolver automatically retries until timeout. For **"permanent"** errors like invalid syntax, it fails immediately.

### Can I combine multiple locator strategies in one query?

**Yes, through chaining syntax.** The `internal:scope` and `internal:filter` prefixes enable composition, and the `>>` operator separates locator stages. For example: `css:nav >> text=Products >> nth=0` finds navigation, filters by text, then takes the first match. The parser in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) handles these composite structures recursively.

### What is the performance difference between role and CSS locators?

**Role locators have higher latency** because they require an asynchronous round-trip to query the Accessibility Tree via Chrome DevTools Protocol (`findBackendNodeIdByRoleName`). CSS and text locators execute entirely in JavaScript within the page context, making them faster for simple selections. Use role locators for stability, CSS for speed when markup is predictable.