From 3f72fe53ecc358508a7e8fa407cf5ab14615eb44 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 29 Apr 2025 19:51:00 -0700 Subject: [PATCH] chore: add support for device (#300) Fixes https://github.com/microsoft/playwright-mcp/issues/294 --- README.md | 17 ++++++++-------- src/config.ts | 7 ++++++- src/context.ts | 2 +- src/program.ts | 1 + tests/device.spec.ts | 43 +++++++++++++++++++++++++++++++++++++++++ utils/generate_links.js | 6 ++++++ 6 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 tests/device.spec.ts create mode 100644 utils/generate_links.js diff --git a/README.md b/README.md index 0408878..aab39c1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A Model Context Protocol (MCP) server that provides browser automation capabilit - General-purpose browser interaction for agents @@ -72,6 +72,7 @@ The Playwright MCP server supports the following command-line options: - `--cdp-endpoint `: CDP endpoint to connect to - `--executable-path `: Path to the browser executable - `--headless`: Run browser in headless mode (headed by default) +- `--device`: Emulate mobile device - `--user-data-dir `: Path to the user data directory - `--port `: Port to listen on for SSE transport - `--host `: Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces. @@ -100,27 +101,29 @@ The Playwright MCP server can be configured using a JSON configuration file. Her browser?: { // Browser type to use (chromium, firefox, or webkit) browserName?: 'chromium' | 'firefox' | 'webkit'; - + // Path to user data directory for browser profile persistence userDataDir?: string; - + // Browser launch options (see Playwright docs) + // @see https://playwright.dev/docs/api/class-browsertype#browser-type-launch launchOptions?: { channel?: string; // Browser channel (e.g. 'chrome') headless?: boolean; // Run in headless mode executablePath?: string; // Path to browser executable // ... other Playwright launch options }; - + // Browser context options + // @see https://playwright.dev/docs/api/class-browser#browser-new-context contextOptions?: { viewport?: { width: number, height: number }; // ... other Playwright context options }; - + // CDP endpoint for connecting to existing browser cdpEndpoint?: string; - + // Remote Playwright server endpoint remoteEndpoint?: string; }, @@ -166,8 +169,6 @@ npx @playwright/mcp@latest --config path/to/config.json ### Running on Linux -When running headless without DISPLAY, pass `--headless` command line argument. - When running headed browser on system w/o display or from worker processes of the IDEs, run the MCP server from environment with the DISPLAY and pass the `--port` flag to enable SSE transport. diff --git a/src/config.ts b/src/config.ts index 4f62287..2bee252 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,11 +18,12 @@ import fs from 'fs'; import net from 'net'; import os from 'os'; import path from 'path'; +import { devices } from 'playwright'; import { sanitizeForFilePath } from './tools/utils'; import type { Config, ToolCapability } from '../config'; -import type { LaunchOptions } from 'playwright'; +import type { BrowserContextOptions, LaunchOptions } from 'playwright'; export type CLIOptions = { browser?: string; @@ -30,6 +31,7 @@ export type CLIOptions = { cdpEndpoint?: string; executablePath?: string; headless?: boolean; + device?: string; userDataDir?: string; port?: number; host?: string; @@ -93,11 +95,14 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise { try { const browserType = browserConfig?.browserName ? playwright[browserConfig.browserName] : playwright.chromium; - return await browserType.launchPersistentContext(browserConfig?.userDataDir || '', browserConfig?.launchOptions); + return await browserType.launchPersistentContext(browserConfig?.userDataDir || '', { ...browserConfig?.launchOptions, ...browserConfig?.contextOptions }); } 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/program.ts b/src/program.ts index d871fe2..6a09ced 100644 --- a/src/program.ts +++ b/src/program.ts @@ -33,6 +33,7 @@ program .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('--device ', 'Device to emulate, for example: "iPhone 15"') .option('--user-data-dir ', 'Path to the user data directory') .option('--port ', 'Port to listen on for SSE transport.') .option('--host ', 'Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.') diff --git a/tests/device.spec.ts b/tests/device.spec.ts new file mode 100644 index 0000000..31860bb --- /dev/null +++ b/tests/device.spec.ts @@ -0,0 +1,43 @@ +/** + * 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'; + +test('browser_take_screenshot (viewport)', async ({ startClient, server }) => { + const client = await startClient({ + args: ['--device', 'iPhone 15'], + }); + + server.route('/', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(` + + + + + + `); + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + })).toContainTextContent(`393x659`); +}); diff --git a/utils/generate_links.js b/utils/generate_links.js new file mode 100644 index 0000000..a54fe9c --- /dev/null +++ b/utils/generate_links.js @@ -0,0 +1,6 @@ +const config = JSON.stringify({ name: 'playwright', command: 'npx', args: ["@playwright/mcp@latest"] }); +const urlForWebsites = `vscode:mcp/install?${encodeURIComponent(config)}`; +// Github markdown does not allow linking to `vscode:` directly, so you can use our redirect: +const urlForGithub = `https://insiders.vscode.dev/redirect?url=${encodeURIComponent(urlForWebsites)}`; + +console.log(urlForGithub); \ No newline at end of file