From d31644114259007129b3b6932c9ff0f4bfa88837 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 31 Mar 2025 15:01:58 -0700 Subject: [PATCH] chore: sanitize file path when saving (#99) Fixes https://github.com/microsoft/playwright-mcp/issues/96 --- src/tools/common.ts | 4 ++-- src/tools/utils.ts | 4 ++++ tests/basic.spec.ts | 22 ++++++++++++++++++++++ tests/fixtures.ts | 9 ++++----- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/tools/common.ts b/src/tools/common.ts index a04ddaf..71583a5 100644 --- a/src/tools/common.ts +++ b/src/tools/common.ts @@ -20,7 +20,7 @@ import path from 'path'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; -import { captureAriaSnapshot, runAndWait } from './utils'; +import { captureAriaSnapshot, runAndWait, sanitizeForFilePath } from './utils'; import type { ToolFactory, Tool } from './tool'; @@ -127,7 +127,7 @@ export const pdf: Tool = { }, handle: async context => { const page = context.existingPage(); - const fileName = path.join(os.tmpdir(), `/page-${new Date().toISOString()}.pdf`); + const fileName = path.join(os.tmpdir(), sanitizeForFilePath(`page-${new Date().toISOString()}`)) + '.pdf'; await page.pdf({ path: fileName }); return { content: [{ diff --git a/src/tools/utils.ts b/src/tools/utils.ts index 29b6dc9..e9c984e 100644 --- a/src/tools/utils.ts +++ b/src/tools/utils.ts @@ -106,3 +106,7 @@ export async function captureAriaSnapshot(context: Context, status: string = '') content: [{ type: 'text', text: lines.join('\n') }], }; } + +export function sanitizeForFilePath(s: string) { + return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); +} diff --git a/tests/basic.spec.ts b/tests/basic.spec.ts index ee9ecc7..5a04cca 100644 --- a/tests/basic.spec.ts +++ b/tests/basic.spec.ts @@ -331,3 +331,25 @@ test('cdp server', async ({ cdpEndpoint, startClient }) => { ` ); }); + +test('save as pdf', async ({ client }) => { + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: 'data:text/html,TitleHello, world!', + }, + })).toHaveTextContent(` +- Page URL: data:text/html,TitleHello, world! +- Page Title: Title +- Page Snapshot +\`\`\`yaml +- document [ref=s1e2]: Hello, world! +\`\`\` +` + ); + + const response = await client.callTool({ + name: 'browser_save_as_pdf', + }); + expect(response).toHaveTextContent(/^Saved as.*page-[^:]+.pdf$/); +}); diff --git a/tests/fixtures.ts b/tests/fixtures.ts index e805f31..c9c8932 100644 --- a/tests/fixtures.ts +++ b/tests/fixtures.ts @@ -81,15 +81,14 @@ export const test = baseTest.extend({ type Response = Awaited>; export const expect = baseExpect.extend({ - toHaveTextContent(response: Response, content: string | string[]) { + toHaveTextContent(response: Response, content: string | RegExp) { const isNot = this.isNot; try { - content = Array.isArray(content) ? content : [content]; - const texts = (response.content as any).map(c => c.text); + const text = (response.content as any)[0].text; if (isNot) - baseExpect(texts).not.toEqual(content); + baseExpect(text).not.toMatch(content); else - baseExpect(texts).toEqual(content); + baseExpect(text).toMatch(content); } catch (e) { return { pass: isNot,