mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2026-01-30 06:22:03 +00:00
chore: add test for cli --extension (#1356)
This commit is contained in:
@@ -363,6 +363,7 @@ Playwright MCP server supports following arguments. They can be provided in the
|
|||||||
| --port <port> | port to listen on for SSE transport.<br>*env* `PLAYWRIGHT_MCP_PORT` |
|
| --port <port> | port to listen on for SSE transport.<br>*env* `PLAYWRIGHT_MCP_PORT` |
|
||||||
| --proxy-bypass <bypass> | comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"<br>*env* `PLAYWRIGHT_MCP_PROXY_BYPASS` |
|
| --proxy-bypass <bypass> | comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"<br>*env* `PLAYWRIGHT_MCP_PROXY_BYPASS` |
|
||||||
| --proxy-server <proxy> | specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"<br>*env* `PLAYWRIGHT_MCP_PROXY_SERVER` |
|
| --proxy-server <proxy> | specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"<br>*env* `PLAYWRIGHT_MCP_PROXY_SERVER` |
|
||||||
|
| --sandbox | enable the sandbox for all process types that are normally not sandboxed.<br>*env* `PLAYWRIGHT_MCP_SANDBOX` |
|
||||||
| --save-session | Whether to save the Playwright MCP session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_SESSION` |
|
| --save-session | Whether to save the Playwright MCP session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_SESSION` |
|
||||||
| --save-trace | Whether to save the Playwright Trace of the session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_TRACE` |
|
| --save-trace | Whether to save the Playwright Trace of the session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_TRACE` |
|
||||||
| --save-video <size> | Whether to save the video of the session into the output directory. For example "--save-video=800x600"<br>*env* `PLAYWRIGHT_MCP_SAVE_VIDEO` |
|
| --save-video <size> | Whether to save the video of the session into the output directory. For example "--save-video=800x600"<br>*env* `PLAYWRIGHT_MCP_SAVE_VIDEO` |
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -13,7 +13,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.25.2",
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||||
"@playwright/test": "1.59.0-alpha-1769452054000",
|
"@playwright/test": "1.59.0-alpha-1769561805000",
|
||||||
"@types/node": "^24.3.0"
|
"@types/node": "^24.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -806,13 +806,13 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.59.0-alpha-1769452054000",
|
"version": "1.59.0-alpha-1769561805000",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1769452054000.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1769561805000.tgz",
|
||||||
"integrity": "sha512-RoLK8rEDtLkfNuRMQtMGFLU+wgBNVHMgUq2/6v9Lh00jucTLsrO0Z4QcPmbo9mGo1jjKmEPIfBas23bJWkN1Jg==",
|
"integrity": "sha512-S6Bvamvt2+M3Aolm5CJwUpLoNTqK32NYtbrt6n278vTjQZOxm9XVCp0+cJyuzDbzIdl+1nMdCB7n1NAFkKoB7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.59.0-alpha-1769452054000"
|
"playwright": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -2562,12 +2562,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.59.0-alpha-1769452054000",
|
"version": "1.59.0-alpha-1769561805000",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1769452054000.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1769561805000.tgz",
|
||||||
"integrity": "sha512-emHM/Pt6ACb0zZOOZNNQg6ahAbpiRKgWxmXeqhcmXWYbZ8zk+GIXavyBHYe5O3KC7GEHizECu83x1EldD3vs7Q==",
|
"integrity": "sha512-ZSqxE5/k3QdPCQL0mqpiRYVkAeFuELBK6NMuoPfHHHx5d1OH2MBiRUL2KEBtJZXqpQt7QfZP664f4qQ0xW48JA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.59.0-alpha-1769452054000"
|
"playwright-core": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@@ -2580,9 +2580,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.59.0-alpha-1769452054000",
|
"version": "1.59.0-alpha-1769561805000",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1769452054000.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1769561805000.tgz",
|
||||||
"integrity": "sha512-JulA7CBOf/Ks/MrXVpylMn9NLKRI933ZOR7A6lqW+VsAgSxrAE+j5BsxArSBaO1dUI1EfrVl0hDzVs4ftnWhaw==",
|
"integrity": "sha512-qB8D0mAP1vrqndK6a/v9iCji9jA/aFv95KSh0TJmoQNLzXkPWwq7a3UWmjUjUDKiyWgiQ8WpI59ham7Q+ypBww==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
@@ -3325,8 +3325,8 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"playwright": "1.59.0-alpha-1769452054000",
|
"playwright": "1.59.0-alpha-1769561805000",
|
||||||
"playwright-core": "1.59.0-alpha-1769452054000"
|
"playwright-core": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-cli": "playwright-cli.js"
|
"playwright-cli": "playwright-cli.js"
|
||||||
@@ -3340,8 +3340,8 @@
|
|||||||
"version": "0.0.61",
|
"version": "0.0.61",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.59.0-alpha-1769452054000",
|
"playwright": "1.59.0-alpha-1769561805000",
|
||||||
"playwright-core": "1.59.0-alpha-1769452054000"
|
"playwright-core": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-mcp": "cli.js"
|
"playwright-mcp": "cli.js"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.25.2",
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||||
"@playwright/test": "1.59.0-alpha-1769452054000",
|
"@playwright/test": "1.59.0-alpha-1769561805000",
|
||||||
"@types/node": "^24.3.0"
|
"@types/node": "^24.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
import { test as base, expect } from '../../playwright-mcp/tests/fixtures';
|
import { test as base, expect } from '../../playwright-mcp/tests/fixtures';
|
||||||
|
|
||||||
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
@@ -28,11 +29,17 @@ type BrowserWithExtension = {
|
|||||||
launch: (mode?: 'disable-extension') => Promise<BrowserContext>;
|
launch: (mode?: 'disable-extension') => Promise<BrowserContext>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CliResult = {
|
||||||
|
output: string;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
type TestFixtures = {
|
type TestFixtures = {
|
||||||
browserWithExtension: BrowserWithExtension,
|
browserWithExtension: BrowserWithExtension,
|
||||||
pathToExtension: string,
|
pathToExtension: string,
|
||||||
useShortConnectionTimeout: (timeoutMs: number) => void
|
useShortConnectionTimeout: (timeoutMs: number) => void
|
||||||
overrideProtocolVersion: (version: number) => void
|
overrideProtocolVersion: (version: number) => void
|
||||||
|
cli: (...args: string[]) => Promise<CliResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const test = base.extend<TestFixtures>({
|
const test = base.extend<TestFixtures>({
|
||||||
@@ -71,6 +78,9 @@ const test = base.extend<TestFixtures>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await browserContext?.close();
|
await browserContext?.close();
|
||||||
|
|
||||||
|
// Free up disk space.
|
||||||
|
await fs.rm(userDataDir, { recursive: true, force: true }).catch(() => {});
|
||||||
},
|
},
|
||||||
|
|
||||||
useShortConnectionTimeout: async ({}, use) => {
|
useShortConnectionTimeout: async ({}, use) => {
|
||||||
@@ -85,9 +95,73 @@ const test = base.extend<TestFixtures>({
|
|||||||
process.env.PWMCP_TEST_PROTOCOL_VERSION = version.toString();
|
process.env.PWMCP_TEST_PROTOCOL_VERSION = version.toString();
|
||||||
});
|
});
|
||||||
process.env.PWMCP_TEST_PROTOCOL_VERSION = undefined;
|
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<CliResult> {
|
||||||
|
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<CliResult>((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<Client> {
|
async function startWithExtensionFlag(browserWithExtension: BrowserWithExtension, startClient: StartClient): Promise<Client> {
|
||||||
const { client } = await startClient({
|
const { client } = await startClient({
|
||||||
args: [`--extension`],
|
args: [`--extension`],
|
||||||
@@ -105,11 +179,11 @@ const testWithOldExtensionVersion = test.extend({
|
|||||||
const extensionDir = testInfo.outputPath('extension');
|
const extensionDir = testInfo.outputPath('extension');
|
||||||
const oldPath = path.resolve(__dirname, '../dist');
|
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 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';
|
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);
|
await use(extensionDir);
|
||||||
},
|
},
|
||||||
@@ -251,7 +325,7 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi
|
|||||||
useShortConnectionTimeout(1000);
|
useShortConnectionTimeout(1000);
|
||||||
|
|
||||||
const executablePath = test.info().outputPath('echo.sh');
|
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({
|
const { client } = await startClient({
|
||||||
args: [`--extension`],
|
args: [`--extension`],
|
||||||
@@ -272,7 +346,7 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi
|
|||||||
error: expect.stringContaining('Extension connection timeout.'),
|
error: expect.stringContaining('Extension connection timeout.'),
|
||||||
isError: true,
|
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 }) => {
|
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!`),
|
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('CLI with extension', () => {
|
||||||
|
test('open <url> --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`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"playwright": "1.59.0-alpha-1769452054000",
|
"playwright": "1.59.0-alpha-1769561805000",
|
||||||
"playwright-core": "1.59.0-alpha-1769452054000"
|
"playwright-core": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-cli": "playwright-cli.js"
|
"playwright-cli": "playwright-cli.js"
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.59.0-alpha-1769452054000",
|
"playwright": "1.59.0-alpha-1769561805000",
|
||||||
"playwright-core": "1.59.0-alpha-1769452054000"
|
"playwright-core": "1.59.0-alpha-1769561805000"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-mcp": "cli.js"
|
"playwright-mcp": "cli.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user