Skip to content

Commit 930d1c5

Browse files
committed
chore(mcp): introduce snapshot-mode full/incremental/none
1 parent 4c65ab3 commit 930d1c5

File tree

7 files changed

+130
-6
lines changed

7 files changed

+130
-6
lines changed

packages/playwright/src/mcp/browser/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export type CLIOptions = {
5959
saveVideo?: ViewportSize;
6060
secrets?: Record<string, string>;
6161
sharedBrowserContext?: boolean;
62+
snapshotMode?: 'incremental' | 'full' | 'none';
6263
storageState?: string;
6364
testIdAttribute?: string;
6465
timeoutAction?: number;
@@ -86,6 +87,9 @@ export const defaultConfig: FullConfig = {
8687
},
8788
server: {},
8889
saveTrace: false,
90+
snapshot: {
91+
mode: 'incremental',
92+
},
8993
timeouts: {
9094
action: 5000,
9195
navigation: 60000,
@@ -103,6 +107,9 @@ export type FullConfig = Config & {
103107
network: NonNullable<Config['network']>,
104108
saveTrace: boolean;
105109
server: NonNullable<Config['server']>,
110+
snapshot: {
111+
mode: 'incremental' | 'full' | 'none';
112+
},
106113
timeouts: {
107114
action: number;
108115
navigation: number;
@@ -243,6 +250,7 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config {
243250
saveVideo: cliOptions.saveVideo,
244251
secrets: cliOptions.secrets,
245252
sharedBrowserContext: cliOptions.sharedBrowserContext,
253+
snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : undefined,
246254
outputDir: cliOptions.outputDir,
247255
imageResponses: cliOptions.imageResponses,
248256
testIdAttribute: cliOptions.testIdAttribute,
@@ -385,6 +393,10 @@ function mergeConfig(base: FullConfig, overrides: Config): FullConfig {
385393
...pickDefined(base.server),
386394
...pickDefined(overrides.server),
387395
},
396+
snapshot: {
397+
...pickDefined(base.snapshot),
398+
...pickDefined(overrides.snapshot),
399+
},
388400
timeouts: {
389401
...pickDefined(base.timeouts),
390402
...pickDefined(overrides.timeouts),

packages/playwright/src/mcp/browser/response.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,12 @@ export class Response {
7575
return this._images;
7676
}
7777

78-
setIncludeSnapshot(full?: 'full') {
79-
this._includeSnapshot = full ?? 'incremental';
78+
setIncludeSnapshot() {
79+
this._includeSnapshot = this._context.config.snapshot.mode;
80+
}
81+
82+
setIncludeFullSnapshot() {
83+
this._includeSnapshot = 'full';
8084
}
8185

8286
setIncludeTabs() {

packages/playwright/src/mcp/browser/tools/snapshot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const snapshot = defineTool({
3030

3131
handle: async (context, params, response) => {
3232
await context.ensureTab();
33-
response.setIncludeSnapshot('full');
33+
response.setIncludeFullSnapshot();
3434
},
3535
});
3636

packages/playwright/src/mcp/browser/tools/tabs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ const browserTabs = defineTool({
4545
}
4646
case 'close': {
4747
await context.closeTab(params.index);
48-
response.setIncludeSnapshot('full');
48+
response.setIncludeFullSnapshot();
4949
return;
5050
}
5151
case 'select': {
5252
if (params.index === undefined)
5353
throw new Error('Tab index is required');
5454
await context.selectTab(params.index);
55-
response.setIncludeSnapshot('full');
55+
response.setIncludeFullSnapshot();
5656
return;
5757
}
5858
}

packages/playwright/src/mcp/config.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,11 @@ export type Config = {
175175
* Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
176176
*/
177177
imageResponses?: 'allow' | 'omit';
178-
};
179178

179+
snapshot?: {
180+
/**
181+
* When taking snapshots for responses, specifies the mode to use.
182+
*/
183+
mode?: 'incremental' | 'full' | 'none';
184+
}
185+
};

packages/playwright/src/mcp/program.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export function decorateCommand(command: Command, version: string) {
6464
.option('--save-video <size>', 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', resolutionParser.bind(null, '--save-video'))
6565
.option('--secrets <path>', 'path to a file containing secrets in the dotenv format', dotenvFileLoader)
6666
.option('--shared-browser-context', 'reuse the same browser context between all connected HTTP clients.')
67+
.option('--snapshot-mode <mode>', 'when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.')
6768
.option('--storage-state <path>', 'path to the storage state file for isolated sessions.')
6869
.option('--test-id-attribute <attribute>', 'specify the attribute to use for test ids, defaults to "data-testid"')
6970
.option('--timeout-action <timeout>', 'specify action timeout in milliseconds, defaults to 5000ms', numberParser)

tests/mcp/snapshot-mode.spec.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { test, expect } from './fixtures';
18+
19+
test('should respect --snapshot-mode=full', async ({ startClient, server }) => {
20+
server.setContent('/', `<button>Button 1</button>`, 'text/html');
21+
22+
const { client } = await startClient({
23+
args: ['--snapshot-mode=full'],
24+
});
25+
26+
expect(await client.callTool({
27+
name: 'browser_navigate',
28+
arguments: {
29+
url: server.PREFIX,
30+
},
31+
})).toHaveResponse({
32+
pageState: expect.stringContaining(`
33+
- button "Button 1" [ref=e2]`),
34+
});
35+
36+
expect(await client.callTool({
37+
name: 'browser_evaluate',
38+
arguments: {
39+
function: `async () => {
40+
const button2 = document.createElement('button');
41+
button2.textContent = 'Button 2';
42+
document.body.appendChild(button2);
43+
}`,
44+
},
45+
})).toHaveResponse({
46+
pageState: expect.stringContaining(`
47+
- button "Button 1" [ref=e2]
48+
- button "Button 2" [ref=e3]`),
49+
});
50+
});
51+
52+
test('should respect --snapshot-mode=incremental', async ({ startClient, server }) => {
53+
server.setContent('/', `<button>Button 1</button>`, 'text/html');
54+
55+
const { client } = await startClient({
56+
args: ['--snapshot-mode=incremental'],
57+
});
58+
59+
expect(await client.callTool({
60+
name: 'browser_navigate',
61+
arguments: {
62+
url: server.PREFIX,
63+
},
64+
})).toHaveResponse({
65+
pageState: expect.stringContaining(`
66+
- button "Button 1" [ref=e2]`),
67+
});
68+
69+
expect(await client.callTool({
70+
name: 'browser_evaluate',
71+
arguments: {
72+
function: `async () => {
73+
const button2 = document.createElement('button');
74+
button2.textContent = 'Button 2';
75+
document.body.appendChild(button2);
76+
}`,
77+
},
78+
})).toHaveResponse({
79+
pageState: expect.stringContaining(`
80+
- <changed> generic [active] [ref=e1]:
81+
- ref=e2 [unchanged]
82+
- button \"Button 2\" [ref=e3]`),
83+
});
84+
});
85+
86+
test('should respect --snapshot-mode=none', async ({ startClient, server }) => {
87+
server.setContent('/', `<button>Button 1</button>`, 'text/html');
88+
89+
const { client } = await startClient({
90+
args: ['--snapshot-mode=none'],
91+
});
92+
93+
expect(await client.callTool({
94+
name: 'browser_navigate',
95+
arguments: {
96+
url: server.PREFIX,
97+
},
98+
})).toHaveResponse({
99+
pageState: undefined
100+
});
101+
});

0 commit comments

Comments
 (0)