mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-26 16:42:27 +08:00
chore: support custom filename in screenshot function (#349)
This commit is contained in:
parent
09ba7989c3
commit
85c85bd2fb
@ -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.
|
- Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `raw` (boolean, optional): Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.
|
- `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.
|
- `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.
|
- `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**
|
- Read-only: **true**
|
||||||
@ -501,7 +502,8 @@ X Y coordinate space, based on the provided screenshot.
|
|||||||
- **browser_pdf_save**
|
- **browser_pdf_save**
|
||||||
- Title: Save as PDF
|
- Title: Save as PDF
|
||||||
- Description: Save page 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**
|
- Read-only: **true**
|
||||||
|
|
||||||
### Utilities
|
### Utilities
|
||||||
|
@ -20,6 +20,10 @@ import { defineTool } from './tool.js';
|
|||||||
import * as javascript from '../javascript.js';
|
import * as javascript from '../javascript.js';
|
||||||
import { outputFile } from '../config.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({
|
const pdf = defineTool({
|
||||||
capability: 'pdf',
|
capability: 'pdf',
|
||||||
|
|
||||||
@ -27,13 +31,13 @@ const pdf = defineTool({
|
|||||||
name: 'browser_pdf_save',
|
name: 'browser_pdf_save',
|
||||||
title: 'Save as PDF',
|
title: 'Save as PDF',
|
||||||
description: 'Save page as PDF',
|
description: 'Save page as PDF',
|
||||||
inputSchema: z.object({}),
|
inputSchema: pdfSchema,
|
||||||
type: 'readOnly',
|
type: 'readOnly',
|
||||||
},
|
},
|
||||||
|
|
||||||
handle: async context => {
|
handle: async (context, params) => {
|
||||||
const tab = context.currentTabOrDie();
|
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 = [
|
const code = [
|
||||||
`// Save page as ${fileName}`,
|
`// Save page as ${fileName}`,
|
||||||
|
@ -220,6 +220,7 @@ const selectOption = defineTool({
|
|||||||
|
|
||||||
const screenshotSchema = z.object({
|
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.'),
|
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.'),
|
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.'),
|
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 => {
|
}).refine(data => {
|
||||||
@ -243,7 +244,7 @@ const screenshot = defineTool({
|
|||||||
const tab = context.currentTabOrDie();
|
const tab = context.currentTabOrDie();
|
||||||
const snapshot = tab.snapshotOrDie();
|
const snapshot = tab.snapshotOrDie();
|
||||||
const fileType = params.raw ? 'png' : 'jpeg';
|
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 options: playwright.PageScreenshotOptions = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName };
|
||||||
const isElementScreenshot = params.element && params.ref;
|
const isElementScreenshot = params.element && params.ref;
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
import { test, expect } from './fixtures.js';
|
import { test, expect } from './fixtures.js';
|
||||||
|
|
||||||
test('save as pdf unavailable', async ({ startClient }) => {
|
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/);
|
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,<html><title>Title</title><body>Hello, world!</body></html>',
|
||||||
|
},
|
||||||
|
})).toContainTextContent(`- generic [ref=s1e2]: Hello, world!`);
|
||||||
|
|
||||||
|
expect(await client.callTool({
|
||||||
|
name: 'browser_pdf_save',
|
||||||
|
arguments: {
|
||||||
|
filename: 'output.pdf',
|
||||||
|
},
|
||||||
|
})).toEqual({
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: expect.stringContaining(`output.pdf`),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const files = [...fs.readdirSync(outputDir)];
|
||||||
|
|
||||||
|
expect(fs.existsSync(outputDir)).toBeTruthy();
|
||||||
|
expect(files).toHaveLength(1);
|
||||||
|
expect(files[0]).toMatch(/^output.pdf$/);
|
||||||
|
});
|
||||||
|
@ -94,9 +94,54 @@ test('--output-dir should work', async ({ startClient, localOutputPath }) => {
|
|||||||
expect([...fs.readdirSync(outputDir)]).toHaveLength(1);
|
expect([...fs.readdirSync(outputDir)]).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const raw of [undefined, true]) {
|
||||||
|
test(`browser_take_screenshot (raw: ${raw})`, async ({ startClient }, testInfo) => {
|
||||||
|
const ext = raw ? 'png' : 'jpeg';
|
||||||
|
const outputDir = testInfo.outputPath('output');
|
||||||
|
const client = await startClient({
|
||||||
|
config: { outputDir },
|
||||||
|
});
|
||||||
|
expect(await client.callTool({
|
||||||
|
name: 'browser_navigate',
|
||||||
|
arguments: {
|
||||||
|
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
|
||||||
|
},
|
||||||
|
})).toContainTextContent(`Navigate to data:text/html`);
|
||||||
|
|
||||||
test('browser_take_screenshot (outputDir)', async ({ startClient, localOutputPath }) => {
|
expect(await client.callTool({
|
||||||
const outputDir = localOutputPath('output');
|
name: 'browser_take_screenshot',
|
||||||
|
arguments: {
|
||||||
|
raw,
|
||||||
|
},
|
||||||
|
})).toEqual({
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
data: expect.any(String),
|
||||||
|
mimeType: `image/${ext}`,
|
||||||
|
type: 'image',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: expect.stringMatching(
|
||||||
|
new RegExp(`page-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}\\-\\d{3}Z\\.${ext}`)
|
||||||
|
),
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const files = [...fs.readdirSync(outputDir)];
|
||||||
|
|
||||||
|
expect(fs.existsSync(outputDir)).toBeTruthy();
|
||||||
|
expect(files).toHaveLength(1);
|
||||||
|
expect(files[0]).toMatch(
|
||||||
|
new RegExp(`^page-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z\\.${ext}$`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient }, testInfo) => {
|
||||||
|
const outputDir = testInfo.outputPath('output');
|
||||||
const client = await startClient({
|
const client = await startClient({
|
||||||
config: { outputDir },
|
config: { outputDir },
|
||||||
});
|
});
|
||||||
@ -107,13 +152,30 @@ test('browser_take_screenshot (outputDir)', async ({ startClient, localOutputPat
|
|||||||
},
|
},
|
||||||
})).toContainTextContent(`Navigate to data:text/html`);
|
})).toContainTextContent(`Navigate to data:text/html`);
|
||||||
|
|
||||||
await client.callTool({
|
expect(await client.callTool({
|
||||||
name: 'browser_take_screenshot',
|
name: 'browser_take_screenshot',
|
||||||
arguments: {},
|
arguments: {
|
||||||
|
filename: 'output.jpeg',
|
||||||
|
},
|
||||||
|
})).toEqual({
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
data: expect.any(String),
|
||||||
|
mimeType: 'image/jpeg',
|
||||||
|
type: 'image',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: expect.stringContaining(`output.jpeg`),
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const files = [...fs.readdirSync(outputDir)];
|
||||||
|
|
||||||
expect(fs.existsSync(outputDir)).toBeTruthy();
|
expect(fs.existsSync(outputDir)).toBeTruthy();
|
||||||
expect([...fs.readdirSync(outputDir)]).toHaveLength(1);
|
expect(files).toHaveLength(1);
|
||||||
|
expect(files[0]).toMatch(/^output.jpeg$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('browser_take_screenshot (noImageResponses)', async ({ startClient }) => {
|
test('browser_take_screenshot (noImageResponses)', async ({ startClient }) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user