chore: allow specifying output dir (#285)

Ref: https://github.com/microsoft/playwright-mcp/issues/279
This commit is contained in:
Pavel Feldman 2025-04-28 16:35:33 -07:00 committed by GitHub
parent 6e76d5e550
commit 697a69a8c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 52 additions and 11 deletions

5
config.d.ts vendored
View File

@ -80,4 +80,9 @@ export type Config = {
* Run server that uses screenshots (Aria snapshots are used by default).
*/
vision?: boolean;
/**
* The directory to save output files.
*/
outputDir?: string;
};

View File

@ -14,8 +14,12 @@
* limitations under the License.
*/
import fs from 'fs';
import net from 'net';
import os from 'os';
import path from 'path';
import { sanitizeForFilePath } from './tools/utils';
import type { Config } from '../config';
import type { LaunchOptions, BrowserContextOptions } from 'playwright';
@ -84,3 +88,10 @@ async function findFreePort() {
server.on('error', reject);
});
}
export async function outputFile(config: Config, name: string): Promise<string> {
const result = config.outputDir ?? os.tmpdir();
await fs.promises.mkdir(result, { recursive: true });
const fileName = sanitizeForFilePath(name);
return path.join(result, fileName);
}

View File

@ -14,14 +14,11 @@
* limitations under the License.
*/
import os from 'os';
import path from 'path';
import { z } from 'zod';
import { defineTool } from './tool';
import { sanitizeForFilePath } from './utils';
import * as javascript from '../javascript';
import { outputFile } from '../config';
const pdf = defineTool({
capability: 'pdf',
@ -34,7 +31,7 @@ const pdf = defineTool({
handle: async context => {
const tab = context.currentTabOrDie();
const fileName = path.join(os.tmpdir(), sanitizeForFilePath(`page-${new Date().toISOString()}`)) + '.pdf';
const fileName = await outputFile(context.config, `page-${new Date().toISOString()}'.pdf'`);
const code = [
`// Save page as ${fileName}`,

View File

@ -14,14 +14,11 @@
* limitations under the License.
*/
import path from 'path';
import os from 'os';
import { z } from 'zod';
import { sanitizeForFilePath } from './utils';
import { defineTool } from './tool';
import * as javascript from '../javascript';
import { outputFile } from '../config';
import type * as playwright from 'playwright';
@ -232,7 +229,7 @@ const screenshot = defineTool({
const tab = context.currentTabOrDie();
const snapshot = tab.snapshotOrDie();
const fileType = params.raw ? 'png' : 'jpeg';
const fileName = path.join(os.tmpdir(), sanitizeForFilePath(`page-${new Date().toISOString()}`)) + `.${fileType}`;
const fileName = await outputFile(context.config, `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;

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import { chromium } from 'playwright';
@ -23,10 +24,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { spawn } from 'child_process';
import { TestServer } from './testserver';
import type { Config } from '../config';
type TestFixtures = {
client: Client;
visionClient: Client;
startClient: (options?: { args?: string[] }) => Promise<Client>;
startClient: (options?: { args?: string[], config?: Config }) => Promise<Client>;
wsEndpoint: string;
cdpEndpoint: string;
server: TestServer;
@ -61,6 +64,11 @@ export const test = baseTest.extend<TestFixtures, WorkerFixtures>({
args.push(`--browser=${mcpBrowser}`);
if (options?.args)
args.push(...options.args);
if (options?.config) {
const configFile = testInfo.outputPath('config.json');
await fs.promises.writeFile(configFile, JSON.stringify(options.config, null, 2));
args.push(`--config=${configFile}`);
}
const transport = new StdioClientTransport({
command: 'node',
args: [path.join(__dirname, '../cli.js'), ...args],

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
import fs from 'fs';
import { test, expect } from './fixtures';
test('browser_take_screenshot (viewport)', async ({ client }) => {
@ -70,3 +72,24 @@ test('browser_take_screenshot (element)', async ({ client }) => {
],
});
});
test('browser_take_screenshot (outputDir)', async ({ startClient }, testInfo) => {
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`);
await client.callTool({
name: 'browser_take_screenshot',
arguments: {},
});
expect(fs.existsSync(outputDir)).toBeTruthy();
expect([...fs.readdirSync(outputDir)]).toHaveLength(1);
});