Locator Strategies Supported by ego-browser and Their Classification in element-resolver.ts
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 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 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.
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.
// 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 assigns kind: "css" and either prefixes with loc=css: for queryAllExpression or passes directly to document.querySelectorAll.
// 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.
// 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 =).
// 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.
// 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.
// 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.
// 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".
// 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".
// 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.
// 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. Role locators trigger Accessibility-Tree resolution rather than DOM queries, making them resilient to markup changes.
// 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.
// 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 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 valuesvalue: The selector bodynthorlast: Optional index modifiersname: 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):
// 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 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 |
Error Classification for Retry Behavior
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:
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 |
Core parser (parseLocator) and resolver with role/DOM branching |
src/locator-query.ts |
JavaScript snippet generation for DOM queries |
src/helpers.ts |
Public API façade (page.locator, page.getByRole) |
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.tsclassifies all locators bykindduringparseLocatorexecution (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 (
transientvspermanent) 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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →