From 4b0e4ef22e20f6d358ac19f8c5683a17c95d3bad Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 3 Aug 2026 14:34:20 -0700 Subject: [PATCH] feat(mcp): add snapshot.boxes config to include bounding boxes in snapshots Fixes: https://github.com/microsoft/playwright-mcp/issues/1695 --- .../src/tools/backend/context.ts | 1 + .../src/tools/backend/response.ts | 3 ++- .../playwright-core/src/tools/mcp/config.d.ts | 6 +++++ .../playwright-core/src/tools/mcp/config.ts | 3 ++- .../src/tools/mcp/configIni.ts | 1 + .../playwright-core/src/tools/mcp/program.ts | 1 + tests/mcp/snapshot-mode.spec.ts | 26 +++++++++++++++++++ 7 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/tools/backend/context.ts b/packages/playwright-core/src/tools/backend/context.ts index 71598b3c1d01f..aa8545962e6f2 100644 --- a/packages/playwright-core/src/tools/backend/context.ts +++ b/packages/playwright-core/src/tools/backend/context.ts @@ -50,6 +50,7 @@ export type ContextConfig = { secrets?: Record; snapshot?: { mode?: 'full' | 'none'; + boxes?: boolean; }; testIdAttribute?: string; timeouts?: { diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts index 725edcdaaff78..c29732af5b6da 100644 --- a/packages/playwright-core/src/tools/backend/response.ts +++ b/packages/playwright-core/src/tools/backend/response.ts @@ -145,13 +145,14 @@ export class Response { setIncludeSnapshot() { this._includeSnapshot = this._context.config.snapshot?.mode ?? 'full'; + this._includeSnapshotBoxes = this._context.config.snapshot?.boxes; } setIncludeFullSnapshot(includeSnapshotFileName?: string, root?: playwright.Locator, depth?: number, boxes?: boolean) { this._includeSnapshot = 'explicit'; this._includeSnapshotFileName = includeSnapshotFileName; this._includeSnapshotDepth = depth; - this._includeSnapshotBoxes = boxes; + this._includeSnapshotBoxes = boxes ?? this._context.config.snapshot?.boxes; this._includeSnapshotRoot = root; } diff --git a/packages/playwright-core/src/tools/mcp/config.d.ts b/packages/playwright-core/src/tools/mcp/config.d.ts index 487f7fac05a5a..9dbda16a1f9ac 100644 --- a/packages/playwright-core/src/tools/mcp/config.d.ts +++ b/packages/playwright-core/src/tools/mcp/config.d.ts @@ -227,6 +227,12 @@ export type Config = { * When taking snapshots for responses, specifies the mode to use. */ mode?: 'full' | 'none'; + + /** + * Whether to include each element's bounding box as [box=x,y,width,height] in snapshots. + * Coordinates are viewport-relative, in CSS pixels (Element.getBoundingClientRect). + */ + boxes?: boolean; }; /** diff --git a/packages/playwright-core/src/tools/mcp/config.ts b/packages/playwright-core/src/tools/mcp/config.ts index fd858938cb7fd..85f634a850f80 100644 --- a/packages/playwright-core/src/tools/mcp/config.ts +++ b/packages/playwright-core/src/tools/mcp/config.ts @@ -69,6 +69,7 @@ export type CLIOptions = { saveSession?: boolean; secrets?: Record; sharedBrowserContext?: boolean; + snapshotBoxes?: boolean; snapshotMode?: 'full' | 'none'; storageState?: string; testIdAttribute?: string; @@ -372,7 +373,7 @@ function configFromCLIOptions(cliOptions: CLIOptions): Config & { configFile?: s saveSession: cliOptions.saveSession, secrets: cliOptions.secrets, sharedBrowserContext: cliOptions.sharedBrowserContext, - snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : undefined, + snapshot: cliOptions.snapshotMode || cliOptions.snapshotBoxes !== undefined ? { mode: cliOptions.snapshotMode, boxes: cliOptions.snapshotBoxes } : undefined, outputDir: cliOptions.outputDir, outputMaxSize: cliOptions.outputMaxSize, imageResponses: cliOptions.imageResponses, diff --git a/packages/playwright-core/src/tools/mcp/configIni.ts b/packages/playwright-core/src/tools/mcp/configIni.ts index c8dc90ed58bf3..7d71b7d8f2546 100644 --- a/packages/playwright-core/src/tools/mcp/configIni.ts +++ b/packages/playwright-core/src/tools/mcp/configIni.ts @@ -185,4 +185,5 @@ const longhandTypes: Record = { // snapshot 'snapshot.mode': 'string', + 'snapshot.boxes': 'boolean', }; diff --git a/packages/playwright-core/src/tools/mcp/program.ts b/packages/playwright-core/src/tools/mcp/program.ts index 9db1980820257..29b2e6ba33925 100644 --- a/packages/playwright-core/src/tools/mcp/program.ts +++ b/packages/playwright-core/src/tools/mcp/program.ts @@ -69,6 +69,7 @@ export function decorateMCPCommand(command: Command) { .option('--save-session', 'Whether to save the Playwright MCP session into the output directory.') .option('--secrets ', 'path to a file containing secrets in the dotenv format', dotenvFileLoader) .option('--shared-browser-context', 'reuse the same browser context between all connected HTTP clients.') + .option('--snapshot-boxes', 'include each element\'s bounding box as [box=x,y,width,height] in snapshots. Coordinates are viewport-relative, in CSS pixels.') .option('--snapshot-mode ', 'when taking snapshots for responses, specifies the mode to use. Can be "full" or "none". Default is "full".') .option('--storage-state ', 'path to the storage state file for isolated sessions.') .option('--test-id-attribute ', 'specify the attribute to use for test ids, defaults to "data-testid"') diff --git a/tests/mcp/snapshot-mode.spec.ts b/tests/mcp/snapshot-mode.spec.ts index e96689ddc5e0b..2b6bafa1e5155 100644 --- a/tests/mcp/snapshot-mode.spec.ts +++ b/tests/mcp/snapshot-mode.spec.ts @@ -96,6 +96,32 @@ test('should not inline console messages with --snapshot-mode=none', async ({ st }); }); +test('should respect --snapshot-boxes', async ({ startClient, server }) => { + server.setContent('/', ` + + + `, 'text/html'); + + const { client } = await startClient({ + args: ['--snapshot-boxes'], + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + })).toHaveResponse({ + snapshot: expect.stringContaining(`- button "click" [ref=e1] [box=100,50,80,40]`), + }); + + expect(await client.callTool({ + name: 'browser_snapshot', + })).toHaveResponse({ + inlineSnapshot: expect.stringContaining(`- button "click" [ref=e1] [box=100,50,80,40]`), + }); +}); + test('should respect snapshot[filename]', async ({ client, server }, testInfo) => { server.setContent('/', ``, 'text/html');