How Ego-Browser Element Refs (`@N` Format) Work and Why Re‑snapshot Is Required
Ego-browser @N element references are numeric snapshot IDs tied to Chromium DevTools backendNodeIds that require automatic re-snapshoting when the DOM changes to prevent stale node errors.
The ego-lite browser automation library uses a compact @N syntax to reference DOM elements without brittle CSS selectors. This article explains how these references are created, resolved, and automatically refreshed through the re-snapshot mechanism.
What Are @N Element References?
Ego-browser represents DOM elements with snapshot references — short strings like @12. These refs are numeric IDs that correspond to the Chromium DevTools Protocol's backendNodeId for a node at the moment a snapshot is taken.
The @ prefix distinguishes snapshot refs from other selector types. You can also use the equivalent ref=12 syntax. Both map to internal numeric keys stored in a runtime RefMap.
How @N References Are Created
When the runtime takes a snapshot of the page, it walks the accessibility tree and stores each node in a RefMap located at package/ego-browser/src/ref-map.ts.
For every node, ref-map.ts records:
this.map.set(refId, {
backendNodeId, // the CDP node identifier
role,
name,
nth,
selector: undefined,
frameId,
});
The public helper parseRef (same file) turns the user-visible string @12 or ref=12 into the numeric key "12":
export function parseRef(input) {
const trimmed = String(input || "").trim();
for (const candidate of [
trimmed.startsWith("@") ? trimmed.slice(1) : null,
trimmed.startsWith("ref=") ? trimmed.slice(4) : null,
trimmed,
]) {
if (candidate && /^\d+$/.test(candidate)) {
return candidate; // → "12"
}
}
return null;
}
This implementation accepts three input formats and validates that the result contains only digits.
Resolving References to DOM Nodes
Helper functions like page.elementCenter('@12') call the resolver in src/element-resolver.ts, which looks up the ID in the current RefMap.
If the map contains the entry, the resolver obtains the backendNodeId and performs CDP actions such as:
- Clicking elements
- Getting bounding boxes
- Capturing screenshots
Why Re‑snapshot Is Required
The snapshot map only reflects the DOM as it existed at the snapshot moment. Navigation, DOM updates, or a page reload can invalidate those IDs.
When a helper tries to resolve a ref and the map is empty or the ID is missing, the resolver treats this as a "transient" failure and automatically triggers a new snapshot.
This auto-re-snapshot rebuilds the RefMap, repopulating it with fresh backendNodeIds that match the current page state. Consequently, the same @N syntax continues to work across separate script rounds without the agent needing to remember to take an explicit snapshot.
Practical Usage Examples
// Take a screenshot of an element using its snapshot ref
await page.screenshot({ path: 'logo.png', selector: '@7' });
// Click the center of an element referenced by @12
await page.click('@12');
// Get the bounding box of a node — re-snapshot happens automatically if needed
const box = await page.boundingBox('@3'); // If the map is stale, ego-browser
// will capture a fresh snapshot first
Design Rationale
The @N reference system provides three key advantages:
- Terseness — Numeric refs keep the public API compact and readable
- Stability — References remain valid for the duration of a round
- Transparency — Automatic re-snapshot abstracts DOM churn, preventing stale node errors
This architecture enables long-running agents that may pause between actions without requiring manual snapshot management.
Summary
@Nandref=Nsyntax both resolve to numeric keys in theRefMapparseRefinsrc/ref-map.tshandles input parsing with flexible prefix support- References map to Chromium DevTools
backendNodeIdvalues captured at snapshot time - The resolver in
src/element-resolver.tstriggers automatic re-snapshot when IDs are missing or stale - This design ensures agents always operate on current DOM state without explicit synchronization
Frequently Asked Questions
What happens if I use an @N ref after the page reloads?
The resolver detects the missing ID in RefMap, treats it as a transient failure, and automatically captures a fresh snapshot before retrying the operation. Your script continues without manual intervention.
Can I mix @N refs with other selector types?
Yes. src/element-resolver.ts handles multiple selector formats including @N, loc= (accessibility locators), and xpath= expressions. The resolver routes each to the appropriate resolution path.
Where is the automatic re-snapshot logic implemented?
The re-snapshot trigger lives in src/element-resolver.ts, which coordinates with the RefMap in src/ref-map.ts. This separation keeps reference storage decoupled from resolution retry logic.
Why does ego-browser use backendNodeId instead of stable selectors?
backendNodeId provides direct Chromium DevTools Protocol integration with minimal overhead. The automatic re-snapshot mechanism compensates for the ID's transient nature, offering both performance and reliability.
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 →