diff --git a/README.md b/README.md index 5978467..18da004 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,25 @@ code-insiders --add-mcp '{"name":"playwright","command":"npx","args":["@playwrig After installation, the Playwright MCP server will be available for use with your GitHub Copilot agent in VS Code. +### CLI Options + +The Playwright MCP server supports the following command-line options: + +- `--browser `: Browser or chrome channel to use. Possible values: + - `chrome`, `firefox`, `webkit`, `msedge` + - Chrome channels: `chrome-beta`, `chrome-canary`, `chrome-dev` + - Edge channels: `msedge-beta`, `msedge-canary`, `msedge-dev` + - Default: `chrome` +- `--cdp-endpoint `: CDP endpoint to connect to +- `--executable-path `: Path to the browser executable +- `--headless`: Run browser in headless mode (headed by default) +- `--port `: Port to listen on for SSE transport +- `--user-data-dir `: Path to the user data directory +- `--vision`: Run server that uses screenshots (Aria snapshots are used by default) + ### User data directory -Playwright MCP will launch Chrome browser with the new profile, located at +Playwright MCP will launch the browser with the new profile, located at ``` - `%USERPROFILE%\AppData\Local\ms-playwright\mcp-chrome-profile` on Windows diff --git a/src/context.ts b/src/context.ts index 88c8fd1..7f07313 100644 --- a/src/context.ts +++ b/src/context.ts @@ -20,6 +20,7 @@ import path from 'path'; import * as playwright from 'playwright'; export type ContextOptions = { + browserName?: 'chromium' | 'firefox' | 'webkit'; userDataDir: string; launchOptions?: playwright.LaunchOptions; cdpEndpoint?: string; @@ -73,7 +74,7 @@ export class Context { } async install(): Promise { - const channel = this._options.launchOptions?.channel || 'chrome'; + const channel = this._options.launchOptions?.channel ?? this._options.browserName ?? 'chrome'; const cli = path.join(require.resolve('playwright/package.json'), '..', 'cli.js'); const child = fork(cli, ['install', channel], { stdio: 'pipe', @@ -125,9 +126,11 @@ export class Context { private async _createPage(): Promise<{ browser?: playwright.Browser, page: playwright.Page }> { if (this._options.remoteEndpoint) { const url = new URL(this._options.remoteEndpoint); + if (this._options.browserName) + url.searchParams.set('browser', this._options.browserName); if (this._options.launchOptions) url.searchParams.set('launch-options', JSON.stringify(this._options.launchOptions)); - const browser = await playwright.chromium.connect(String(url)); + const browser = await playwright[this._options.browserName ?? 'chromium'].connect(String(url)); const page = await browser.newPage(); return { browser, page }; } @@ -148,7 +151,8 @@ export class Context { private async _launchPersistentContext(): Promise { try { - return await playwright.chromium.launchPersistentContext(this._options.userDataDir, this._options.launchOptions); + const browserType = this._options.browserName ? playwright[this._options.browserName] : playwright.chromium; + return await browserType.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.`); diff --git a/src/index.ts b/src/index.ts index e88f298..a54a674 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,6 +65,7 @@ const resources: Resource[] = [ ]; type Options = { + browserName?: 'chromium' | 'firefox' | 'webkit'; userDataDir?: string; launchOptions?: LaunchOptions; cdpEndpoint?: string; @@ -80,6 +81,7 @@ export function createServer(options?: Options): Server { version: packageJSON.version, tools, resources, + browserName: options?.browserName, userDataDir: options?.userDataDir ?? '', launchOptions: options?.launchOptions, cdpEndpoint: options?.cdpEndpoint, diff --git a/src/program.ts b/src/program.ts index 71c8bf5..bdf9fea 100644 --- a/src/program.ts +++ b/src/program.ts @@ -35,21 +35,52 @@ const packageJSON = require('../package.json'); program .version('Version ' + packageJSON.version) .name(packageJSON.name) + .option('--browser ', 'Browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.') + .option('--cdp-endpoint ', 'CDP endpoint to connect to.') + .option('--executable-path ', 'Path to the browser executable.') .option('--headless', 'Run browser in headless mode, headed by default') + .option('--port ', 'Port to listen on for SSE transport.') .option('--user-data-dir ', 'Path to the user data directory') .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)') - .option('--port ', 'Port to listen on for SSE transport.') - .option('--cdp-endpoint ', 'CDP endpoint to connect to.') - .option('--channel ', 'Channel to use for browser, possible values: chrome, msedge, chromium. Default: chrome') - .option('--executable-path ', 'Path to the browser executable.') .action(async options => { + let browserName: 'chromium' | 'firefox' | 'webkit'; + let channel: string | undefined; + switch (options.browser) { + case 'chrome': + case 'chrome-beta': + case 'chrome-canary': + case 'chrome-dev': + case 'msedge': + case 'msedge-beta': + case 'msedge-canary': + case 'msedge-dev': + browserName = 'chromium'; + channel = options.browser; + break; + case 'chromium': + browserName = 'chromium'; + break; + case 'firefox': + browserName = 'firefox'; + break; + case 'webkit': + browserName = 'webkit'; + break; + default: + browserName = 'chromium'; + channel = 'chrome'; + } + const launchOptions: LaunchOptions = { headless: !!options.headless, - channel: options.channel ?? 'chrome', + channel, executablePath: options.executablePath, }; - const userDataDir = options.userDataDir ?? await createUserDataDir(); + + const userDataDir = options.userDataDir ?? await createUserDataDir(browserName); + const serverList = new ServerList(() => createServer({ + browserName, userDataDir, launchOptions, vision: !!options.vision, @@ -75,7 +106,7 @@ function setupExitWatchdog(serverList: ServerList) { program.parse(process.argv); -async function createUserDataDir() { +async function createUserDataDir(browserName: 'chromium' | 'firefox' | 'webkit') { let cacheDirectory: string; if (process.platform === 'linux') cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'); @@ -85,7 +116,7 @@ async function createUserDataDir() { cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); else throw new Error('Unsupported platform: ' + process.platform); - const result = path.join(cacheDirectory, 'ms-playwright', 'mcp-chrome-profile'); + const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${browserName}-profile`); await fs.promises.mkdir(result, { recursive: true }); return result; }