From 1594b6dcf60e72a1c82b77e0ad4f90f363bef812 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 15 May 2026 15:12:26 -0700 Subject: [PATCH] fix(mcp): report missing ffmpeg distinctly from missing browser The MCP launch error handler was rewriting any `Executable doesn't exist` error into `Browser "" is not installed`, but the registry uses the same prefix for ffmpeg too. When `recordVideo` was enabled and ffmpeg was missing, users were told to install a browser that was already there. Disambiguate the rewrite by the executable path so ffmpeg gets its own message pointing at `install-browser ffmpeg`. Fixes: https://github.com/microsoft/playwright/issues/40862 --- .../src/tools/mcp/browserFactory.ts | 21 ++++++----- tests/mcp/video.spec.ts | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index ed4dc7ffe5fba..2f33851a7c566 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -96,8 +96,7 @@ async function createIsolatedBrowser(config: FullConfig, clientInfo: ClientInfo) handleSIGINT: false, handleSIGTERM: false, }).catch(error => { - if (error.message.includes('Executable doesn\'t exist')) - throwBrowserIsNotInstalledError(config); + throwIfExecutableMissing(error, config); throw error; }); return browser; @@ -171,8 +170,7 @@ async function createPersistentBrowser(config: FullConfig, clientInfo: ClientInf const browser = browserContext.browser()!; return browser; } catch (error: any) { - if (error.message.includes('Executable doesn\'t exist')) - throwBrowserIsNotInstalledError(config); + throwIfExecutableMissing(error, config); if (error.message.includes('cannot open shared object file: No such file or directory')) { const browserName = launchOptions.channel ?? config.browser.browserName; throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`); @@ -236,10 +234,15 @@ export function isProfileLocked(userDataDir: string): boolean { } } -function throwBrowserIsNotInstalledError(config: FullConfig): never { - const channel = config.browser.launchOptions?.channel ?? config.browser.browserName; +function throwIfExecutableMissing(error: Error, config: FullConfig): void { + // The "Executable doesn't exist" prefix is shared by all managed binaries + // (browser, ffmpeg, winldd). Disambiguate by the path so the user is told + // which dependency to install. + if (!error.message.includes(`Executable doesn't exist`)) + return; + const target = error.message.includes('ffmpeg') ? 'ffmpeg' : (config.browser.launchOptions?.channel ?? config.browser.browserName); + const label = target === 'ffmpeg' ? 'FFmpeg' : `Browser "${target}"`; if (config.skillMode) - throw new Error(`Browser "${channel}" is not installed. Run \`playwright-cli install-browser ${channel}\` to install`); - else - throw new Error(`Browser "${channel}" is not installed. Run \`npx @playwright/mcp install-browser ${channel}\` to install`); + throw new Error(`${label} is not installed. Run \`playwright-cli install-browser ${target}\` to install`); + throw new Error(`${label} is not installed. Run \`npx @playwright/mcp install-browser ${target}\` to install`); } diff --git a/tests/mcp/video.spec.ts b/tests/mcp/video.spec.ts index 6481bdbb124db..79043bdb5f0e5 100644 --- a/tests/mcp/video.spec.ts +++ b/tests/mcp/video.spec.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import fs from 'fs'; + import { test, expect } from './fixtures'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -60,6 +62,39 @@ async function closeBrowser(client: Client) { }); } +test('reports missing ffmpeg, not missing browser, when recordVideo is enabled', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40862' }, +}, async ({ startClient, server, mcpBrowser }, testInfo) => { + test.skip(mcpBrowser !== 'chrome' && mcpBrowser !== 'msedge', 'Channel browsers use system-installed binaries; bundled browsers would also be missing under an empty PLAYWRIGHT_BROWSERS_PATH'); + + const emptyBrowsersPath = testInfo.outputPath('empty-browsers'); + await fs.promises.mkdir(emptyBrowsersPath, { recursive: true }); + + const { client } = await startClient({ + env: { PLAYWRIGHT_BROWSERS_PATH: emptyBrowsersPath }, + config: { + browser: { + contextOptions: { + recordVideo: { dir: testInfo.outputPath('videos') }, + }, + }, + }, + }); + + const response = await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + }); + expect.soft(response).toHaveResponse({ + isError: true, + error: expect.stringContaining('FFmpeg is not installed'), + }); + expect.soft(response).toHaveResponse({ + isError: true, + error: expect.not.stringContaining(`Browser "${mcpBrowser}" is not installed`), + }); +}); + async function produceFrames(client: Client) { expect(await client.callTool({ name: 'browser_evaluate',