From 3254746aadd2dba655865fcd6242b16ece9e873e Mon Sep 17 00:00:00 2001 From: Chromie Bot Date: Wed, 31 Dec 2025 00:10:45 +0000 Subject: [PATCH 1/4] test: add failing test for page.snapshot method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test validates the new page.snapshot() method that exposes captureHybridSnapshot functionality publicly, allowing users to capture a hybrid DOM + Accessibility snapshot of the page. Tests include: - Basic snapshot with combined tree and maps - Scoped snapshot with CSS focusSelector - Scoped snapshot with XPath focusSelector - Per-frame data availability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../core/lib/v3/tests/page-snapshot.spec.ts | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 packages/core/lib/v3/tests/page-snapshot.spec.ts diff --git a/packages/core/lib/v3/tests/page-snapshot.spec.ts b/packages/core/lib/v3/tests/page-snapshot.spec.ts new file mode 100644 index 000000000..414343cd8 --- /dev/null +++ b/packages/core/lib/v3/tests/page-snapshot.spec.ts @@ -0,0 +1,138 @@ +import { test, expect } from "@playwright/test"; +import { V3 } from "../v3"; +import { v3TestConfig } from "./v3.config"; + +test.describe("Page.snapshot", () => { + let v3: V3; + + test.beforeEach(async () => { + v3 = new V3(v3TestConfig); + await v3.init(); + }); + + test.afterEach(async () => { + await v3?.close?.().catch(() => {}); + }); + + test("returns a hybrid snapshot with combined tree and maps", async () => { + const page = v3.context.pages()[0]; + + const html = ` + + + Snapshot Test + +

Hello World

+ + Link + + + `; + + await page.goto("data:text/html," + encodeURIComponent(html)); + + // Call the new snapshot method + const snapshot = await page.snapshot(); + + // Verify structure matches HybridSnapshot type + expect(snapshot).toBeDefined(); + expect(typeof snapshot.combinedTree).toBe("string"); + expect(typeof snapshot.combinedXpathMap).toBe("object"); + expect(typeof snapshot.combinedUrlMap).toBe("object"); + + // The combined tree should contain our page content + expect(snapshot.combinedTree).toContain("Hello World"); + expect(snapshot.combinedTree).toContain("Submit"); + expect(snapshot.combinedTree).toContain("Link"); + + // XPath map should have entries + expect(Object.keys(snapshot.combinedXpathMap).length).toBeGreaterThan(0); + + // URL map should contain the link URL + expect(Object.values(snapshot.combinedUrlMap)).toContain( + "https://example.com/", + ); + }); + + test("supports focusSelector option to scope the snapshot", async () => { + const page = v3.context.pages()[0]; + + const html = ` + + + Scoped Snapshot Test + +
Outside Content
+
+

Main Heading

+

Main paragraph

+
+ + + `; + + await page.goto("data:text/html," + encodeURIComponent(html)); + + // Snapshot with focusSelector should scope to that element + const scopedSnapshot = await page.snapshot({ + focusSelector: "#main-content", + }); + + expect(scopedSnapshot).toBeDefined(); + expect(scopedSnapshot.combinedTree).toContain("Main Heading"); + expect(scopedSnapshot.combinedTree).toContain("Main paragraph"); + }); + + test("supports XPath focusSelector", async () => { + const page = v3.context.pages()[0]; + + const html = ` + + + +
+ Target Text +
+ + + `; + + await page.goto("data:text/html," + encodeURIComponent(html)); + + // Use XPath-style focusSelector + const snapshot = await page.snapshot({ + focusSelector: "//div[@id='container']", + }); + + expect(snapshot).toBeDefined(); + expect(snapshot.combinedTree).toContain("Target Text"); + }); + + test("returns perFrame data when available", async () => { + const page = v3.context.pages()[0]; + + const html = ` + + + +

Simple page

+ + + `; + + await page.goto("data:text/html," + encodeURIComponent(html)); + + const snapshot = await page.snapshot(); + + // perFrame should be present and contain at least one frame entry + expect(snapshot.perFrame).toBeDefined(); + expect(Array.isArray(snapshot.perFrame)).toBe(true); + expect(snapshot.perFrame!.length).toBeGreaterThanOrEqual(1); + + const mainFrame = snapshot.perFrame![0]; + expect(mainFrame.frameId).toBeDefined(); + expect(typeof mainFrame.outline).toBe("string"); + expect(typeof mainFrame.xpathMap).toBe("object"); + expect(typeof mainFrame.urlMap).toBe("object"); + }); +}); From 881efcd500b8609ec7015ccc588306b1d24f03d6 Mon Sep 17 00:00:00 2001 From: Chromie Bot Date: Wed, 31 Dec 2025 00:10:54 +0000 Subject: [PATCH 2/4] feat: expose page.snapshot() method for hybrid snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new public page.snapshot() method that exposes the internal captureHybridSnapshot functionality, allowing users to capture a hybrid DOM + Accessibility snapshot of the page. The method returns a HybridSnapshot containing: - combinedTree: Merged outline across every frame - combinedXpathMap: EncodedId -> absolute XPath mapping - combinedUrlMap: EncodedId -> URL extracted from AX properties - perFrame: Per-frame payloads with original relative data Options include: - focusSelector: Filter to a specific element/subtree (XPath or CSS) - pierceShadow: Pierce shadow DOM (default: true) - experimental: Enable experimental AX traversal tweaks Also exports HybridSnapshot, SnapshotOptions, and PerFrameSnapshot types publicly for TypeScript users. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/core/lib/v3/types/public/page.ts | 7 +++++++ packages/core/lib/v3/understudy/page.ts | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/core/lib/v3/types/public/page.ts b/packages/core/lib/v3/types/public/page.ts index f141f3d67..3273740c7 100644 --- a/packages/core/lib/v3/types/public/page.ts +++ b/packages/core/lib/v3/types/public/page.ts @@ -11,3 +11,10 @@ export type { ConsoleListener } from "../../understudy/consoleMessage"; export type LoadState = "load" | "domcontentloaded" | "networkidle"; export { Response } from "../../understudy/response"; + +// Snapshot types for page.snapshot() method +export type { + HybridSnapshot, + SnapshotOptions, + PerFrameSnapshot, +} from "../private/snapshot"; diff --git a/packages/core/lib/v3/understudy/page.ts b/packages/core/lib/v3/understudy/page.ts index 6943583b4..081322739 100644 --- a/packages/core/lib/v3/understudy/page.ts +++ b/packages/core/lib/v3/understudy/page.ts @@ -7,7 +7,8 @@ import { CdpConnection } from "./cdp"; import { Frame } from "./frame"; import { FrameLocator } from "./frameLocator"; import { deepLocatorFromPage } from "./deepLocator"; -import { resolveXpathForLocation } from "./a11y/snapshot"; +import { resolveXpathForLocation, captureHybridSnapshot } from "./a11y/snapshot"; +import type { HybridSnapshot, SnapshotOptions } from "../types/private"; import { FrameRegistry } from "./frameRegistry"; import { executionContexts } from "./executionContextRegistry"; import { LoadState } from "../types/public/page"; @@ -2066,6 +2067,26 @@ export class Page { .map((c) => c.substring(0, c.length - 1)); } + // ---- Snapshot API ---- + + /** + * Capture a hybrid DOM + Accessibility snapshot of the page. + * + * Returns a combined tree representation with XPath and URL maps for every + * element. This is the same snapshot format used internally by act/extract/observe. + * + * @param options Optional configuration for the snapshot capture. + * @param options.focusSelector Filter the snapshot to a specific element/subtree. + * Supports XPath (prefixed with `xpath=` or starting with `/`) and CSS with iframe hops via `>>`. + * @param options.pierceShadow Pierce shadow DOM when walking the document (default: true). + * @param options.experimental Enable experimental traversal tweaks in the Accessibility layer. + * @returns A HybridSnapshot containing the combined tree, XPath map, URL map, and per-frame data. + */ + @logAction("Page.snapshot") + async snapshot(options?: SnapshotOptions): Promise { + return captureHybridSnapshot(this, options); + } + // ---- Page-level lifecycle waiter that follows main frame id swaps ---- /** Resolve the main-world execution context for the current main frame. */ From 3a88c094b44764bd0006d505e5aef258a4fd3086 Mon Sep 17 00:00:00 2001 From: Chromie Bot Date: Wed, 31 Dec 2025 15:26:24 +0000 Subject: [PATCH 3/4] docs: add page.snapshot() to v3 reference documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds comprehensive documentation for the page.snapshot() method to the v3 page reference docs, including: - Method signature and description - All SnapshotOptions parameters (focusSelector, pierceShadow, experimental) - Return type documentation (HybridSnapshot, PerFrameSnapshot) - Example usage code - Type definitions in the Types section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/docs/v3/references/page.mdx | 126 +++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/packages/docs/v3/references/page.mdx b/packages/docs/v3/references/page.mdx index bda6f5274..c315110a8 100644 --- a/packages/docs/v3/references/page.mdx +++ b/packages/docs/v3/references/page.mdx @@ -388,6 +388,94 @@ await page.screenshot(options?: ScreenshotOptions): Promise **Returns:** A `Promise` containing the screenshot image data. +## Snapshot + +### snapshot() + +Capture a hybrid DOM + Accessibility snapshot of the page. This method exposes Stagehand's internal page representation, which combines DOM structure with accessibility tree information across all frames. + +```typescript +await page.snapshot(options?: SnapshotOptions): Promise +``` + + + Filter the snapshot to a specific element/subtree using a selector. + Supports XPath (prefixed with `xpath=` or starting with `/`) and CSS selectors. + Can cross iframes using the `>>` syntax. + + + + Pierce shadow DOM when capturing the DOM structure. + + **Default:** `true` + + + + Enable experimental accessibility tree traversal tweaks. + + **Default:** `false` + + +**Returns:** A `Promise` containing the snapshot data. + +#### HybridSnapshot + +The returned snapshot object contains: + +```typescript +interface HybridSnapshot { + /** Merged accessibility tree outline across every frame */ + combinedTree: string; + /** EncodedId -> absolute XPath mapping */ + combinedXpathMap: Record; + /** EncodedId -> URL extracted from accessibility properties */ + combinedUrlMap: Record; + /** Per-frame payloads with original relative data (for debugging) */ + perFrame?: PerFrameSnapshot[]; +} +``` + +#### PerFrameSnapshot + +Each per-frame snapshot contains: + +```typescript +interface PerFrameSnapshot { + frameId: string; + outline: string; + xpathMap: Record; + urlMap: Record; +} +``` + +#### Example Usage + +```typescript +import { Stagehand } from "@browserbasehq/stagehand"; +import type { HybridSnapshot } from "@browserbasehq/stagehand"; + +const stagehand = new Stagehand({ env: "LOCAL" }); +await stagehand.init(); +const page = stagehand.context.pages()[0]; + +await page.goto("https://example.com"); + +// Capture full page snapshot +const snapshot: HybridSnapshot = await page.snapshot(); +console.log("Accessibility tree:", snapshot.combinedTree); +console.log("XPath mappings:", Object.keys(snapshot.combinedXpathMap).length); + +// Capture snapshot focused on a specific element +const focusedSnapshot = await page.snapshot({ + focusSelector: "//main", +}); + +// Capture without piercing shadow DOM +const noShadowSnapshot = await page.snapshot({ + pierceShadow: false, +}); +``` + ## Viewport ### setViewportSize() @@ -676,6 +764,44 @@ interface ScreenshotOptions { Matches Playwright's screenshot signature with sensible defaults to control how a capture is produced. +### SnapshotOptions + +```typescript +interface SnapshotOptions { + focusSelector?: string; + pierceShadow?: boolean; + experimental?: boolean; +} +``` + +Options for capturing hybrid snapshots with `page.snapshot()`. + +### HybridSnapshot + +```typescript +interface HybridSnapshot { + combinedTree: string; + combinedXpathMap: Record; + combinedUrlMap: Record; + perFrame?: PerFrameSnapshot[]; +} +``` + +The result of `page.snapshot()`, containing merged accessibility tree data across all frames. + +### PerFrameSnapshot + +```typescript +interface PerFrameSnapshot { + frameId: string; + outline: string; + xpathMap: Record; + urlMap: Record; +} +``` + +Per-frame snapshot data for debugging and advanced use cases. + ## Error Handling Page methods may throw the following errors: From de6bd5f7e022a9ccebcb50d193ffd42e490fe6d1 Mon Sep 17 00:00:00 2001 From: Chromie Bot Date: Wed, 31 Dec 2025 15:37:01 +0000 Subject: [PATCH 4/4] docs: simplify page.snapshot() documentation to match v3 reference style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce verbosity of page.snapshot() docs to match the overall level of detail in the v3 page reference documentation: - Shortened method description - Simplified parameter descriptions - Removed experimental parameter documentation - Removed inline interface definitions (kept in Types section) - Removed separate example code block - Simplified type definitions in Types section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/docs/v3/references/page.mdx | 91 +--------------------------- 1 file changed, 3 insertions(+), 88 deletions(-) diff --git a/packages/docs/v3/references/page.mdx b/packages/docs/v3/references/page.mdx index c315110a8..17525061a 100644 --- a/packages/docs/v3/references/page.mdx +++ b/packages/docs/v3/references/page.mdx @@ -392,90 +392,24 @@ await page.screenshot(options?: ScreenshotOptions): Promise ### snapshot() -Capture a hybrid DOM + Accessibility snapshot of the page. This method exposes Stagehand's internal page representation, which combines DOM structure with accessibility tree information across all frames. +Capture a hybrid DOM + Accessibility snapshot of the page. ```typescript await page.snapshot(options?: SnapshotOptions): Promise ``` - Filter the snapshot to a specific element/subtree using a selector. - Supports XPath (prefixed with `xpath=` or starting with `/`) and CSS selectors. - Can cross iframes using the `>>` syntax. + Filter the snapshot to a specific element using a selector. - Pierce shadow DOM when capturing the DOM structure. + Pierce shadow DOM when capturing. **Default:** `true` - - Enable experimental accessibility tree traversal tweaks. - - **Default:** `false` - - **Returns:** A `Promise` containing the snapshot data. -#### HybridSnapshot - -The returned snapshot object contains: - -```typescript -interface HybridSnapshot { - /** Merged accessibility tree outline across every frame */ - combinedTree: string; - /** EncodedId -> absolute XPath mapping */ - combinedXpathMap: Record; - /** EncodedId -> URL extracted from accessibility properties */ - combinedUrlMap: Record; - /** Per-frame payloads with original relative data (for debugging) */ - perFrame?: PerFrameSnapshot[]; -} -``` - -#### PerFrameSnapshot - -Each per-frame snapshot contains: - -```typescript -interface PerFrameSnapshot { - frameId: string; - outline: string; - xpathMap: Record; - urlMap: Record; -} -``` - -#### Example Usage - -```typescript -import { Stagehand } from "@browserbasehq/stagehand"; -import type { HybridSnapshot } from "@browserbasehq/stagehand"; - -const stagehand = new Stagehand({ env: "LOCAL" }); -await stagehand.init(); -const page = stagehand.context.pages()[0]; - -await page.goto("https://example.com"); - -// Capture full page snapshot -const snapshot: HybridSnapshot = await page.snapshot(); -console.log("Accessibility tree:", snapshot.combinedTree); -console.log("XPath mappings:", Object.keys(snapshot.combinedXpathMap).length); - -// Capture snapshot focused on a specific element -const focusedSnapshot = await page.snapshot({ - focusSelector: "//main", -}); - -// Capture without piercing shadow DOM -const noShadowSnapshot = await page.snapshot({ - pierceShadow: false, -}); -``` - ## Viewport ### setViewportSize() @@ -770,12 +704,9 @@ capture is produced. interface SnapshotOptions { focusSelector?: string; pierceShadow?: boolean; - experimental?: boolean; } ``` -Options for capturing hybrid snapshots with `page.snapshot()`. - ### HybridSnapshot ```typescript @@ -783,25 +714,9 @@ interface HybridSnapshot { combinedTree: string; combinedXpathMap: Record; combinedUrlMap: Record; - perFrame?: PerFrameSnapshot[]; } ``` -The result of `page.snapshot()`, containing merged accessibility tree data across all frames. - -### PerFrameSnapshot - -```typescript -interface PerFrameSnapshot { - frameId: string; - outline: string; - xpathMap: Record; - urlMap: Record; -} -``` - -Per-frame snapshot data for debugging and advanced use cases. - ## Error Handling Page methods may throw the following errors: