From 8723dced716020b178ab2ed5e512f42cde8e95cf Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 11 Aug 2026 09:47:42 -0700 Subject: [PATCH] fix(mcp): honor --user-data-dir in extension mode (#42190) --- .../src/tools/mcp/browserFactory.ts | 2 +- .../playwright-core/src/tools/mcp/cdpRelay.ts | 22 +++++++++---------- .../src/tools/mcp/extensionContextFactory.ts | 17 +++++++------- tests/extension/extension.spec.ts | 15 +++++++++++++ 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index eef9c2d903cb6..8cd9656dd5ce4 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -60,7 +60,7 @@ export async function createBrowserWithInfo(config: FullConfig, clientInfo: Clie ownership = 'own'; } else if (config.extension) { const { channel, executablePath } = resolveExtensionOptions(cliOptions); - browser = await createExtensionBrowser(channel, executablePath, clientInfo.clientName); + browser = await createExtensionBrowser(channel, executablePath, config.browser.userDataDir, clientInfo.clientName); ownership = 'attached'; } else { browser = await createPersistentBrowser(config, clientInfo); diff --git a/packages/playwright-core/src/tools/mcp/cdpRelay.ts b/packages/playwright-core/src/tools/mcp/cdpRelay.ts index e24ae1a6c42d8..3338c83d3ce61 100644 --- a/packages/playwright-core/src/tools/mcp/cdpRelay.ts +++ b/packages/playwright-core/src/tools/mcp/cdpRelay.ts @@ -34,7 +34,7 @@ import { ManualPromise } from '@isomorphic/manualPromise'; import { WSServer } from '@utils/wsServer'; import { registry } from '../../server/registry/index'; -import { findPlaywrightExtensionProfile, playwrightExtensionId } from '../utils/extension'; +import { playwrightExtensionId } from '../utils/extension'; import { logUnhandledError } from './log'; import { ExtensionProtocolV2 } from './cdpRelayV2'; import * as protocol from './protocol'; @@ -61,7 +61,8 @@ export class CDPRelayServer { private _wsHost!: string; private _browserChannel: string; private _executablePath?: string; - private _userDataDir?: string; + private _customUserDataDir?: string; + private _profileDirectory?: string; private _cdpPath: string; private _extensionPath: string; private _cdpConnection: WebSocket | null = null; @@ -70,10 +71,11 @@ export class CDPRelayServer { private _handler: ExtensionProtocolV2; private _extensionConnectionPromise = new ManualPromise(); - constructor(browserChannel: string, executablePath?: string, userDataDir?: string) { + constructor(browserChannel: string, executablePath?: string, customUserDataDir?: string, profileDirectory?: string) { this._browserChannel = browserChannel; this._executablePath = executablePath; - this._userDataDir = userDataDir; + this._customUserDataDir = customUserDataDir; + this._profileDirectory = profileDirectory; this._protocolVersion = parseInt(process.env.PLAYWRIGHT_EXTENSION_PROTOCOL ?? protocol.VERSION.toString(), 10); const sendCommand = (method: string, params: any): Promise => { @@ -156,13 +158,11 @@ export class CDPRelayServer { } const args: string[] = []; - const testUserDataDir = process.env.PWTEST_EXTENSION_USER_DATA_DIR; - if (testUserDataDir) - args.push(`--user-data-dir=${testUserDataDir}`); - const userDataDir = testUserDataDir ?? this._userDataDir; - const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined; - if (profileDirectory) - args.push(`--profile-directory=${profileDirectory}`); + // The default profile dir is not passed explicitly, the browser resolves it on its own. + if (this._customUserDataDir) + args.push(`--user-data-dir=${this._customUserDataDir}`); + if (this._profileDirectory) + args.push(`--profile-directory=${this._profileDirectory}`); if (os.platform() === 'linux' && channel === 'chromium') args.push('--no-sandbox'); args.push(href); diff --git a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts index ae34a5a8497f8..fa2b147a05ff7 100644 --- a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts +++ b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts @@ -17,23 +17,22 @@ import debug from 'debug'; import { defaultUserDataDirForChannel } from '@utils/chromiumChannels'; import { playwright } from '../../inprocess'; -import { isPlaywrightExtensionInstalled, playwrightExtensionInstallUrl } from '../utils/extension'; +import { findPlaywrightExtensionProfile, playwrightExtensionInstallUrl } from '../utils/extension'; import { CDPRelayServer } from './cdpRelay'; import type * as playwrightTypes from '../../..'; const debugLogger = debug('pw:mcp:relay'); -export async function createExtensionBrowser(channel: string, executablePath: string | undefined, clientName: string): Promise { +export async function createExtensionBrowser(channel: string, executablePath: string | undefined, customUserDataDir: string | undefined, clientName: string): Promise { + customUserDataDir ??= process.env.PWTEST_EXTENSION_USER_DATA_DIR; // Custom executablePath may target a browser in a different filesystem (e.g. Windows chrome.exe from WSL2), so the local profile path is not meaningful. - let userDataDir: string | undefined; - if (!executablePath) { - userDataDir = process.env.PWTEST_EXTENSION_USER_DATA_DIR ?? defaultUserDataDirForChannel(channel); - if (userDataDir && !await isPlaywrightExtensionInstalled(userDataDir)) - throw new Error(`Playwright Extension not found in "${userDataDir}". Install it from ${playwrightExtensionInstallUrl}, or set the PLAYWRIGHT_MCP_EXECUTABLE_PATH environment variable to use a browser at a custom location.`); - } + const userDataDir = customUserDataDir ?? (executablePath ? undefined : defaultUserDataDirForChannel(channel)); + const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined; + if (!executablePath && userDataDir && !profileDirectory) + throw new Error(`Playwright Extension not found in "${userDataDir}". Install it from ${playwrightExtensionInstallUrl}, or set the PLAYWRIGHT_MCP_EXECUTABLE_PATH environment variable to use a browser at a custom location.`); - const relay = new CDPRelayServer(channel, executablePath, userDataDir); + const relay = new CDPRelayServer(channel, executablePath, customUserDataDir, profileDirectory); await relay.start(); debugLogger(`CDP relay server started, extension endpoint: ${relay.extensionEndpoint()}.`); diff --git a/tests/extension/extension.spec.ts b/tests/extension/extension.spec.ts index ba6a64a1f6d17..ded650508afc1 100644 --- a/tests/extension/extension.spec.ts +++ b/tests/extension/extension.spec.ts @@ -324,6 +324,21 @@ test(`fails when extension is missing in custom userDataDir`, async ({ startClie }); }); +test(`navigate with extension via --user-data-dir`, { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42163' }, +}, async ({ browserWithExtension, startClient, server }) => { + const browserContext = await browserWithExtension.launch(); + + const { client } = await startClient({ + args: [`--extension`, `--user-data-dir=${browserWithExtension.userDataDir}`], + }); + + const response = await connectAndNavigate(browserContext, client, server.HELLO_WORLD); + expect(response).toHaveResponse({ + snapshot: expect.stringContaining(`Hello, world!`), + }); +}); + test(`--browser selects channel-specific userDataDir`, { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-mcp/issues/1589' }, }, async ({ startClient, server }) => {