Skip to content

Commit 3f13551

Browse files
committed
add mcp and cli commands
1 parent 542d8a5 commit 3f13551

11 files changed

Lines changed: 96 additions & 9 deletions

File tree

docs/src/api/class-locator.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,12 @@ Returns an accessibility snapshot of the element's subtree optimized for AI cons
24982498
### option: Locator.snapshotForAI.timeout = %%-input-timeout-js-%%
24992499
* since: v1.59
25002500

2501+
### option: Locator.snapshotForAI.depth
2502+
* since: v1.59
2503+
- `depth` <[int]>
2504+
2505+
When specified, limits the depth of the snapshot.
2506+
25012507
## async method: Locator.tap
25022508
* since: v1.14
25032509

packages/playwright-client/types/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14689,6 +14689,11 @@ export interface Locator {
1468914689
* @param options
1469014690
*/
1469114691
snapshotForAI(options?: {
14692+
/**
14693+
* When specified, limits the depth of the snapshot.
14694+
*/
14695+
depth?: number;
14696+
1469214697
/**
1469314698
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
1469414699
* option in the config, or by using the

packages/playwright-core/src/client/locator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,8 @@ export class Locator implements api.Locator {
378378
await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options, timeout: this._frame._timeout(options) });
379379
}
380380

381-
async snapshotForAI(options: TimeoutOptions = {}): Promise<{ full: string }> {
382-
return await this._frame._page!._channel.snapshotForAI({ timeout: this._frame._timeout(options), selector: this._selector });
381+
async snapshotForAI(options: TimeoutOptions & { depth?: number } = {}): Promise<{ full: string }> {
382+
return await this._frame._page!._channel.snapshotForAI({ timeout: this._frame._timeout(options), selector: this._selector, depth: options.depth });
383383
}
384384

385385
async _expect(expression: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean, errorMessage?: string }> {

packages/playwright-core/src/tools/backend/response.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class Response {
4848
private _includeSnapshot: 'none' | 'full' | 'incremental' = 'none';
4949
private _includeSnapshotFileName: string | undefined;
5050
private _includeSnapshotSelector: string | undefined;
51+
private _includeSnapshotDepth: number | undefined;
5152
private _isClose: boolean = false;
5253

5354
readonly toolName: string;
@@ -127,9 +128,10 @@ export class Response {
127128
this._includeSnapshot = this._context.config.snapshot?.mode || 'incremental';
128129
}
129130

130-
setIncludeFullSnapshot(includeSnapshotFileName?: string, selector?: string) {
131+
setIncludeFullSnapshot(includeSnapshotFileName?: string, selector?: string, depth?: number) {
131132
this._includeSnapshot = 'full';
132133
this._includeSnapshotFileName = includeSnapshotFileName;
134+
this._includeSnapshotDepth = depth;
133135
this._includeSnapshotSelector = selector;
134136
}
135137

@@ -195,7 +197,7 @@ export class Response {
195197
addSection('Ran Playwright code', this._code, 'js');
196198

197199
// Render tab titles upon changes or when more than one tab.
198-
const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot(this._includeSnapshotSelector, this._clientWorkspace) : undefined;
200+
const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot(this._includeSnapshotSelector, this._includeSnapshotDepth, this._clientWorkspace) : undefined;
199201
const tabHeaders = await Promise.all(this._context.tabs().map(tab => tab.headerSnapshot()));
200202
if (this._includeSnapshot !== 'none' || tabHeaders.some(header => header.changed)) {
201203
if (tabHeaders.length !== 1)

packages/playwright-core/src/tools/backend/snapshot.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ const snapshot = defineTool({
2828
inputSchema: z.object({
2929
filename: z.string().optional().describe('Save snapshot to markdown file instead of returning it in the response.'),
3030
selector: z.string().optional().describe('Element selector of the root element to capture a partial snapshot instead of the whole page'),
31+
depth: z.number().optional().describe('Limit the depth of the snapshot tree'),
3132
}),
3233
type: 'readOnly',
3334
},
3435

3536
handle: async (context, params, response) => {
3637
await context.ensureTab();
37-
response.setIncludeFullSnapshot(params.filename, params.selector);
38+
response.setIncludeFullSnapshot(params.filename, params.selector, params.depth);
3839
},
3940
});
4041

packages/playwright-core/src/tools/backend/tab.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,11 @@ export class Tab extends EventEmitter<TabEventsInterface> {
372372
this._requests.length = 0;
373373
}
374374

375-
async captureSnapshot(selector: string | undefined, relativeTo: string | undefined): Promise<TabSnapshot> {
375+
async captureSnapshot(selector: string | undefined, depth: number | undefined, relativeTo: string | undefined): Promise<TabSnapshot> {
376376
await this._initializedPromise;
377377
let tabSnapshot: TabSnapshot | undefined;
378378
const modalStates = await this._raceAgainstModalStates(async () => {
379-
const snapshot: { full: string, incremental?: string } = selector ? await this.page.locator(selector).snapshotForAI() : await this.page.snapshotForAI({ track: 'response' });
379+
const snapshot: { full: string, incremental?: string } = selector ? await this.page.locator(selector).snapshotForAI({ depth }) : await this.page.snapshotForAI({ track: 'response', depth });
380380
tabSnapshot = {
381381
ariaSnapshot: snapshot.full,
382382
ariaSnapshotDiff: this._needsFullSnapshot ? undefined : snapshot.incremental,

packages/playwright-core/src/tools/cli-daemon/commands.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,10 @@ const snapshot = declareCommand({
347347
}),
348348
options: z.object({
349349
filename: z.string().optional().describe('Save snapshot to markdown file instead of returning it in the response.'),
350+
depth: numberArg.optional().describe('Limit snapshot depth, unlimited by default.'),
350351
}),
351352
toolName: 'browser_snapshot',
352-
toolParams: ({ filename, element }) => ({ filename, selector: element }),
353+
toolParams: ({ filename, element, depth }) => ({ filename, selector: element, depth }),
353354
});
354355

355356
const evaluate = declareCommand({

packages/playwright-core/types/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14689,6 +14689,11 @@ export interface Locator {
1468914689
* @param options
1469014690
*/
1469114691
snapshotForAI(options?: {
14692+
/**
14693+
* When specified, limits the depth of the snapshot.
14694+
*/
14695+
depth?: number;
14696+
1469214697
/**
1469314698
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
1469414699
* option in the config, or by using the

tests/mcp/cli-core.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,20 @@ test('partial snapshot', async ({ cli, server }) => {
279279
const { output: noMatchError } = await cli('snapshot', '#target');
280280
expect(noMatchError).toContain(`Selector "#target" does not match any element`);
281281
});
282+
283+
test('snapshot depth', async ({ cli, server }) => {
284+
server.setContent('/', `<ul><li><button id=one>Submit</button></li><li><button id=two>Cancel</button></li></ul>`, 'text/html');
285+
await cli('open', server.PREFIX);
286+
287+
const { snapshot: limitedSnapshot } = await cli('snapshot', '--depth=1');
288+
expect(limitedSnapshot).toBe(`- list [ref=e2]:
289+
- listitem [ref=e3]
290+
- listitem [ref=e5]`);
291+
292+
const { snapshot: fullSnapshot } = await cli('snapshot', '--depth=100');
293+
expect(fullSnapshot).toBe(`- list [ref=e2]:
294+
- listitem [ref=e3]:
295+
- button "Submit" [ref=e4]
296+
- listitem [ref=e5]:
297+
- button "Cancel" [ref=e6]`);
298+
});

tests/mcp/core.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,46 @@ test('visibility: hidden > visible should be shown', { annotation: { type: 'issu
246246
snapshot: expect.stringContaining(`- button "Button"`),
247247
});
248248
});
249+
250+
test('snapshot depth', async ({ client, server }) => {
251+
server.setContent('/', `
252+
<ul>
253+
<li>text</li>
254+
<li>
255+
<button>Button</button>
256+
</li>
257+
</ul>
258+
`, 'text/html');
259+
260+
await client.callTool({
261+
name: 'browser_navigate',
262+
arguments: { url: server.PREFIX },
263+
});
264+
265+
expect(await client.callTool({
266+
name: 'browser_snapshot',
267+
arguments: {
268+
depth: 1,
269+
}
270+
})).toHaveResponse({
271+
snapshot: `\`\`\`yaml
272+
- list [ref=e2]:
273+
- listitem [ref=e3]: text
274+
- listitem [ref=e4]
275+
\`\`\``,
276+
});
277+
278+
expect(await client.callTool({
279+
name: 'browser_snapshot',
280+
arguments: {
281+
depth: 2,
282+
}
283+
})).toHaveResponse({
284+
snapshot: `\`\`\`yaml
285+
- list [ref=e2]:
286+
- listitem [ref=e3]: text
287+
- listitem [ref=e4]:
288+
- button "Button" [ref=e5]
289+
\`\`\``,
290+
});
291+
});

0 commit comments

Comments
 (0)