diff --git a/packages/playwright-core/src/tools/backend/screenshot.ts b/packages/playwright-core/src/tools/backend/screenshot.ts index d2aeea504a2d3..0f83b638ace85 100644 --- a/packages/playwright-core/src/tools/backend/screenshot.ts +++ b/packages/playwright-core/src/tools/backend/screenshot.ts @@ -29,6 +29,7 @@ const screenshotSchema = optionalElementSchema.extend({ type: z.enum(['png', 'jpeg']).default('png').describe('Image format for the screenshot. Default is png.'), filename: z.string().optional().describe('File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory.'), fullPage: z.boolean().optional().describe('When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.'), + scale: z.enum(['css', 'device']).default('css').describe('Image resolution scale. "css" produces a screenshot sized in CSS pixels (smaller, consistent across devices). "device" produces a high-resolution screenshot using device pixels (larger, accounts for the device pixel ratio). Default is css.'), }); const screenshot = defineTabTool({ @@ -49,7 +50,7 @@ const screenshot = defineTabTool({ const options: playwright.PageScreenshotOptions = { type: fileType, quality: fileType === 'png' ? undefined : 90, - scale: 'css', + scale: params.scale, ...tab.actionTimeoutOptions, ...(params.fullPage !== undefined && { fullPage: params.fullPage }) }; 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 252e62ce4f294..2fa5d0e96f9fe 100644 --- a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md +++ b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md @@ -93,6 +93,7 @@ playwright-cli mousewheel 0 100 playwright-cli screenshot playwright-cli screenshot e5 playwright-cli screenshot --filename=page.png +playwright-cli screenshot --hires playwright-cli pdf --filename=page.pdf ``` diff --git a/packages/playwright-core/src/tools/cli-daemon/commands.ts b/packages/playwright-core/src/tools/cli-daemon/commands.ts index 689f7b0e358a9..7505f6a3a466e 100644 --- a/packages/playwright-core/src/tools/cli-daemon/commands.ts +++ b/packages/playwright-core/src/tools/cli-daemon/commands.ts @@ -791,9 +791,10 @@ const screenshot = declareCommand({ options: z.object({ filename: z.string().optional().describe('File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified.'), ['full-page']: z.boolean().optional().describe('When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport.'), + hires: z.boolean().optional().describe('When true, captures a high-resolution screenshot using device pixels (accounts for the device pixel ratio), instead of CSS pixels.'), }), toolName: 'browser_take_screenshot', - toolParams: ({ target, filename, ['full-page']: fullPage }) => ({ filename, target, fullPage }), + toolParams: ({ target, filename, ['full-page']: fullPage, hires }) => ({ filename, target, fullPage, scale: hires ? 'device' : undefined }), }); const pdfSave = declareCommand({ diff --git a/tests/mcp/cli-save-as.spec.ts b/tests/mcp/cli-save-as.spec.ts index b566c0c022d9c..7aaf9c2ca415e 100644 --- a/tests/mcp/cli-save-as.spec.ts +++ b/tests/mcp/cli-save-as.spec.ts @@ -38,6 +38,13 @@ test('screenshot --full-page', async ({ cli, server, mcpBrowser }) => { expect(attachments[0].data).toEqual(expect.any(Buffer)); }); +test('screenshot --hires', async ({ cli, server, mcpBrowser }) => { + await cli('open', server.HELLO_WORLD); + const { attachments } = await cli('screenshot', '--hires'); + expect(attachments[0].name).toEqual('Screenshot of viewport'); + expect(attachments[0].data).toEqual(expect.any(Buffer)); +}); + test('screenshot --filename', async ({ cli, server, mcpBrowser }) => { await cli('open', server.HELLO_WORLD); const { output, attachments } = await cli('screenshot', '--filename=screenshot.png'); diff --git a/tests/mcp/screenshot.spec.ts b/tests/mcp/screenshot.spec.ts index faa0d7d5eec73..16d84b5640b5f 100644 --- a/tests/mcp/screenshot.spec.ts +++ b/tests/mcp/screenshot.spec.ts @@ -344,6 +344,40 @@ test('browser_take_screenshot size cap', async ({ startClient, server, mcpBrowse } }); +test('browser_take_screenshot (scale: device)', async ({ startClient, server }, testInfo) => { + const { client } = await startClient({ + config: { + outputDir: testInfo.outputPath('output'), + browser: { + contextOptions: { + viewport: { width: 400, height: 300 }, + deviceScaleFactor: 2, + }, + }, + }, + }); + await client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } }); + + const cssResult = await client.callTool({ + name: 'browser_take_screenshot', + arguments: { scale: 'css' }, + }); + const css = PNG.sync.read(Buffer.from(cssResult.content?.[1]?.data, 'base64')); + expect(css.width).toBe(400); + expect(css.height).toBe(300); + + const deviceResult = await client.callTool({ + name: 'browser_take_screenshot', + arguments: { scale: 'device' }, + }); + expect(deviceResult).toHaveResponse({ + code: expect.stringContaining(`scale: 'device'`), + }); + const device = PNG.sync.read(Buffer.from(deviceResult.content?.[1]?.data, 'base64')); + expect(device.width).toBe(800); + expect(device.height).toBe(600); +}); + test('browser_take_screenshot (fullPage with element should error)', async ({ startClient, server }, testInfo) => { const { client } = await startClient({ config: { outputDir: testInfo.outputPath('output') },