diff --git a/src/_internal/browser-eval-manager.ts b/src/_internal/browser-eval-manager.ts index dd6dea6..9b86037 100644 --- a/src/_internal/browser-eval-manager.ts +++ b/src/_internal/browser-eval-manager.ts @@ -50,6 +50,7 @@ export async function ensureBrowserEvalMCP(): Promise { export async function startBrowserEvalMCP(options?: { browser?: "chrome" | "firefox" | "webkit" | "msedge" headless?: boolean + executablePath?: string }): Promise { // Ensure playwright-mcp is installed await ensureBrowserEvalMCP() @@ -75,6 +76,11 @@ export async function startBrowserEvalMCP(options?: { args.push("--headless") } + // Custom browser executable path (useful for Homebrew-installed Playwright browsers) + if (options?.executablePath) { + args.push("--executable-path", options.executablePath) + } + // Always enable verbose logging via environment variables const env = { ...process.env, diff --git a/src/tools/browser-eval.ts b/src/tools/browser-eval.ts index 2baad56..74cfff7 100644 --- a/src/tools/browser-eval.ts +++ b/src/tools/browser-eval.ts @@ -33,6 +33,13 @@ export const inputSchema = { .optional() .describe("Run browser in headless mode (default: true). Only used with 'start' action."), + executablePath: z + .string() + .optional() + .describe( + "Custom browser executable path. Use this when Playwright browsers are installed in non-standard locations (e.g., Homebrew installations at ~/Library/Caches/ms-playwright/). Only used with 'start' action." + ), + url: z.string().optional().describe("URL to navigate to (required for 'navigate' action)"), element: z.string().optional().describe("Element to interact with (CSS selector or text)"), @@ -84,6 +91,11 @@ export const metadata = { description: `Automate and test web applications using Playwright browser automation. This tool connects to playwright-mcp server and provides access to all Playwright capabilities. +CUSTOM BROWSER PATH: +Use the 'executablePath' parameter when starting the browser to specify a custom browser binary location. +This is useful for Homebrew-installed Playwright browsers at ~/Library/Caches/ms-playwright/. +Example: executablePath="/path/to/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" + CRITICAL FOR PAGE VERIFICATION: When verifying pages in Next.js projects (especially during upgrades or testing), you MUST use browser automation to load pages in a real browser instead of curl or simple HTTP requests. This is because: @@ -131,6 +143,7 @@ type BrowserEvalArgs = { | "list_tools" browser?: "chrome" | "firefox" | "webkit" | "msedge" headless?: boolean | string + executablePath?: string url?: string element?: string ref?: string @@ -155,6 +168,7 @@ export async function handler(args: BrowserEvalArgs): Promise { const connection = await startBrowserEvalMCP({ browser: args.browser || "chrome", headless: args.headless !== false, + executablePath: args.executablePath, }) return JSON.stringify({ success: true, diff --git a/test/unit/browser-eval-screenshot.test.ts b/test/unit/browser-eval-screenshot.test.ts index 80d7235..d68c060 100644 --- a/test/unit/browser-eval-screenshot.test.ts +++ b/test/unit/browser-eval-screenshot.test.ts @@ -70,4 +70,42 @@ describe("browser-eval playwright-mcp screenshot tool", () => { ) expect(imageContent).toBeUndefined() }) + + it("should pass --executable-path flag to playwright-mcp when provided", async () => { + const execPath = "/path/to/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" + await startBrowserEvalMCP({ executablePath: execPath }) + + expect(connectToMCPServer).toHaveBeenCalledWith( + "npx", + expect.arrayContaining(["--executable-path", execPath]), + expect.any(Object) + ) + }) + + it("should not include --executable-path flag when not provided", async () => { + await startBrowserEvalMCP() + + const callArgs = vi.mocked(connectToMCPServer).mock.calls[0] + const args = callArgs[1] as string[] + expect(args).not.toContain("--executable-path") + }) + + it("should combine executablePath with browser and headless options", async () => { + const execPath = "/custom/path/to/browser" + await startBrowserEvalMCP({ + browser: "firefox", + headless: true, + executablePath: execPath, + }) + + expect(connectToMCPServer).toHaveBeenCalledWith( + "npx", + expect.arrayContaining([ + "--browser", "firefox", + "--headless", + "--executable-path", execPath, + ]), + expect.any(Object) + ) + }) })