Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/_internal/browser-eval-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export async function ensureBrowserEvalMCP(): Promise<void> {
export async function startBrowserEvalMCP(options?: {
browser?: "chrome" | "firefox" | "webkit" | "msedge"
headless?: boolean
executablePath?: string
}): Promise<MCPConnection> {
// Ensure playwright-mcp is installed
await ensureBrowserEvalMCP()
Expand All @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions src/tools/browser-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -131,6 +143,7 @@ type BrowserEvalArgs = {
| "list_tools"
browser?: "chrome" | "firefox" | "webkit" | "msedge"
headless?: boolean | string
executablePath?: string
url?: string
element?: string
ref?: string
Expand All @@ -155,6 +168,7 @@ export async function handler(args: BrowserEvalArgs): Promise<string> {
const connection = await startBrowserEvalMCP({
browser: args.browser || "chrome",
headless: args.headless !== false,
executablePath: args.executablePath,
})
return JSON.stringify({
success: true,
Expand Down
38 changes: 38 additions & 0 deletions test/unit/browser-eval-screenshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
})
})