diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 179bc2f22879d..d361b0b1a2f29 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -2498,6 +2498,12 @@ Returns an accessibility snapshot of the element's subtree optimized for AI cons ### option: Locator.snapshotForAI.timeout = %%-input-timeout-js-%% * since: v1.59 +### option: Locator.snapshotForAI.depth +* since: v1.59 +- `depth` <[int]> + +When specified, limits the depth of the snapshot. + ## async method: Locator.tap * since: v1.14 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 071af1d84d76a..730ced1bad463 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -4232,6 +4232,12 @@ Returns an accessibility snapshot of the page optimized for AI consumption. When specified, enables incremental snapshots. Subsequent calls with the same track name will return an incremental snapshot containing only changes since the last call. +### option: Page.snapshotForAI.depth +* since: v1.59 +- `depth` <[int]> + +When specified, limits the depth of the snapshot. + ## async method: Page.tap * since: v1.8 * discouraged: Use locator-based [`method: Locator.tap`] instead. Read more about [locators](../locators.md). diff --git a/packages/injected/src/ariaSnapshot.ts b/packages/injected/src/ariaSnapshot.ts index a47c6b29ffcc3..b661c7fdc6515 100644 --- a/packages/injected/src/ariaSnapshot.ts +++ b/packages/injected/src/ariaSnapshot.ts @@ -40,6 +40,7 @@ export type AriaTreeOptions = { mode: 'ai' | 'expect' | 'codegen' | 'autoexpect'; refPrefix?: string; doNotRenderActive?: boolean; + depth?: number; }; type InternalOptions = { @@ -563,6 +564,10 @@ function filterSnapshotDiff(nodes: (aria.AriaNode | string)[], statusMap: Map { + const visitText = (text: string, depth: number) => { + if (publicOptions.depth && depth > publicOptions.depth) + return; const escaped = yamlEscapeValueIfNeeded(renderString(text)); if (escaped) - lines.push(indent + '- text: ' + escaped); + lines.push(indent(depth) + '- text: ' + escaped); }; const createKey = (ariaNode: aria.AriaNode, renderCursorPointer: boolean): string => { @@ -623,19 +630,24 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, publicOptions: AriaTr return ariaNode?.children.length === 1 && typeof ariaNode.children[0] === 'string' && !Object.keys(ariaNode.props).length ? ariaNode.children[0] : undefined; }; - const visit = (ariaNode: aria.AriaNode, indent: string, renderCursorPointer: boolean) => { + const visit = (ariaNode: aria.AriaNode, depth: number, renderCursorPointer: boolean) => { + if (publicOptions.depth && depth > publicOptions.depth) + return; + // Replace the whole subtree with a single reference when possible. if (statusMap.get(ariaNode) === 'same' && ariaNode.ref) { - lines.push(indent + `- ref=${ariaNode.ref} [unchanged]`); + lines.push(indent(depth) + `- ref=${ariaNode.ref} [unchanged]`); return; } // When producing a diff, add marker to all diff roots. - const isDiffRoot = !!previousSnapshot && !indent; - const escapedKey = indent + '- ' + (isDiffRoot ? ' ' : '') + yamlEscapeKeyIfNeeded(createKey(ariaNode, renderCursorPointer)); + const isDiffRoot = !!previousSnapshot && !depth; + const escapedKey = indent(depth) + '- ' + (isDiffRoot ? ' ' : '') + yamlEscapeKeyIfNeeded(createKey(ariaNode, renderCursorPointer)); const singleInlinedTextChild = getSingleInlinedTextChild(ariaNode); + const isAtDepthLimit = !!publicOptions.depth && depth === publicOptions.depth; + const hasNoChildren = !singleInlinedTextChild && (!ariaNode.children.length || isAtDepthLimit); - if (!ariaNode.children.length && !Object.keys(ariaNode.props).length) { + if (hasNoChildren && !Object.keys(ariaNode.props).length) { // Leaf node without children. lines.push(escapedKey); } else if (singleInlinedTextChild !== undefined) { @@ -649,24 +661,23 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, publicOptions: AriaTr // Node with (optional) props and some children. lines.push(escapedKey + ':'); for (const [name, value] of Object.entries(ariaNode.props)) - lines.push(indent + ' - /' + name + ': ' + yamlEscapeValueIfNeeded(value)); + lines.push(indent(depth + 1) + '- /' + name + ': ' + yamlEscapeValueIfNeeded(value)); - const childIndent = indent + ' '; const inCursorPointer = !!ariaNode.ref && renderCursorPointer && aria.hasPointerCursor(ariaNode); for (const child of ariaNode.children) { if (typeof child === 'string') - visitText(includeText(ariaNode, child) ? child : '', childIndent); + visitText(includeText(ariaNode, child) ? child : '', depth + 1); else - visit(child, childIndent, renderCursorPointer && !inCursorPointer); + visit(child, depth + 1, renderCursorPointer && !inCursorPointer); } } }; for (const nodeToRender of nodesToRender) { if (typeof nodeToRender === 'string') - visitText(nodeToRender, ''); + visitText(nodeToRender, 0); else - visit(nodeToRender, '', !!options.renderCursorPointer); + visit(nodeToRender, 0, !!options.renderCursorPointer); } return lines.join('\n'); } diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index 5ec25b19e5c19..416ef12ea697b 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -306,7 +306,7 @@ export class InjectedScript { return this.incrementalAriaSnapshot(node, options).full; } - incrementalAriaSnapshot(node: Node, options: AriaTreeOptions & { track?: string }): { full: string, incremental?: string, iframeRefs: string[] } { + incrementalAriaSnapshot(node: Node, options: AriaTreeOptions & { track?: string, depth?: number }): { full: string, incremental?: string, iframeRefs: string[] } { if (node.nodeType !== Node.ELEMENT_NODE) throw this.createStacklessError('Can only capture aria snapshot of Element nodes.'); const ariaSnapshot = generateAriaTree(node as Element, options); diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index a8796454b58f2..c754d27e5614c 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -4520,6 +4520,11 @@ export interface Page { * @param options */ snapshotForAI(options?: { + /** + * When specified, limits the depth of the snapshot. + */ + depth?: number; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the @@ -14689,6 +14694,11 @@ export interface Locator { * @param options */ snapshotForAI(options?: { + /** + * When specified, limits the depth of the snapshot. + */ + depth?: number; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index fdf1cb8e081a8..6e543098eef4a 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -378,8 +378,8 @@ export class Locator implements api.Locator { await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options, timeout: this._frame._timeout(options) }); } - async snapshotForAI(options: TimeoutOptions = {}): Promise<{ full: string }> { - return await this._frame._page!._channel.snapshotForAI({ timeout: this._frame._timeout(options), selector: this._selector }); + async snapshotForAI(options: TimeoutOptions & { depth?: number } = {}): Promise<{ full: string }> { + return await this._frame._page!._channel.snapshotForAI({ timeout: this._frame._timeout(options), selector: this._selector, depth: options.depth }); } async _expect(expression: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean, errorMessage?: string }> { diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 3f58b2bb37921..3109aa8c5468e 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -851,8 +851,8 @@ export class Page extends ChannelOwner implements api.Page return result.pdf; } - async snapshotForAI(options: TimeoutOptions & { track?: string } = {}): Promise<{ full: string, incremental?: string }> { - return await this._channel.snapshotForAI({ timeout: this._timeoutSettings.timeout(options), track: options.track }); + async snapshotForAI(options: TimeoutOptions & { track?: string, depth?: number } = {}): Promise<{ full: string, incremental?: string }> { + return await this._channel.snapshotForAI({ timeout: this._timeoutSettings.timeout(options), track: options.track, depth: options.depth }); } async _setDockTile(image: Buffer) { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index a22619d8dc5a6..8c1109dceb41c 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1497,6 +1497,7 @@ scheme.PageRequestsResult = tObject({ scheme.PageSnapshotForAIParams = tObject({ track: tOptional(tString), selector: tOptional(tString), + depth: tOptional(tInt), timeout: tFloat, }); scheme.PageSnapshotForAIResult = tObject({ diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 4ace511c0a313..567d2633baf86 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -886,7 +886,7 @@ export class Page extends SdkObject { await Promise.all(this.frames().map(frame => frame.hideHighlight().catch(() => {}))); } - async snapshotForAI(progress: Progress, options: { track?: string, doNotRenderActive?: boolean, selector?: string } = {}): Promise<{ full: string, incremental?: string }> { + async snapshotForAI(progress: Progress, options: { track?: string, doNotRenderActive?: boolean, selector?: string, depth?: number } = {}): Promise<{ full: string, incremental?: string }> { if (options.selector && options.track) throw new Error('Cannot specify both selector and track options'); @@ -1047,7 +1047,7 @@ export class InitScript extends DisposableObject { } } -async function snapshotFrameForAI(progress: Progress, frame: frames.Frame, options: { track?: string, doNotRenderActive?: boolean, info?: SelectorInfo } = {}): Promise<{ full: string[], incremental?: string[] }> { +async function snapshotFrameForAI(progress: Progress, frame: frames.Frame, options: { track?: string, doNotRenderActive?: boolean, info?: SelectorInfo, depth?: number } = {}): Promise<{ full: string[], incremental?: string[] }> { // Only await the topmost navigations, inner frames will be empty when racing. const snapshot = await frame.retryWithProgressAndTimeouts(progress, [1000, 2000, 4000, 8000], async continuePolling => { try { @@ -1069,6 +1069,7 @@ async function snapshotFrameForAI(progress: Progress, frame: frames.Frame, optio track: options.track, doNotRenderActive: options.doNotRenderActive, info: options.info, + depth: options.depth, })); if (snapshotOrRetry === true) return continuePolling; diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts index d7de715ea84c6..0e4941351faeb 100644 --- a/packages/playwright-core/src/tools/backend/response.ts +++ b/packages/playwright-core/src/tools/backend/response.ts @@ -48,6 +48,7 @@ export class Response { private _includeSnapshot: 'none' | 'full' | 'incremental' = 'none'; private _includeSnapshotFileName: string | undefined; private _includeSnapshotSelector: string | undefined; + private _includeSnapshotDepth: number | undefined; private _isClose: boolean = false; readonly toolName: string; @@ -127,9 +128,10 @@ export class Response { this._includeSnapshot = this._context.config.snapshot?.mode || 'incremental'; } - setIncludeFullSnapshot(includeSnapshotFileName?: string, selector?: string) { + setIncludeFullSnapshot(includeSnapshotFileName?: string, selector?: string, depth?: number) { this._includeSnapshot = 'full'; this._includeSnapshotFileName = includeSnapshotFileName; + this._includeSnapshotDepth = depth; this._includeSnapshotSelector = selector; } @@ -195,7 +197,7 @@ export class Response { addSection('Ran Playwright code', this._code, 'js'); // Render tab titles upon changes or when more than one tab. - const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot(this._includeSnapshotSelector, this._clientWorkspace) : undefined; + const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot(this._includeSnapshotSelector, this._includeSnapshotDepth, this._clientWorkspace) : undefined; const tabHeaders = await Promise.all(this._context.tabs().map(tab => tab.headerSnapshot())); if (this._includeSnapshot !== 'none' || tabHeaders.some(header => header.changed)) { if (tabHeaders.length !== 1) diff --git a/packages/playwright-core/src/tools/backend/snapshot.ts b/packages/playwright-core/src/tools/backend/snapshot.ts index 0506017564c14..e4982abae5ff5 100644 --- a/packages/playwright-core/src/tools/backend/snapshot.ts +++ b/packages/playwright-core/src/tools/backend/snapshot.ts @@ -28,13 +28,14 @@ const snapshot = defineTool({ inputSchema: z.object({ filename: z.string().optional().describe('Save snapshot to markdown file instead of returning it in the response.'), selector: z.string().optional().describe('Element selector of the root element to capture a partial snapshot instead of the whole page'), + depth: z.number().optional().describe('Limit the depth of the snapshot tree'), }), type: 'readOnly', }, handle: async (context, params, response) => { await context.ensureTab(); - response.setIncludeFullSnapshot(params.filename, params.selector); + response.setIncludeFullSnapshot(params.filename, params.selector, params.depth); }, }); diff --git a/packages/playwright-core/src/tools/backend/tab.ts b/packages/playwright-core/src/tools/backend/tab.ts index 89d4241044813..1570af91752ac 100644 --- a/packages/playwright-core/src/tools/backend/tab.ts +++ b/packages/playwright-core/src/tools/backend/tab.ts @@ -374,11 +374,11 @@ export class Tab extends EventEmitter { this._requests.length = 0; } - async captureSnapshot(selector: string | undefined, relativeTo: string | undefined): Promise { + async captureSnapshot(selector: string | undefined, depth: number | undefined, relativeTo: string | undefined): Promise { await this._initializedPromise; let tabSnapshot: TabSnapshot | undefined; const modalStates = await this._raceAgainstModalStates(async () => { - const snapshot: { full: string, incremental?: string } = selector ? await this.page.locator(selector).snapshotForAI() : await this.page.snapshotForAI({ track: 'response' }); + const snapshot: { full: string, incremental?: string } = selector ? await this.page.locator(selector).snapshotForAI({ depth }) : await this.page.snapshotForAI({ track: 'response', depth }); tabSnapshot = { ariaSnapshot: snapshot.full, ariaSnapshotDiff: this._needsFullSnapshot ? undefined : snapshot.incremental, diff --git a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md index 22060877694d0..2d417d46789f4 100644 --- a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md +++ b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md @@ -43,7 +43,6 @@ playwright-cli upload ./document.pdf playwright-cli check e12 playwright-cli uncheck e12 playwright-cli snapshot -playwright-cli snapshot --filename=after-click.yaml playwright-cli eval "document.title" playwright-cli eval "el => el.textContent" e5 # get element id, class, or any attribute not visible in the snapshot @@ -195,9 +194,21 @@ After each command, playwright-cli provides a snapshot of the current browser st [Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml) ``` -You can also take a snapshot on demand using `playwright-cli snapshot` command. +You can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed. + +```bash +# default - save to a file with timestamp-based name +playwright-cli snapshot + +# save to file, use when snapshot is a part of the workflow result +playwright-cli snapshot --filename=after-click.yaml + +# snapshot an element instead of the whole page +playwright-cli snapshot "#main" -If `--filename` is not provided, a new snapshot file is created with a timestamp. Default to automatic file naming, use `--filename=` when artifact is a part of the workflow result. +# limit snapshot depth for efficiency +playwright-cli snapshot --depth=4 +``` ## Targeting elements diff --git a/packages/playwright-core/src/tools/cli-daemon/commands.ts b/packages/playwright-core/src/tools/cli-daemon/commands.ts index f94a682d82436..8872ab682c250 100644 --- a/packages/playwright-core/src/tools/cli-daemon/commands.ts +++ b/packages/playwright-core/src/tools/cli-daemon/commands.ts @@ -347,9 +347,10 @@ const snapshot = declareCommand({ }), options: z.object({ filename: z.string().optional().describe('Save snapshot to markdown file instead of returning it in the response.'), + depth: numberArg.optional().describe('Limit snapshot depth, unlimited by default.'), }), toolName: 'browser_snapshot', - toolParams: ({ filename, element }) => ({ filename, selector: element }), + toolParams: ({ filename, element, depth }) => ({ filename, selector: element, depth }), }); const evaluate = declareCommand({ diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index a8796454b58f2..c754d27e5614c 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -4520,6 +4520,11 @@ export interface Page { * @param options */ snapshotForAI(options?: { + /** + * When specified, limits the depth of the snapshot. + */ + depth?: number; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the @@ -14689,6 +14694,11 @@ export interface Locator { * @param options */ snapshotForAI(options?: { + /** + * When specified, limits the depth of the snapshot. + */ + depth?: number; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index f9d1d4613fd42..1af10f89a87f3 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -2613,11 +2613,13 @@ export type PageRequestsResult = { export type PageSnapshotForAIParams = { track?: string, selector?: string, + depth?: number, timeout: number, }; export type PageSnapshotForAIOptions = { track?: string, selector?: string, + depth?: number, }; export type PageSnapshotForAIResult = { full: string, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 5cb737443885b..f037fa3312e8a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -2014,6 +2014,7 @@ Page: # When track is present, an incremental snapshot is returned when possible. track: string? selector: string? + depth: int? timeout: float returns: full: string diff --git a/tests/mcp/cli-core.spec.ts b/tests/mcp/cli-core.spec.ts index f5d1674cda788..785ba1a189b45 100644 --- a/tests/mcp/cli-core.spec.ts +++ b/tests/mcp/cli-core.spec.ts @@ -279,3 +279,20 @@ test('partial snapshot', async ({ cli, server }) => { const { output: noMatchError } = await cli('snapshot', '#target'); expect(noMatchError).toContain(`Selector "#target" does not match any element`); }); + +test('snapshot depth', async ({ cli, server }) => { + server.setContent('/', `
`, 'text/html'); + await cli('open', server.PREFIX); + + const { snapshot: limitedSnapshot } = await cli('snapshot', '--depth=1'); + expect(limitedSnapshot).toBe(`- list [ref=e2]: + - listitem [ref=e3] + - listitem [ref=e5]`); + + const { snapshot: fullSnapshot } = await cli('snapshot', '--depth=100'); + expect(fullSnapshot).toBe(`- list [ref=e2]: + - listitem [ref=e3]: + - button "Submit" [ref=e4] + - listitem [ref=e5]: + - button "Cancel" [ref=e6]`); +}); diff --git a/tests/mcp/core.spec.ts b/tests/mcp/core.spec.ts index 731ac32ed2406..1eefe26ac4f80 100644 --- a/tests/mcp/core.spec.ts +++ b/tests/mcp/core.spec.ts @@ -246,3 +246,46 @@ test('visibility: hidden > visible should be shown', { annotation: { type: 'issu snapshot: expect.stringContaining(`- button "Button"`), }); }); + +test('snapshot depth', async ({ client, server }) => { + server.setContent('/', ` +
    +
  • text
  • +
  • + +
  • +
+ `, 'text/html'); + + await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.PREFIX }, + }); + + expect(await client.callTool({ + name: 'browser_snapshot', + arguments: { + depth: 1, + } + })).toHaveResponse({ + snapshot: `\`\`\`yaml +- list [ref=e2]: + - listitem [ref=e3]: text + - listitem [ref=e4] +\`\`\``, + }); + + expect(await client.callTool({ + name: 'browser_snapshot', + arguments: { + depth: 2, + } + })).toHaveResponse({ + snapshot: `\`\`\`yaml +- list [ref=e2]: + - listitem [ref=e3]: text + - listitem [ref=e4]: + - button "Button" [ref=e5] +\`\`\``, + }); +}); diff --git a/tests/page/page-aria-snapshot-ai.spec.ts b/tests/page/page-aria-snapshot-ai.spec.ts index 6f924cdb6716c..d4cf11f00aa77 100644 --- a/tests/page/page-aria-snapshot-ai.spec.ts +++ b/tests/page/page-aria-snapshot-ai.spec.ts @@ -17,8 +17,9 @@ import { test as it, expect } from './pageTest'; import { unshift } from '../config/utils'; +import type { Page } from 'playwright-core'; -async function snapshotForAI(page: any, options?: { timeout?: number, mode?: 'full' | 'incremental', track?: string }): Promise { +async function snapshotForAI(page: Page, options?: Parameters[0] & { mode?: 'full' | 'incremental' }): Promise { const snapshot = await page.snapshotForAI(options); return options?.mode === 'incremental' ? snapshot.incremental : snapshot.full; } @@ -739,3 +740,64 @@ it('should create incremental snapshot for children swap', async ({ page }) => { - ref=e3 [unchanged] `); }); + +it('should limit depth', async ({ page }) => { + await page.setContent(` +
    +
  • item1
  • + link +
  • +
      +
    • item2
    • +
    • +
        +
      • item3
      • +
      +
    • +
    +
  • +
+ `); + + const snapshot1 = await snapshotForAI(page, { depth: 1 }); + expect(snapshot1).toContainYaml(` + - list [ref=e2]: + - listitem [ref=e3]: item1 + - link "link" [ref=e4] [cursor=pointer]: + - /url: about:blank + - listitem [ref=e5] + `); + + const snapshot2 = await snapshotForAI(page, { depth: 3 }); + expect(snapshot2).toContainYaml(` + - list [ref=e2]: + - listitem [ref=e3]: item1 + - link "link" [ref=e4] [cursor=pointer]: + - /url: about:blank + - listitem [ref=e5]: + - list [ref=e6]: + - listitem [ref=e7]: item2 + - listitem [ref=e8] + `); + + const snapshot3 = await snapshotForAI(page, { depth: 100 }); + expect(snapshot3).toContainYaml(` + - list [ref=e2]: + - listitem [ref=e3]: item1 + - link "link" [ref=e4] [cursor=pointer]: + - /url: about:blank + - listitem [ref=e5]: + - list [ref=e6]: + - listitem [ref=e7]: item2 + - listitem [ref=e8]: + - list [ref=e9]: + - listitem [ref=e10]: item3 + `); + + const { full: snapshot4 } = await page.locator('#target').snapshotForAI({ depth: 1 }); + expect(snapshot4).toContainYaml(` + - list [ref=e6]: + - listitem [ref=e7]: item2 + - listitem [ref=e8] + `); +});