Implementation Details of getByRole, getByText, and getByLabel in ego-lite
ego-lite implements Playwright-style locator methods through a dual-layer façade system that converts high-level calls into Chrome DevTools Protocol (CDP) queries using internal loc= selector protocols and accessibility tree resolution.
The citrolabs/ego-lite repository provides a browser automation library that mirrors Playwright's ergonomic locator API while leveraging Chrome DevTools Protocol for element resolution. Understanding how getByRole, getByText, and getByLabel translate high-level JavaScript calls into low-level browser commands reveals the architectural patterns that enable reliable element detection across dynamic web applications.
High-Level Page Façade Implementation
The page façade is defined in package/ego-browser/src/helpers.ts (lines 684-735) via the createPageFacade function. This façade exposes the primary locator methods directly on the page object, converting human-readable arguments into internal selector strings.
getByRole and Role-Based Selection
The getByRole method utilizes the role selector function (roleSelector) to construct strings following the protocol loc=role:${role}${name}. When the optional options.name parameter is provided, the implementation appends the name filter to the selector string. This format instructs the runtime to query the browser's accessibility (AX) tree rather than performing DOM traversal, ensuring semantic element resolution that respects ARIA roles.
getByText, getByLabel, and Text-Based Selection
Methods including getByText, getByLabel, getByPlaceholder, getByAltText, and getByTitle share a common implementation through the text selector helper (textSelector) in package/ego-browser/src/helpers.ts (lines 736-756). These generate selectors using the pattern loc=${prefix}:${value}, where prefix corresponds to the search type ("text", "label", etc.). When options.exact is set to true, the value is prefixed with exact: to enforce literal string matching during resolution.
Low-Level Locator Façade and Chaining
For selector chaining scenarios such as page.locator('div').getByRole('button'), ego-lite provides the createLocator function (lines 533-560 in package/ego-browser/src/helpers.ts). This locator façade returns an object exposing the same getter methods as the page façade, but with additional context about the current selector scope.
The scopedSelector function combines the existing selector with the new one, producing an internal scope selector formatted as internal:scope:{base, child}. This indirection allows the runtime to track hierarchical relationships between selectors while maintaining a consistent API surface across both simple and complex traversal operations.
Resolution Mechanics and CDP Integration
When the runtime encounters a role-based selector (containing loc=role:), it delegates resolution to queryRoleBackendNodeIds in package/ego-browser/src/element-resolver.ts (lines 17-30). This function forwards the request to the CDP method queryRoleLocatorBackendNodeIds, which queries the browser's accessibility tree to return matching backend node IDs.
For text-based selectors, the system invokes buildQueryAllExpression to construct a DOM query expression that matches the specified text, label, or attribute content. This dual-path resolution strategy ensures that semantic queries leverage the accessibility tree while content queries execute efficient DOM searches.
Internal Selector Protocol
All ego-lite locator methods eventually produce strings utilizing the loc= protocol for element identification. Role-based selectors use the loc=role: namespace, while text-based selectors use type-specific prefixes like loc=text: or loc=label:. Chained selectors utilize the internal:scope format to encode parent-child relationships.
This protocol abstraction enables auto-waiting semantics and retry-on-transient-error logic by providing a uniform interface that the runtime can parse and execute against the CDP layer, regardless of the original high-level method invoked.
Practical Usage Examples
// Locate a button by its accessible role and name
await page.getByRole('button', { name: 'Submit' }).click();
// Find a paragraph that contains exact text
await page.getByText('Welcome to ego-lite', { exact: true }).innerText();
// Locate an input by its associated <label> text
await page.getByLabel('Email').fill('user@example.com');
// Chain selectors: start from a container, then narrow by role
await page.locator('section#login')
.getByRole('textbox', { name: 'Username' })
.fill('myUser');
Summary
- Dual-layer architecture:
createPageFacadeprovides page-level methods whilecreateLocatorenables chained selector composition throughscopedSelector. - Protocol-based selectors: Internal
loc=strings (loc=role:,loc=text:,loc=label:) decouple API methods from CDP implementation details. - Accessibility tree integration: Role-based lookups use
queryRoleBackendNodeIdsandqueryRoleLocatorBackendNodeIdsfor semantic element resolution. - Text matching flexibility: The
textSelectorhelper supports exact matching via theexact:prefix whenoptions.exactis enabled.
Frequently Asked Questions
How does selector chaining work in ego-lite?
Selector chaining relies on the createLocator façade and the scopedSelector function to combine parent and child selectors into an internal:scope:{base, child} format. This allows expressions like page.locator('div').getByRole('button') to resolve correctly by maintaining the hierarchical relationship between the container element and the target role.
What CDP method does ego-lite use for role-based lookups?
The library uses queryRoleLocatorBackendNodeIds via the accessibility tree to resolve selectors containing loc=role:. This function is called from queryRoleBackendNodeIds in package/ego-browser/src/element-resolver.ts, ensuring that role queries respect ARIA semantics and browser accessibility implementations rather than relying solely on DOM attributes.
How is exact text matching implemented in getByText?
When the exact option is set to true in getByText or similar methods, the textSelector helper prefixes the selector value with exact: (e.g., loc=text:exact:Welcome). This prefix is later interpreted by the query builder to enforce literal string matching rather than substring containment during element resolution.
Where are the locator methods defined in the source code?
The methods are defined in package/ego-browser/src/helpers.ts, specifically within createPageFacade (lines 684-735) for page-level access and createLocator (lines 533-560) for chained locator instances. Resolution logic resides in package/ego-browser/src/element-resolver.ts, while low-level element operations are handled in package/ego-browser/src/driver/locator.ts.
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 →