mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-27 00:52:27 +08:00
chore: support channel and executable path params (#90)
Fixes https://github.com/microsoft/playwright-mcp/issues/89
This commit is contained in:
parent
d316441142
commit
9042c03faa
@ -14,6 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import * as playwright from 'playwright';
|
import * as playwright from 'playwright';
|
||||||
|
|
||||||
export type ContextOptions = {
|
export type ContextOptions = {
|
||||||
@ -69,6 +72,25 @@ export class Context {
|
|||||||
this._console.length = 0;
|
this._console.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async install(): Promise<string> {
|
||||||
|
const channel = this._options.launchOptions?.channel || 'chrome';
|
||||||
|
const cli = path.join(require.resolve('playwright/package.json'), '..', 'cli.js');
|
||||||
|
const child = fork(cli, ['install', channel], {
|
||||||
|
stdio: 'pipe',
|
||||||
|
});
|
||||||
|
const output: string[] = [];
|
||||||
|
child.stdout?.on('data', data => output.push(data.toString()));
|
||||||
|
child.stderr?.on('data', data => output.push(data.toString()));
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child.on('close', code => {
|
||||||
|
if (code === 0)
|
||||||
|
resolve(channel);
|
||||||
|
else
|
||||||
|
reject(new Error(`Failed to install browser: ${output.join('')}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
existingPage(): playwright.Page {
|
existingPage(): playwright.Page {
|
||||||
if (!this._page)
|
if (!this._page)
|
||||||
throw new Error('Navigate to a location to create a page');
|
throw new Error('Navigate to a location to create a page');
|
||||||
@ -119,11 +141,21 @@ export class Context {
|
|||||||
return { browser, page };
|
return { browser, page };
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = await playwright.chromium.launchPersistentContext(this._options.userDataDir, this._options.launchOptions);
|
const context = await this._launchPersistentContext();
|
||||||
const [page] = context.pages();
|
const [page] = context.pages();
|
||||||
return { page };
|
return { page };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _launchPersistentContext(): Promise<playwright.BrowserContext> {
|
||||||
|
try {
|
||||||
|
return await playwright.chromium.launchPersistentContext(this._options.userDataDir, this._options.launchOptions);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.message.includes('Executable doesn\'t exist'))
|
||||||
|
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async allFramesSnapshot() {
|
async allFramesSnapshot() {
|
||||||
const page = this.existingPage();
|
const page = this.existingPage();
|
||||||
const visibleFrames = await page.locator('iframe').filter({ visible: true }).all();
|
const visibleFrames = await page.locator('iframe').filter({ visible: true }).all();
|
||||||
|
@ -30,6 +30,7 @@ const commonTools: Tool[] = [
|
|||||||
common.wait,
|
common.wait,
|
||||||
common.pdf,
|
common.pdf,
|
||||||
common.close,
|
common.close,
|
||||||
|
common.install,
|
||||||
];
|
];
|
||||||
|
|
||||||
const snapshotTools: Tool[] = [
|
const snapshotTools: Tool[] = [
|
||||||
|
@ -40,10 +40,13 @@ program
|
|||||||
.option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
|
.option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
|
||||||
.option('--port <port>', 'Port to listen on for SSE transport.')
|
.option('--port <port>', 'Port to listen on for SSE transport.')
|
||||||
.option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
|
.option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
|
||||||
|
.option('--channel <channel>', 'Channel to use for browser, possible values: chrome, msedge, chromium. Default: chrome')
|
||||||
|
.option('--executable-path <path>', 'Path to the browser executable.')
|
||||||
.action(async options => {
|
.action(async options => {
|
||||||
const launchOptions: LaunchOptions = {
|
const launchOptions: LaunchOptions = {
|
||||||
headless: !!options.headless,
|
headless: !!options.headless,
|
||||||
channel: 'chrome',
|
channel: options.channel ?? 'chrome',
|
||||||
|
executablePath: options.executablePath,
|
||||||
};
|
};
|
||||||
const userDataDir = options.userDataDir ?? await createUserDataDir();
|
const userDataDir = options.userDataDir ?? await createUserDataDir();
|
||||||
const serverList = new ServerList(() => createServer({
|
const serverList = new ServerList(() => createServer({
|
||||||
|
@ -174,3 +174,20 @@ export const chooseFile: ToolFactory = snapshot => ({
|
|||||||
}, snapshot);
|
}, snapshot);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const install: Tool = {
|
||||||
|
schema: {
|
||||||
|
name: 'browser_install',
|
||||||
|
description: 'Install the browser specified in the config. Call this if you get an error about the browser not being installed.',
|
||||||
|
inputSchema: zodToJsonSchema(z.object({})),
|
||||||
|
},
|
||||||
|
handle: async context => {
|
||||||
|
const channel = await context.install();
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: 'text',
|
||||||
|
text: `Browser ${channel} installed`,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -36,6 +36,7 @@ test('test tool list', async ({ client, visionClient }) => {
|
|||||||
'browser_wait',
|
'browser_wait',
|
||||||
'browser_save_as_pdf',
|
'browser_save_as_pdf',
|
||||||
'browser_close',
|
'browser_close',
|
||||||
|
'browser_install',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { tools: visionTools } = await visionClient.listTools();
|
const { tools: visionTools } = await visionClient.listTools();
|
||||||
@ -53,6 +54,7 @@ test('test tool list', async ({ client, visionClient }) => {
|
|||||||
'browser_wait',
|
'browser_wait',
|
||||||
'browser_save_as_pdf',
|
'browser_save_as_pdf',
|
||||||
'browser_close',
|
'browser_close',
|
||||||
|
'browser_install',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -353,3 +355,14 @@ test('save as pdf', async ({ client }) => {
|
|||||||
});
|
});
|
||||||
expect(response).toHaveTextContent(/^Saved as.*page-[^:]+.pdf$/);
|
expect(response).toHaveTextContent(/^Saved as.*page-[^:]+.pdf$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('executable path', async ({ startClient }) => {
|
||||||
|
const client = await startClient({ args: [`--executable-path=bogus`] });
|
||||||
|
const response = await client.callTool({
|
||||||
|
name: 'browser_navigate',
|
||||||
|
arguments: {
|
||||||
|
url: 'data:text/html,<html><title>Title</title><body>Hello, world!</body></html>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response).toContainTextContent(`executable doesn't exist`);
|
||||||
|
});
|
||||||
|
@ -71,6 +71,7 @@ export const test = baseTest.extend<Fixtures>({
|
|||||||
cdpEndpoint: async ({ }, use, testInfo) => {
|
cdpEndpoint: async ({ }, use, testInfo) => {
|
||||||
const port = 3200 + (+process.env.TEST_PARALLEL_INDEX!);
|
const port = 3200 + (+process.env.TEST_PARALLEL_INDEX!);
|
||||||
const browser = await chromium.launchPersistentContext(testInfo.outputPath('user-data-dir'), {
|
const browser = await chromium.launchPersistentContext(testInfo.outputPath('user-data-dir'), {
|
||||||
|
channel: 'chrome',
|
||||||
args: [`--remote-debugging-port=${port}`],
|
args: [`--remote-debugging-port=${port}`],
|
||||||
});
|
});
|
||||||
await use(`http://localhost:${port}`);
|
await use(`http://localhost:${port}`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user