Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
177b008328 | ||
|
|
9429463951 | ||
|
|
45f493da6c | ||
|
|
9e5ffd2ccf | ||
|
|
1051ea810a | ||
|
|
f20ae22ec6 | ||
|
|
13cd1b4bd9 |
@@ -137,7 +137,10 @@ Playwright MCP server supports following arguments. They can be provided in the
|
||||
--ignore-https-errors ignore https errors
|
||||
--isolated keep the browser profile in memory, do not save
|
||||
it to disk.
|
||||
--no-image-responses do not send image responses to the client.
|
||||
--image-responses <mode> whether to send image responses to the client.
|
||||
Can be "allow", "omit", or "auto". Defaults to
|
||||
"auto", which sends images if the client can
|
||||
display them.
|
||||
--no-sandbox disable the sandbox for all process types that
|
||||
are normally sandboxed.
|
||||
--output-dir <path> path to the directory for output files.
|
||||
|
||||
4
config.d.ts
vendored
4
config.d.ts
vendored
@@ -117,7 +117,7 @@ export type Config = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Do not send image responses to the client.
|
||||
* Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
|
||||
*/
|
||||
noImageResponses?: boolean;
|
||||
imageResponses?: 'allow' | 'omit' | 'auto';
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Generate test for scenario:
|
||||
Use Playwright tools to generate test for scenario:
|
||||
|
||||
## GitHub PR Checks Navigation Checklist
|
||||
|
||||
|
||||
2
index.js
2
index.js
@@ -15,5 +15,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createConnection } from './lib/index';
|
||||
import { createConnection } from './lib/index.js';
|
||||
export { createConnection };
|
||||
|
||||
30
package-lock.json
generated
30
package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@playwright/mcp",
|
||||
"version": "0.0.26",
|
||||
"version": "0.0.27",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@playwright/mcp",
|
||||
"version": "0.0.26",
|
||||
"version": "0.0.27",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
"commander": "^13.1.0",
|
||||
"playwright": "1.53.0-alpha-1746832516000",
|
||||
"playwright": "1.53.0-alpha-2025-05-27",
|
||||
"zod-to-json-schema": "^3.24.4"
|
||||
},
|
||||
"bin": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@playwright/test": "1.53.0-alpha-1746832516000",
|
||||
"@playwright/test": "1.53.0-alpha-2025-05-27",
|
||||
"@stylistic/eslint-plugin": "^3.0.1",
|
||||
"@types/node": "^22.13.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
@@ -286,13 +286,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.53.0-alpha-1746832516000",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-1746832516000.tgz",
|
||||
"integrity": "sha512-Sec+6uzpA4MfwmQqJFBFVazffynqVwLO5swDxG7WoqgpUdn9gQX4K4tDG64SV6f4nOpwdM5LKTasPSXu02nn/Q==",
|
||||
"version": "1.53.0-alpha-2025-05-27",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0-alpha-2025-05-27.tgz",
|
||||
"integrity": "sha512-G2zG56kEQOWhk3nQyPKH5u41jyQw5jx+Kga5huUi7RjBjPEnNtiCMNXMNGCh6dDYCIyQkLJvz/o1H/QN26HLsg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.53.0-alpha-1746832516000"
|
||||
"playwright": "1.53.0-alpha-2025-05-27"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -3298,12 +3298,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.53.0-alpha-1746832516000",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-1746832516000.tgz",
|
||||
"integrity": "sha512-kcC1B2XJr4VaDAcVzi61SbYGkodq1QIqQXuPieXsNgZZ7cEKWzO2sI42yp2yie6wlCx0oLkSS2Q6jWSRVRLeaw==",
|
||||
"version": "1.53.0-alpha-2025-05-27",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0-alpha-2025-05-27.tgz",
|
||||
"integrity": "sha512-CD0BTwV5javEJ3hf3rhFJEvR3ZoWsu4HUQFfLH2mtVVe+grGPCP55FnlOjpDnJ5pP4Kibe/ZcmgPDg56ic/y9g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.53.0-alpha-1746832516000"
|
||||
"playwright-core": "1.53.0-alpha-2025-05-27"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -3316,9 +3316,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.53.0-alpha-1746832516000",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-1746832516000.tgz",
|
||||
"integrity": "sha512-4O98y4zV0rOP6CepMLC/VGuzqGaR1sS9AVh+i0CghWMQHM/8bxPJI8W38QndO0JU0V5nBD6j7DQeNt1mJ+CZ+g==",
|
||||
"version": "1.53.0-alpha-2025-05-27",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0-alpha-2025-05-27.tgz",
|
||||
"integrity": "sha512-uVxs7YjENoBMFyQhsZWImIBuo/oX7Mu63djhQN3qFz/NdXA/rOAnP73XzfB+VJNwRMKgIOtqHQgjOG3Rl/lm0A==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@playwright/mcp",
|
||||
"version": "0.0.26",
|
||||
"version": "0.0.27",
|
||||
"description": "Playwright Tools for MCP",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
@@ -37,13 +37,13 @@
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
"commander": "^13.1.0",
|
||||
"playwright": "1.53.0-alpha-1746832516000",
|
||||
"playwright": "1.53.0-alpha-2025-05-27",
|
||||
"zod-to-json-schema": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@playwright/test": "1.53.0-alpha-1746832516000",
|
||||
"@playwright/test": "1.53.0-alpha-2025-05-27",
|
||||
"@stylistic/eslint-plugin": "^3.0.1",
|
||||
"@types/node": "^22.13.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
|
||||
@@ -29,7 +29,14 @@ export default defineConfig<TestOptions>({
|
||||
{ name: 'chrome' },
|
||||
{ name: 'msedge', use: { mcpBrowser: 'msedge' } },
|
||||
{ name: 'chromium', use: { mcpBrowser: 'chromium' } },
|
||||
...process.env.MCP_IN_DOCKER ? [{ name: 'chromium-docker', use: { mcpBrowser: 'chromium', mcpMode: 'docker' as const } }] : [],
|
||||
...process.env.MCP_IN_DOCKER ? [{
|
||||
name: 'chromium-docker',
|
||||
grep: /browser_navigate|browser_click/,
|
||||
use: {
|
||||
mcpBrowser: 'chromium',
|
||||
mcpMode: 'docker' as const
|
||||
}
|
||||
}] : [],
|
||||
{ name: 'firefox', use: { mcpBrowser: 'firefox' } },
|
||||
{ name: 'webkit', use: { mcpBrowser: 'webkit' } },
|
||||
],
|
||||
|
||||
@@ -38,7 +38,7 @@ export type CLIOptions = {
|
||||
host?: string;
|
||||
ignoreHttpsErrors?: boolean;
|
||||
isolated?: boolean;
|
||||
imageResponses: boolean;
|
||||
imageResponses?: 'allow' | 'omit' | 'auto';
|
||||
sandbox: boolean;
|
||||
outputDir?: string;
|
||||
port?: number;
|
||||
@@ -94,11 +94,13 @@ export async function resolveCLIConfig(cliOptions: CLIOptions): Promise<FullConf
|
||||
// Derive artifact output directory from config.outputDir
|
||||
if (result.saveTrace)
|
||||
result.browser.launchOptions.tracesDir = path.join(result.outputDir, 'traces');
|
||||
if (result.browser.browserName === 'chromium')
|
||||
(result.browser.launchOptions as any).cdpPort = await findFreePort();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function configFromCLIOptions(cliOptions: CLIOptions): Promise<Config> {
|
||||
let browserName: 'chromium' | 'firefox' | 'webkit';
|
||||
let browserName: 'chromium' | 'firefox' | 'webkit' | undefined;
|
||||
let channel: string | undefined;
|
||||
switch (cliOptions.browser) {
|
||||
case 'chrome':
|
||||
@@ -119,9 +121,6 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise<Conf
|
||||
case 'webkit':
|
||||
browserName = 'webkit';
|
||||
break;
|
||||
default:
|
||||
browserName = 'chromium';
|
||||
channel = 'chrome';
|
||||
}
|
||||
|
||||
// Launch options
|
||||
@@ -131,13 +130,9 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise<Conf
|
||||
headless: cliOptions.headless,
|
||||
};
|
||||
|
||||
if (browserName === 'chromium') {
|
||||
(launchOptions as any).cdpPort = await findFreePort();
|
||||
if (!cliOptions.sandbox) {
|
||||
// --no-sandbox was passed, disable the sandbox
|
||||
launchOptions.chromiumSandbox = false;
|
||||
}
|
||||
}
|
||||
// --no-sandbox was passed, disable the sandbox
|
||||
if (!cliOptions.sandbox)
|
||||
launchOptions.chromiumSandbox = false;
|
||||
|
||||
if (cliOptions.proxyServer) {
|
||||
launchOptions.proxy = {
|
||||
@@ -193,13 +188,9 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise<Conf
|
||||
},
|
||||
saveTrace: cliOptions.saveTrace,
|
||||
outputDir: cliOptions.outputDir,
|
||||
imageResponses: cliOptions.imageResponses,
|
||||
};
|
||||
|
||||
if (!cliOptions.imageResponses) {
|
||||
// --no-image-responses was passed, disable image responses
|
||||
result.noImageResponses = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,7 @@ export class Connection {
|
||||
await new Promise<void>(resolve => {
|
||||
this.server.oninitialized = () => resolve();
|
||||
});
|
||||
if (this.server.getClientVersion()?.name.includes('cursor'))
|
||||
this.context.config.noImageResponses = true;
|
||||
this.context.clientVersion = this.server.getClientVersion();
|
||||
}
|
||||
|
||||
async close() {
|
||||
|
||||
@@ -48,12 +48,21 @@ export class Context {
|
||||
private _modalStates: (ModalState & { tab: Tab })[] = [];
|
||||
private _pendingAction: PendingAction | undefined;
|
||||
private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = [];
|
||||
clientVersion: { name: string; version: string; } | undefined;
|
||||
|
||||
constructor(tools: Tool[], config: FullConfig) {
|
||||
this.tools = tools;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
clientSupportsImages(): boolean {
|
||||
if (this.config.imageResponses === 'allow')
|
||||
return true;
|
||||
if (this.config.imageResponses === 'omit')
|
||||
return false;
|
||||
return !this.clientVersion?.name.includes('cursor');
|
||||
}
|
||||
|
||||
modalStates(): ModalState[] {
|
||||
return this._modalStates;
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ export class PageSnapshot {
|
||||
}
|
||||
|
||||
private async _build() {
|
||||
// FIXME: Rountrip evaluate to ensure _snapshotForAI works.
|
||||
// This probably broke once we moved off locator snapshots
|
||||
await this._page.evaluate(() => 1);
|
||||
const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI());
|
||||
this._text = [
|
||||
`- Page Snapshot`,
|
||||
@@ -52,7 +49,7 @@ export class PageSnapshot {
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
refLocator(ref: string): playwright.Locator {
|
||||
return this._page.locator(`aria-ref=${ref}`);
|
||||
refLocator(params: { element: string, ref: string }): playwright.Locator {
|
||||
return this._page.locator(`aria-ref=${params.ref}`).describe(params.element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ program
|
||||
.option('--host <host>', 'host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
|
||||
.option('--ignore-https-errors', 'ignore https errors')
|
||||
.option('--isolated', 'keep the browser profile in memory, do not save it to disk.')
|
||||
.option('--no-image-responses', 'do not send image responses to the client.')
|
||||
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.')
|
||||
.option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.')
|
||||
.option('--output-dir <path>', 'path to the directory for output files.')
|
||||
.option('--port <port>', 'port to listen on for SSE transport.')
|
||||
|
||||
@@ -57,14 +57,14 @@ const screenshot = defineTool({
|
||||
`// Screenshot ${isElementScreenshot ? params.element : 'viewport'} and save it as ${fileName}`,
|
||||
];
|
||||
|
||||
const locator = params.ref ? snapshot.refLocator(params.ref) : null;
|
||||
const locator = params.ref ? snapshot.refLocator({ element: params.element || '', ref: params.ref }) : null;
|
||||
|
||||
if (locator)
|
||||
code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
|
||||
else
|
||||
code.push(`await page.screenshot(${javascript.formatObject(options)});`);
|
||||
|
||||
const includeBase64 = !context.config.noImageResponses;
|
||||
const includeBase64 = context.clientSupportsImages();
|
||||
const action = async () => {
|
||||
const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
|
||||
return {
|
||||
|
||||
@@ -58,7 +58,7 @@ const click = defineTool({
|
||||
|
||||
handle: async (context, params) => {
|
||||
const tab = context.currentTabOrDie();
|
||||
const locator = tab.snapshotOrDie().refLocator(params.ref);
|
||||
const locator = tab.snapshotOrDie().refLocator(params);
|
||||
|
||||
const code = [
|
||||
`// Click ${params.element}`,
|
||||
@@ -91,8 +91,8 @@ const drag = defineTool({
|
||||
|
||||
handle: async (context, params) => {
|
||||
const snapshot = context.currentTabOrDie().snapshotOrDie();
|
||||
const startLocator = snapshot.refLocator(params.startRef);
|
||||
const endLocator = snapshot.refLocator(params.endRef);
|
||||
const startLocator = snapshot.refLocator({ ref: params.startRef, element: params.startElement });
|
||||
const endLocator = snapshot.refLocator({ ref: params.endRef, element: params.endElement });
|
||||
|
||||
const code = [
|
||||
`// Drag ${params.startElement} to ${params.endElement}`,
|
||||
@@ -120,7 +120,7 @@ const hover = defineTool({
|
||||
|
||||
handle: async (context, params) => {
|
||||
const snapshot = context.currentTabOrDie().snapshotOrDie();
|
||||
const locator = snapshot.refLocator(params.ref);
|
||||
const locator = snapshot.refLocator(params);
|
||||
|
||||
const code = [
|
||||
`// Hover over ${params.element}`,
|
||||
@@ -154,7 +154,7 @@ const type = defineTool({
|
||||
|
||||
handle: async (context, params) => {
|
||||
const snapshot = context.currentTabOrDie().snapshotOrDie();
|
||||
const locator = snapshot.refLocator(params.ref);
|
||||
const locator = snapshot.refLocator(params);
|
||||
|
||||
const code: string[] = [];
|
||||
const steps: (() => Promise<void>)[] = [];
|
||||
@@ -200,7 +200,7 @@ const selectOption = defineTool({
|
||||
|
||||
handle: async (context, params) => {
|
||||
const snapshot = context.currentTabOrDie().snapshotOrDie();
|
||||
const locator = snapshot.refLocator(params.ref);
|
||||
const locator = snapshot.refLocator(params);
|
||||
|
||||
const code = [
|
||||
`// Select options [${params.values.join(', ')}] in ${params.element}`,
|
||||
|
||||
@@ -78,9 +78,9 @@ export function sanitizeForFilePath(s: string) {
|
||||
}
|
||||
|
||||
export async function generateLocator(locator: playwright.Locator): Promise<string> {
|
||||
return (locator as any)._frame._wrapApiCall(() => (locator as any)._generateLocatorString(), true);
|
||||
return (locator as any)._generateLocatorString();
|
||||
}
|
||||
|
||||
export async function callOnPageNoTrace<T>(page: playwright.Page, callback: (page: playwright.Page) => Promise<T>): Promise<T> {
|
||||
return await (page as any)._wrapApiCall(() => callback(page), true);
|
||||
return await (page as any)._wrapApiCall(() => callback(page), { internal: true });
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import fs from 'node:fs';
|
||||
import { Config } from '../config.js';
|
||||
import { test, expect } from './fixtures.js';
|
||||
|
||||
test('config user data dir', async ({ startClient, localOutputPath, server }) => {
|
||||
test('config user data dir', async ({ startClient, server }, testInfo) => {
|
||||
server.setContent('/', `
|
||||
<title>Title</title>
|
||||
<body>Hello, world!</body>
|
||||
@@ -27,10 +27,10 @@ test('config user data dir', async ({ startClient, localOutputPath, server }) =>
|
||||
|
||||
const config: Config = {
|
||||
browser: {
|
||||
userDataDir: localOutputPath('user-data-dir'),
|
||||
userDataDir: testInfo.outputPath('user-data-dir'),
|
||||
},
|
||||
};
|
||||
const configPath = localOutputPath('config.json');
|
||||
const configPath = testInfo.outputPath('config.json');
|
||||
await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
const client = await startClient({ args: ['--config', configPath] });
|
||||
@@ -42,3 +42,22 @@ test('config user data dir', async ({ startClient, localOutputPath, server }) =>
|
||||
const files = await fs.promises.readdir(config.browser!.userDataDir!);
|
||||
expect(files.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test.describe(() => {
|
||||
test.use({ mcpBrowser: '' });
|
||||
test('browserName', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-mcp/issues/458' } }, async ({ startClient }, testInfo) => {
|
||||
const config: Config = {
|
||||
browser: {
|
||||
browserName: 'firefox',
|
||||
},
|
||||
};
|
||||
const configPath = testInfo.outputPath('config.json');
|
||||
await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
const client = await startClient({ args: ['--config', configPath] });
|
||||
expect(await client.callTool({
|
||||
name: 'browser_navigate',
|
||||
arguments: { url: 'data:text/html,<script>document.title = navigator.userAgent</script>' },
|
||||
})).toContainTextContent(`Firefox`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
import { test, expect } from './fixtures.js';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
test('browser_file_upload', async ({ client, localOutputPath, server }) => {
|
||||
test('browser_file_upload', async ({ client, server }, testInfo) => {
|
||||
server.setContent('/', `
|
||||
<input type="file" />
|
||||
<button>Button</button>
|
||||
@@ -54,7 +53,7 @@ The tool "browser_file_upload" can only be used when there is related modal stat
|
||||
})).toContainTextContent(`### Modal state
|
||||
- [File chooser]: can be handled by the "browser_file_upload" tool`);
|
||||
|
||||
const filePath = localOutputPath('test.txt');
|
||||
const filePath = testInfo.outputPath('test.txt');
|
||||
await fs.writeFile(filePath, 'Hello, world!');
|
||||
|
||||
{
|
||||
@@ -101,10 +100,9 @@ The tool "browser_file_upload" can only be used when there is related modal stat
|
||||
}
|
||||
});
|
||||
|
||||
test('clicking on download link emits download', async ({ startClient, localOutputPath, server }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('clicking on download link emits download', async ({ startClient, server }, testInfo) => {
|
||||
const client = await startClient({
|
||||
config: { outputDir },
|
||||
config: { outputDir: testInfo.outputPath('output') },
|
||||
});
|
||||
|
||||
server.setContent('/', `<a href="/download" download="test.txt">Download</a>`, 'text/html');
|
||||
@@ -123,13 +121,12 @@ test('clicking on download link emits download', async ({ startClient, localOutp
|
||||
});
|
||||
await expect.poll(() => client.callTool({ name: 'browser_snapshot' })).toContainTextContent(`
|
||||
### Downloads
|
||||
- Downloaded file test.txt to ${path.join(outputDir, 'test.txt')}`);
|
||||
- Downloaded file test.txt to ${testInfo.outputPath('output', 'test.txt')}`);
|
||||
});
|
||||
|
||||
test('navigating to download link emits download', async ({ startClient, localOutputPath, mcpBrowser, server }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('navigating to download link emits download', async ({ startClient, server, mcpBrowser }, testInfo) => {
|
||||
const client = await startClient({
|
||||
args: ['--output-dir', outputDir],
|
||||
config: { outputDir: testInfo.outputPath('output') },
|
||||
});
|
||||
|
||||
test.skip(mcpBrowser === 'webkit' && process.platform === 'linux', 'https://github.com/microsoft/playwright/blob/8e08fdb52c27bb75de9bf87627bf740fadab2122/tests/library/download.spec.ts#L436');
|
||||
|
||||
@@ -46,7 +46,6 @@ type TestFixtures = {
|
||||
server: TestServer;
|
||||
httpsServer: TestServer;
|
||||
mcpHeadless: boolean;
|
||||
localOutputPath: (filePath: string) => string;
|
||||
};
|
||||
|
||||
type WorkerFixtures = {
|
||||
@@ -129,13 +128,6 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
||||
|
||||
mcpMode: [undefined, { option: true }],
|
||||
|
||||
localOutputPath: async ({ mcpMode }, use, testInfo) => {
|
||||
await use(filePath => {
|
||||
test.skip(mcpMode === 'docker', 'Mounting files is not supported in docker mode');
|
||||
return testInfo.outputPath(filePath);
|
||||
});
|
||||
},
|
||||
|
||||
_workerServers: [async ({}, use, workerInfo) => {
|
||||
const port = 8907 + workerInfo.workerIndex * 4;
|
||||
const server = await TestServer.create(port);
|
||||
|
||||
@@ -104,8 +104,8 @@ test('isolated context', async ({ startClient, server }) => {
|
||||
expect(response2).toContainTextContent(`Storage: NO`);
|
||||
});
|
||||
|
||||
test('isolated context with storage state', async ({ startClient, server, localOutputPath }) => {
|
||||
const storageStatePath = localOutputPath('storage-state.json');
|
||||
test('isolated context with storage state', async ({ startClient, server }, testInfo) => {
|
||||
const storageStatePath = testInfo.outputPath('storage-state.json');
|
||||
await fs.promises.writeFile(storageStatePath, JSON.stringify({
|
||||
origins: [
|
||||
{
|
||||
|
||||
28
tests/library.spec.ts
Normal file
28
tests/library.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { test, expect } from './fixtures.js';
|
||||
import fs from 'node:fs/promises';
|
||||
import child_process from 'node:child_process';
|
||||
|
||||
test('library can be used from CommonJS', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-mcp/issues/456' } }, async ({}, testInfo) => {
|
||||
const file = testInfo.outputPath('main.cjs');
|
||||
await fs.writeFile(file, `
|
||||
import('@playwright/mcp')
|
||||
.then(playwrightMCP => playwrightMCP.createConnection())
|
||||
.then(() => console.log('OK'));
|
||||
`);
|
||||
expect(child_process.execSync(`node ${file}`, { encoding: 'utf-8' })).toContain('OK');
|
||||
});
|
||||
@@ -30,10 +30,9 @@ test('save as pdf unavailable', async ({ startClient, server }) => {
|
||||
})).toHaveTextContent(/Tool \"browser_pdf_save\" not found/);
|
||||
});
|
||||
|
||||
test('save as pdf', async ({ startClient, mcpBrowser, server, localOutputPath }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('save as pdf', async ({ startClient, mcpBrowser, server }, testInfo) => {
|
||||
const client = await startClient({
|
||||
config: { outputDir },
|
||||
config: { outputDir: testInfo.outputPath('output') },
|
||||
});
|
||||
|
||||
test.skip(!!mcpBrowser && !['chromium', 'chrome', 'msedge'].includes(mcpBrowser), 'Save as PDF is only supported in Chromium.');
|
||||
@@ -49,9 +48,9 @@ test('save as pdf', async ({ startClient, mcpBrowser, server, localOutputPath })
|
||||
expect(response).toHaveTextContent(/Save page as.*page-[^:]+.pdf/);
|
||||
});
|
||||
|
||||
test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser, server, localOutputPath }) => {
|
||||
test('save as pdf (filename: output.pdf)', async ({ startClient, mcpBrowser, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
test.skip(!!mcpBrowser && !['chromium', 'chrome', 'msedge'].includes(mcpBrowser), 'Save as PDF is only supported in Chromium.');
|
||||
const outputDir = localOutputPath('output');
|
||||
const client = await startClient({
|
||||
config: { outputDir },
|
||||
});
|
||||
|
||||
@@ -18,10 +18,9 @@ import fs from 'fs';
|
||||
|
||||
import { test, expect } from './fixtures.js';
|
||||
|
||||
test('browser_take_screenshot (viewport)', async ({ startClient, server, localOutputPath }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('browser_take_screenshot (viewport)', async ({ startClient, server }, testInfo) => {
|
||||
const client = await startClient({
|
||||
args: ['--output-dir', outputDir],
|
||||
config: { outputDir: testInfo.outputPath('output') },
|
||||
});
|
||||
expect(await client.callTool({
|
||||
name: 'browser_navigate',
|
||||
@@ -45,10 +44,9 @@ test('browser_take_screenshot (viewport)', async ({ startClient, server, localOu
|
||||
});
|
||||
});
|
||||
|
||||
test('browser_take_screenshot (element)', async ({ startClient, server, localOutputPath }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('browser_take_screenshot (element)', async ({ startClient, server }, testInfo) => {
|
||||
const client = await startClient({
|
||||
args: ['--output-dir', outputDir],
|
||||
config: { outputDir: testInfo.outputPath('output') },
|
||||
});
|
||||
expect(await client.callTool({
|
||||
name: 'browser_navigate',
|
||||
@@ -76,10 +74,10 @@ test('browser_take_screenshot (element)', async ({ startClient, server, localOut
|
||||
});
|
||||
});
|
||||
|
||||
test('--output-dir should work', async ({ startClient, localOutputPath, server }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('--output-dir should work', async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
const client = await startClient({
|
||||
args: ['--output-dir', outputDir],
|
||||
config: { outputDir },
|
||||
});
|
||||
expect(await client.callTool({
|
||||
name: 'browser_navigate',
|
||||
@@ -97,9 +95,9 @@ test('--output-dir should work', async ({ startClient, localOutputPath, server }
|
||||
});
|
||||
|
||||
for (const raw of [undefined, true]) {
|
||||
test(`browser_take_screenshot (raw: ${raw})`, async ({ startClient, localOutputPath, server }) => {
|
||||
test(`browser_take_screenshot (raw: ${raw})`, async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
const ext = raw ? 'png' : 'jpeg';
|
||||
const outputDir = localOutputPath('output');
|
||||
const client = await startClient({
|
||||
config: { outputDir },
|
||||
});
|
||||
@@ -138,8 +136,8 @@ for (const raw of [undefined, true]) {
|
||||
|
||||
}
|
||||
|
||||
test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient, localOutputPath, server }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
const client = await startClient({
|
||||
config: { outputDir },
|
||||
});
|
||||
@@ -174,11 +172,12 @@ test('browser_take_screenshot (filename: "output.jpeg")', async ({ startClient,
|
||||
expect(files[0]).toMatch(/^output\.jpeg$/);
|
||||
});
|
||||
|
||||
test('browser_take_screenshot (noImageResponses)', async ({ startClient, server, localOutputPath }) => {
|
||||
test('browser_take_screenshot (imageResponses=omit)', async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
const client = await startClient({
|
||||
config: {
|
||||
outputDir: localOutputPath('output'),
|
||||
noImageResponses: true,
|
||||
outputDir,
|
||||
imageResponses: 'omit',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -203,8 +202,9 @@ test('browser_take_screenshot (noImageResponses)', async ({ startClient, server,
|
||||
});
|
||||
});
|
||||
|
||||
test('browser_take_screenshot (cursor)', async ({ startClient, server, localOutputPath }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('browser_take_screenshot (cursor)', async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
|
||||
const client = await startClient({
|
||||
clientName: 'cursor:vscode',
|
||||
config: { outputDir },
|
||||
|
||||
@@ -65,8 +65,8 @@ test('streamable http transport', async ({ serverEndpoint }) => {
|
||||
expect(transport.sessionId, 'has session support').toBeDefined();
|
||||
});
|
||||
|
||||
test('sse transport via public API', async ({ server, localOutputPath }) => {
|
||||
const userDataDir = localOutputPath('user-data-dir');
|
||||
test('sse transport via public API', async ({ server }, testInfo) => {
|
||||
const userDataDir = testInfo.outputPath('user-data-dir');
|
||||
const sessions = new Map<string, SSEServerTransport>();
|
||||
const mcpServer = http.createServer(async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
|
||||
@@ -19,8 +19,9 @@ import path from 'path';
|
||||
|
||||
import { test, expect } from './fixtures.js';
|
||||
|
||||
test('check that trace is saved', async ({ startClient, server, localOutputPath }) => {
|
||||
const outputDir = localOutputPath('output');
|
||||
test('check that trace is saved', async ({ startClient, server }, testInfo) => {
|
||||
const outputDir = testInfo.outputPath('output');
|
||||
|
||||
const client = await startClient({
|
||||
args: ['--save-trace', `--output-dir=${outputDir}`],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user