How to Use Snapshot References (backendNodeId) for Element Interaction in Ego Lite
Ego Lite leverages numeric backendNodeId identifiers from Chrome’s Accessibility (AX) tree to create stable snapshot references (e.g., @21) that enable direct, high-performance element interaction without expensive DOM queries.
The citrolabs/ego-lite browser automation framework captures DOM snapshots as lightweight numeric references tied to Chrome's internal node identifiers. This approach eliminates the fragility of CSS selectors by storing backendNodeId values—the same identifiers Chrome DevTools Protocol (CDP) uses to address elements directly—enabling faster, more stable automation scripts.
Understanding Snapshot References and backendNodeId
When Ego Lite captures a page snapshot via Accessibility.getFullAXTree, it generates snapshot references that map human-readable strings like @21 to Chrome's internal backendNodeId integers. These identifiers point directly to nodes in the browser's accessibility tree, providing a stable addressing mechanism that survives DOM mutations until the element is removed or recreated.
The reference lifecycle follows four distinct phases:
- Snapshot Capture – The runtime invokes
Accessibility.getFullAXTreeto build the AX tree and extractbackendDOMNodeIdvalues from each node. - Reference Storage – The
RefMapclass insrc/ref-map.tspersists mappings between reference strings (e.g.,@21),backendNodeIdintegers, element roles, and accessibility names. - Direct Resolution – When a helper receives a reference like
@21, theparseReffunction (lines 44-53 insrc/ref-map.ts) extracts the numeric identifier for lookup. - Stale Recovery – If the stored
backendNodeIdno longer exists, the system falls back to role/name-based lookup viafindBackendNodeIdByRoleName.
Resolving Elements from References
The src/element-resolver.ts file contains the core logic for transforming snapshot references into actionable element handles. Two primary functions handle different interaction requirements:
resolveElementCenter (lines 63-104): Converts a reference into screen coordinates by first looking up the stored backendNodeId, then calling DOM.getBoxModel via CDP to calculate the element's center point. If the node is stale, it triggers findBackendNodeIdByRoleName to refresh the mapping.
resolveElementObjectId (lines 149-202): Returns a CDP runtime object ID required for event dispatch. This function queries the RefMap for the backendNodeId, then executes DOM.resolveNode to obtain a live object reference, falling back to accessibility tree traversal on failure.
Both functions rely on parseRef to normalize input formats including @N, ref=N, or plain numeric strings.
Practical Implementation Examples
The following examples demonstrate how to interact with elements using snapshot references in Ego Lite scripts. These helpers are automatically injected into the runtime environment.
Clicking Elements by Reference
Use the click helper to dispatch mouse events via stored backend node IDs:
// Target element from previous snapshot with reference @21
const { click } = ego;
await click('@21');
Internally, click invokes resolveElementObjectId, which parses @21 through parseRef, retrieves the entry from RefMap, and calls DOM.resolveNode with the stored backendNodeId. The resulting object ID is passed to Input.dispatchMouseEvent to execute the click.
Retrieving Element Coordinates
Extract precise center coordinates for visual validation or custom event dispatch:
const { getCenter } = ego;
const { x, y } = await getCenter('@42');
console.log(`Element center at (${x}, ${y})`);
This flows through resolveElementCenter, which uses DOM.getBoxModel on the stored backendNodeId, then calculates the center via boxModelCenter (lines 53-68 in src/element-resolver.ts).
Handling Stale References
When page navigation or DOM updates invalidate stored identifiers, force a fresh snapshot:
// Reference @33 may be stale after previous actions
await ego.waitForRef('@33'); // Triggers new snapshot if needed
await ego.click('@33'); // Safe execution on fresh backendNodeId
The waitForRef method guarantees that RefMap contains a valid entry before proceeding, preventing errors from stale backendNodeId values.
Manual Reference Management
For custom tooling scenarios, manually inject references into the map:
// Directly register a backendNodeId with custom reference '99'
ego.refMap.add('99', 123456, 'button', 'Submit');
await ego.click('ref=99');
The RefMap.add method (lines 8-10 in src/ref-map.ts) stores the mapping immediately, allowing subsequent helper calls to resolve the reference through standard CDP methods.
Key Source Files in citrolabs/ego-lite
The snapshot reference system spans four critical files in the repository:
src/ref-map.ts: Implements theRefMapclass andparseRefutility for reference string normalization and storage.src/element-resolver.ts: ContainsresolveElementCenter,resolveElementObjectId, andfindBackendNodeIdByRoleNamefor CDP-based element resolution.src/helpers.ts: Exposes the public API (click,getCenter,waitForRef) that orchestrates resolution and interaction.src/browser-runtime.ts: Manages snapshot creation viaAccessibility.getFullAXTreeand populates theRefMapwith freshbackendNodeIdvalues.
Summary
- Snapshot references in Ego Lite map strings like
@21to Chrome's internalbackendNodeIdintegers from the accessibility tree. - The RefMap class (
src/ref-map.ts) persists these mappings, while parseRef normalizes input formats. - Element resolution occurs through CDP methods
DOM.getBoxModelandDOM.resolveNode, providing direct access without DOM querying. - Automatic fallback to role/name-based lookup occurs when
backendNodeIdvalues become stale, ensuring robust test execution. - References remain valid across multiple actions until the DOM node is recreated or removed, offering superior performance compared to CSS selectors.
Frequently Asked Questions
What is a backendNodeId in Chrome DevTools Protocol?
The backendNodeId is a numeric identifier assigned to each node in Chrome's internal accessibility (AX) tree representation. Unlike DOM IDs, these integers are stable for the lifetime of the node and allow direct addressing via CDP methods like DOM.resolveNode and DOM.getBoxModel without requiring CSS selector evaluation or JavaScript execution in the target page.
How does Ego Lite handle stale snapshot references?
When a stored backendNodeId no longer resolves (typically due to DOM removal or re-creation), Ego Lite triggers fallback resolution via findBackendNodeIdByRoleName in src/element-resolver.ts (lines 96-124). This function re-queries the full AX tree using Accessibility.getFullAXTree, then matches nodes by their accessibility role and name to locate the new backendDOMNodeId, effectively refreshing the reference mapping.
What performance benefits do snapshot references provide?
Snapshot references eliminate the runtime overhead of CSS selector parsing, DOM traversal, and repeated accessibility tree queries during multi-step interactions. Because backendNodeId values provide direct handles to internal browser structures, subsequent actions like clicks and coordinate lookups execute via lightweight CDP calls rather than expensive DOM operations, significantly reducing latency in automation sequences.
Can snapshot references persist across page navigations?
No, backendNodeId values are tied to specific document lifecycles and invalidate when navigating to new pages. Ego Lite provides the waitForRef helper to regenerate snapshots after navigation, ensuring fresh references for the new document context. The framework automatically detects stale identifiers and can refresh them using role/name matching when possible, though best practice involves re-capturing snapshots after significant DOM changes.
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 →