From 85c85bd2fbf1a3cc332d5dd5916090591b23b30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=8C=E3=81=A9=E3=82=89?= <61941819+ogadra@users.noreply.github.com> Date: Fri, 9 May 2025 03:04:18 +0900 Subject: [PATCH] chore: support custom filename in screenshot function (#349) --- README.md | 4 ++- src/tools/pdf.ts | 10 ++++-- src/tools/snapshot.ts | 3 +- tests/pdf.spec.ts | 37 +++++++++++++++++++++ tests/screenshot.spec.ts | 72 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 116 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cf57dd7..18c84a8 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,7 @@ X Y coordinate space, based on the provided screenshot. - Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions. - Parameters: - `raw` (boolean, optional): Whether to return without compression (in PNG format). Default is false, which returns a JPEG image. + - `filename` (string, optional): File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. - `element` (string, optional): Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too. - `ref` (string, optional): Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too. - Read-only: **true** @@ -501,7 +502,8 @@ X Y coordinate space, based on the provided screenshot. - **browser_pdf_save** - Title: Save as PDF - Description: Save page as PDF - - Parameters: None + - Parameters: + - `filename` (string, optional): File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. - Read-only: **true** ### Utilities diff --git a/src/tools/pdf.ts b/src/tools/pdf.ts index ff2465b..c020f03 100644 --- a/src/tools/pdf.ts +++ b/src/tools/pdf.ts @@ -20,6 +20,10 @@ import { defineTool } from './tool.js'; import * as javascript from '../javascript.js'; import { outputFile } from '../config.js'; +const pdfSchema = z.object({ + filename: z.string().optional().describe('File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.'), +}); + const pdf = defineTool({ capability: 'pdf', @@ -27,13 +31,13 @@ const pdf = defineTool({ name: 'browser_pdf_save', title: 'Save as PDF', description: 'Save page as PDF', - inputSchema: z.object({}), + inputSchema: pdfSchema, type: 'readOnly', }, - handle: async context => { + handle: async (context, params) => { const tab = context.currentTabOrDie(); - const fileName = await outputFile(context.config, `page-${new Date().toISOString()}.pdf`); + const fileName = await outputFile(context.config, params.filename ?? `page-${new Date().toISOString()}.pdf`); const code = [ `// Save page as ${fileName}`, diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index 410d2f9..e6a2d66 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -220,6 +220,7 @@ const selectOption = defineTool({ const screenshotSchema = z.object({ raw: z.boolean().optional().describe('Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.'), + filename: z.string().optional().describe('File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified.'), element: z.string().optional().describe('Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too.'), ref: z.string().optional().describe('Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.'), }).refine(data => { @@ -243,7 +244,7 @@ const screenshot = defineTool({ const tab = context.currentTabOrDie(); const snapshot = tab.snapshotOrDie(); const fileType = params.raw ? 'png' : 'jpeg'; - const fileName = await outputFile(context.config, `page-${new Date().toISOString()}.${fileType}`); + const fileName = await outputFile(context.config, params.filename ?? `page-${new Date().toISOString()}.${fileType}`); const options: playwright.PageScreenshotOptions = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName }; const isElementScreenshot = params.element && params.ref; diff --git a/tests/pdf.spec.ts b/tests/pdf.spec.ts index dd9cd8d..dbccbfa 100644 --- a/tests/pdf.spec.ts +++ b/tests/pdf.spec.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import fs from 'fs'; + import { test, expect } from './fixtures.js'; test('save as pdf unavailable', async ({ startClient }) => { @@ -45,3 +47,38 @@ test('save as pdf', async ({ client, mcpBrowser }) => { }); expect(response).toHaveTextContent(/Save page as.*page-[^:]+.pdf/); }); + +test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser }, testInfo) => { + test.skip(!!mcpBrowser && !['chromium', 'chrome', 'msedge'].includes(mcpBrowser), 'Save as PDF is only supported in Chromium.'); + const outputDir = testInfo.outputPath('output'); + const client = await startClient({ + config: { outputDir }, + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: 'data:text/html,