From 8b8e518029513e73674005855b20352451db3dce Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 28 Jan 2026 20:24:03 -0800 Subject: [PATCH] chore: add test for cli --extension (#1356) --- README.md | 1 + package-lock.json | 32 +++--- package.json | 2 +- packages/extension/tests/extension.spec.ts | 123 +++++++++++++++++++-- packages/playwright-cli/package.json | 4 +- packages/playwright-mcp/package.json | 4 +- 6 files changed, 138 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 96841f2..3ad4222 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,7 @@ Playwright MCP server supports following arguments. They can be provided in the | --port | port to listen on for SSE transport.
*env* `PLAYWRIGHT_MCP_PORT` | | --proxy-bypass | comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"
*env* `PLAYWRIGHT_MCP_PROXY_BYPASS` | | --proxy-server | specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"
*env* `PLAYWRIGHT_MCP_PROXY_SERVER` | +| --sandbox | enable the sandbox for all process types that are normally not sandboxed.
*env* `PLAYWRIGHT_MCP_SANDBOX` | | --save-session | Whether to save the Playwright MCP session into the output directory.
*env* `PLAYWRIGHT_MCP_SAVE_SESSION` | | --save-trace | Whether to save the Playwright Trace of the session into the output directory.
*env* `PLAYWRIGHT_MCP_SAVE_TRACE` | | --save-video | Whether to save the video of the session into the output directory. For example "--save-video=800x600"
*env* `PLAYWRIGHT_MCP_SAVE_VIDEO` | diff --git a/package-lock.json b/package-lock.json index 9d3b0c3..e72f96f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ ], "devDependencies": { "@modelcontextprotocol/sdk": "^1.25.2", - "@playwright/test": "1.59.0-alpha-1769452054000", + "@playwright/test": "1.59.0-alpha-1769561805000", "@types/node": "^24.3.0" } }, @@ -806,13 +806,13 @@ "link": true }, "node_modules/@playwright/test": { - "version": "1.59.0-alpha-1769452054000", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1769452054000.tgz", - "integrity": "sha512-RoLK8rEDtLkfNuRMQtMGFLU+wgBNVHMgUq2/6v9Lh00jucTLsrO0Z4QcPmbo9mGo1jjKmEPIfBas23bJWkN1Jg==", + "version": "1.59.0-alpha-1769561805000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1769561805000.tgz", + "integrity": "sha512-S6Bvamvt2+M3Aolm5CJwUpLoNTqK32NYtbrt6n278vTjQZOxm9XVCp0+cJyuzDbzIdl+1nMdCB7n1NAFkKoB7g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.59.0-alpha-1769452054000" + "playwright": "1.59.0-alpha-1769561805000" }, "bin": { "playwright": "cli.js" @@ -2562,12 +2562,12 @@ } }, "node_modules/playwright": { - "version": "1.59.0-alpha-1769452054000", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1769452054000.tgz", - "integrity": "sha512-emHM/Pt6ACb0zZOOZNNQg6ahAbpiRKgWxmXeqhcmXWYbZ8zk+GIXavyBHYe5O3KC7GEHizECu83x1EldD3vs7Q==", + "version": "1.59.0-alpha-1769561805000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1769561805000.tgz", + "integrity": "sha512-ZSqxE5/k3QdPCQL0mqpiRYVkAeFuELBK6NMuoPfHHHx5d1OH2MBiRUL2KEBtJZXqpQt7QfZP664f4qQ0xW48JA==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.59.0-alpha-1769452054000" + "playwright-core": "1.59.0-alpha-1769561805000" }, "bin": { "playwright": "cli.js" @@ -2580,9 +2580,9 @@ } }, "node_modules/playwright-core": { - "version": "1.59.0-alpha-1769452054000", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1769452054000.tgz", - "integrity": "sha512-JulA7CBOf/Ks/MrXVpylMn9NLKRI933ZOR7A6lqW+VsAgSxrAE+j5BsxArSBaO1dUI1EfrVl0hDzVs4ftnWhaw==", + "version": "1.59.0-alpha-1769561805000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1769561805000.tgz", + "integrity": "sha512-qB8D0mAP1vrqndK6a/v9iCji9jA/aFv95KSh0TJmoQNLzXkPWwq7a3UWmjUjUDKiyWgiQ8WpI59ham7Q+ypBww==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -3325,8 +3325,8 @@ "license": "Apache-2.0", "dependencies": { "minimist": "^1.2.5", - "playwright": "1.59.0-alpha-1769452054000", - "playwright-core": "1.59.0-alpha-1769452054000" + "playwright": "1.59.0-alpha-1769561805000", + "playwright-core": "1.59.0-alpha-1769561805000" }, "bin": { "playwright-cli": "playwright-cli.js" @@ -3340,8 +3340,8 @@ "version": "0.0.61", "license": "Apache-2.0", "dependencies": { - "playwright": "1.59.0-alpha-1769452054000", - "playwright-core": "1.59.0-alpha-1769452054000" + "playwright": "1.59.0-alpha-1769561805000", + "playwright-core": "1.59.0-alpha-1769561805000" }, "bin": { "playwright-mcp": "cli.js" diff --git a/package.json b/package.json index 130e7a0..b902e17 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "devDependencies": { "@modelcontextprotocol/sdk": "^1.25.2", - "@playwright/test": "1.59.0-alpha-1769452054000", + "@playwright/test": "1.59.0-alpha-1769561805000", "@types/node": "^24.3.0" } } diff --git a/packages/extension/tests/extension.spec.ts b/packages/extension/tests/extension.spec.ts index 43c40bb..9ff74b0 100644 --- a/packages/extension/tests/extension.spec.ts +++ b/packages/extension/tests/extension.spec.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import fs from 'fs'; +import fs from 'fs/promises'; import path from 'path'; import { chromium } from 'playwright'; +import { spawn } from 'child_process'; import { test as base, expect } from '../../playwright-mcp/tests/fixtures'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -28,11 +29,17 @@ type BrowserWithExtension = { launch: (mode?: 'disable-extension') => Promise; }; +type CliResult = { + output: string; + error: string; +}; + type TestFixtures = { browserWithExtension: BrowserWithExtension, pathToExtension: string, useShortConnectionTimeout: (timeoutMs: number) => void overrideProtocolVersion: (version: number) => void + cli: (...args: string[]) => Promise; }; const test = base.extend({ @@ -71,6 +78,9 @@ const test = base.extend({ } }); await browserContext?.close(); + + // Free up disk space. + await fs.rm(userDataDir, { recursive: true, force: true }).catch(() => {}); }, useShortConnectionTimeout: async ({}, use) => { @@ -85,9 +95,73 @@ const test = base.extend({ process.env.PWMCP_TEST_PROTOCOL_VERSION = version.toString(); }); process.env.PWMCP_TEST_PROTOCOL_VERSION = undefined; - } + }, + + cli: async ({ mcpBrowser }, use, testInfo) => { + await use(async (...args: string[]) => { + return await runCli(args, { mcpBrowser, testInfo }); + }); + + // Cleanup sessions + await runCli(['session-stop-all'], { mcpBrowser, testInfo }).catch(() => {}); + + const daemonDir = path.join(testInfo.outputDir, 'daemon'); + await fs.rm(daemonDir, { recursive: true, force: true }).catch(() => {}); + }, }); +async function runCli( + args: string[], + options: { mcpBrowser?: string, testInfo: any }, +): Promise { + const stepTitle = `cli ${args.join(' ')}`; + + return await test.step(stepTitle, async () => { + const testInfo = options.testInfo; + + // Path to the terminal CLI + const cliPath = path.join(__dirname, '../../../node_modules/playwright/lib/mcp/terminal/cli.js'); + + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + + const childProcess = spawn(process.execPath, [cliPath, ...args], { + cwd: testInfo.outputPath(), + env: { + ...process.env, + PLAYWRIGHT_DAEMON_INSTALL_DIR: testInfo.outputPath(), + PLAYWRIGHT_DAEMON_SESSION_DIR: testInfo.outputPath('daemon'), + PLAYWRIGHT_DAEMON_SOCKETS_DIR: path.join(testInfo.project.outputDir, 'daemon-sockets'), + PLAYWRIGHT_MCP_BROWSER: options.mcpBrowser, + PLAYWRIGHT_MCP_HEADLESS: 'false', + }, + detached: true, + }); + + childProcess.stdout?.on('data', (data) => { + stdout += data.toString(); + }); + + childProcess.stderr?.on('data', (data) => { + if (process.env.PWMCP_DEBUG) + process.stderr.write(data); + stderr += data.toString(); + }); + + childProcess.on('close', async (code) => { + await testInfo.attach(stepTitle, { body: stdout, contentType: 'text/plain' }); + resolve({ + output: stdout.trim(), + error: stderr.trim(), + }); + }); + + childProcess.on('error', reject); + }); + }); +} + async function startWithExtensionFlag(browserWithExtension: BrowserWithExtension, startClient: StartClient): Promise { const { client } = await startClient({ args: [`--extension`], @@ -105,11 +179,11 @@ const testWithOldExtensionVersion = test.extend({ const extensionDir = testInfo.outputPath('extension'); const oldPath = path.resolve(__dirname, '../dist'); - await fs.promises.cp(oldPath, extensionDir, { recursive: true }); + await fs.cp(oldPath, extensionDir, { recursive: true }); const manifestPath = path.join(extensionDir, 'manifest.json'); - const manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8')); + const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8')); manifest.version = '0.0.1'; - await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); + await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); await use(extensionDir); }, @@ -251,7 +325,7 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi useShortConnectionTimeout(1000); const executablePath = test.info().outputPath('echo.sh'); - await fs.promises.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); + await fs.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); const { client } = await startClient({ args: [`--extension`], @@ -272,7 +346,7 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi error: expect.stringContaining('Extension connection timeout.'), isError: true, }); - expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?'); + expect(await fs.readFile(test.info().outputPath('output.txt'), 'utf8')).toMatch(/Custom exec args.*chrome-extension:\/\/jakfalbnbhgkpmoaakfflhflbfpkailf\/connect\.html\?/); }); test(`bypass connection dialog with token`, async ({ browserWithExtension, startClient, server }) => { @@ -302,3 +376,38 @@ test(`bypass connection dialog with token`, async ({ browserWithExtension, start snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); }); + +test.describe('CLI with extension', () => { + test('open --extension', async ({ browserWithExtension, cli, server }, testInfo) => { + const browserContext = await browserWithExtension.launch(); + + // Write config file with userDataDir + const configPath = testInfo.outputPath('cli-config.json'); + await fs.writeFile(configPath, JSON.stringify({ + browser: { + userDataDir: browserWithExtension.userDataDir, + } + }, null, 2)); + + const confirmationPagePromise = browserContext.waitForEvent('page', page => { + return page.url().startsWith('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html'); + }); + + // Start the CLI command in the background + const cliPromise = cli('open', server.HELLO_WORLD, '--extension', `--config=cli-config.json`); + + // Wait for the confirmation page to appear + const confirmationPage = await confirmationPagePromise; + + // Click the Allow button + await confirmationPage.getByRole('button', { name: 'Allow' }).click(); + + // Wait for the CLI command to complete + const { output } = await cliPromise; + + // Verify the output + expect(output).toContain(`### Page`); + expect(output).toContain(`- Page URL: ${server.HELLO_WORLD}`); + expect(output).toContain(`- Page Title: Title`); + }); +}); diff --git a/packages/playwright-cli/package.json b/packages/playwright-cli/package.json index 4a1ef80..2fc258c 100644 --- a/packages/playwright-cli/package.json +++ b/packages/playwright-cli/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "minimist": "^1.2.5", - "playwright": "1.59.0-alpha-1769452054000", - "playwright-core": "1.59.0-alpha-1769452054000" + "playwright": "1.59.0-alpha-1769561805000", + "playwright-core": "1.59.0-alpha-1769561805000" }, "bin": { "playwright-cli": "playwright-cli.js" diff --git a/packages/playwright-mcp/package.json b/packages/playwright-mcp/package.json index bff7d3c..f687110 100644 --- a/packages/playwright-mcp/package.json +++ b/packages/playwright-mcp/package.json @@ -34,8 +34,8 @@ } }, "dependencies": { - "playwright": "1.59.0-alpha-1769452054000", - "playwright-core": "1.59.0-alpha-1769452054000" + "playwright": "1.59.0-alpha-1769561805000", + "playwright-core": "1.59.0-alpha-1769561805000" }, "bin": { "playwright-mcp": "cli.js"