From c7c2b45568908a039107f3935ab2f059dd7a36e3 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 2 Sep 2026 14:31:06 -0700 Subject: [PATCH 1/2] feat(mcp): select the Chrome profile via --user-data-dir in extension mode When the extension is installed in several profiles, the connect page opens in the last used one, which is not necessarily the profile the token belongs to. Accept a profile path ("Profile Path" at chrome://version) in --user-data-dir and launch that profile explicitly. References: https://github.com/microsoft/playwright-mcp/issues/1732 --- packages/extension/README.md | 21 +++++++ .../src/tools/mcp/extensionContextFactory.ts | 15 +++-- .../src/tools/utils/extension.ts | 18 +++++- tests/extension/extension.spec.ts | 57 +++++++++++++++++++ 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/packages/extension/README.md b/packages/extension/README.md index 521dd1be5d514..055d4bd2b5da8 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -71,4 +71,25 @@ By default, you'll need to approve each connection when the MCP server tries to This token is unique to your browser profile and provides secure authentication between the MCP server and the extension. Once configured, you won't need to manually approve connections each time. +### Selecting a Chrome Profile + +If the extension is installed in several Chrome profiles, the connection is made to the one you used last. To always connect to a specific profile, pass its path, shown as "Profile Path" at `chrome://version`, via `--user-data-dir`: + +```json +{ + "mcpServers": { + "playwright-extension": { + "command": "npx", + "args": [ + "@playwright/mcp@latest", + "--extension", + "--user-data-dir=/Users/me/Library/Application Support/Google/Chrome/Profile 2" + ] + } + } +} +``` + +The authentication token is specific to the profile, so use the one displayed in that profile. + diff --git a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts index fa2b147a05ff7..a7127229dda47 100644 --- a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts +++ b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts @@ -14,10 +14,12 @@ * limitations under the License. */ +import path from 'path'; + import debug from 'debug'; import { defaultUserDataDirForChannel } from '@utils/chromiumChannels'; import { playwright } from '../../inprocess'; -import { findPlaywrightExtensionProfile, playwrightExtensionInstallUrl } from '../utils/extension'; +import { findPlaywrightExtensionProfile, isExtensionInstalledInProfile, playwrightExtensionInstallUrl, splitProfilePath } from '../utils/extension'; import { CDPRelayServer } from './cdpRelay'; import type * as playwrightTypes from '../../..'; @@ -26,13 +28,14 @@ const debugLogger = debug('pw:mcp:relay'); export async function createExtensionBrowser(channel: string, executablePath: string | undefined, customUserDataDir: string | undefined, clientName: string): Promise { customUserDataDir ??= process.env.PWTEST_EXTENSION_USER_DATA_DIR; + const custom = customUserDataDir ? await splitProfilePath(customUserDataDir) : undefined; // 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. - 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 userDataDir = custom?.userDataDir ?? (executablePath ? undefined : defaultUserDataDirForChannel(channel)); + const profileDirectory = custom?.profileDirectory ?? (userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined); + if (userDataDir && !executablePath && (!profileDirectory || !await isExtensionInstalledInProfile(path.join(userDataDir, profileDirectory)))) + throw new Error(`Playwright Extension not found in "${customUserDataDir ?? 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, customUserDataDir, profileDirectory); + const relay = new CDPRelayServer(channel, executablePath, custom?.userDataDir, profileDirectory); await relay.start(); debugLogger(`CDP relay server started, extension endpoint: ${relay.extensionEndpoint()}.`); diff --git a/packages/playwright-core/src/tools/utils/extension.ts b/packages/playwright-core/src/tools/utils/extension.ts index 9e2ed64d6a7e9..c02e57532d039 100644 --- a/packages/playwright-core/src/tools/utils/extension.ts +++ b/packages/playwright-core/src/tools/utils/extension.ts @@ -22,6 +22,20 @@ export const playwrightExtensionId = 'mmlmfjhmonkocbjadbfplnigmagldckm'; export const playwrightExtensionInstallUrl = `https://chromewebstore.google.com/detail/playwright-extension/${playwrightExtensionId}`; +// A path to a profile inside the user data dir, as shown at chrome://version, selects that profile explicitly. +// The parent must be a user data dir, so that a user data dir merely named like a profile is left alone. +export async function splitProfilePath(dir: string): Promise<{ userDataDir: string, profileDirectory?: string }> { + const name = path.basename(dir); + const userDataDir = path.dirname(dir); + if (isProfileDirectoryName(name) && await pathExists(path.join(userDataDir, 'Local State'))) + return { userDataDir, profileDirectory: name }; + return { userDataDir: dir }; +} + +function isProfileDirectoryName(name: string): boolean { + return name === 'Default' || /^Profile \d+$/.test(name); +} + export async function findPlaywrightExtensionProfile(userDataDir: string): Promise { const profiles = await listProfileDirectories(userDataDir); const lastUsed = await readLastUsedProfile(userDataDir); @@ -42,7 +56,7 @@ async function listProfileDirectories(userDataDir: string): Promise { } catch { return []; } - const profiles = entries.filter(entry => entry === 'Default' || /^Profile \d+$/.test(entry)); + const profiles = entries.filter(isProfileDirectoryName); profiles.sort((a, b) => profileRank(a) - profileRank(b)); return profiles; } @@ -65,7 +79,7 @@ export async function isPlaywrightExtensionInstalled(userDataDir: string): Promi return await findPlaywrightExtensionProfile(userDataDir) !== undefined; } -async function isExtensionInstalledInProfile(profileDir: string): Promise { +export async function isExtensionInstalledInProfile(profileDir: string): Promise { // Web store installs unpack into /Extensions/; `--load-extension` only // leaves a settings record in the preferences. if (await pathExists(path.join(profileDir, 'Extensions', playwrightExtensionId))) diff --git a/tests/extension/extension.spec.ts b/tests/extension/extension.spec.ts index d0145b7a1d1fa..dee8d826013e8 100644 --- a/tests/extension/extension.spec.ts +++ b/tests/extension/extension.spec.ts @@ -343,6 +343,50 @@ test(`ignores orphaned preferences entries of an uninstalled extension`, { }).toPass(); }); +test(`--user-data-dir pointing at a profile selects it`, { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-mcp/issues/1732' }, +}, async ({ startClient, server }, testInfo) => { + // Both profiles have the extension and Default was used last, so only the explicit path selects Profile 1. + const userDataDir = testInfo.outputPath('multi-profile'); + await fs.mkdir(path.join(userDataDir, 'Default', 'Extensions', extensionId), { recursive: true }); + await fs.mkdir(path.join(userDataDir, 'Profile 1', 'Extensions', extensionId), { recursive: true }); + await fs.writeFile(path.join(userDataDir, 'Local State'), JSON.stringify({ + profile: { last_used: 'Default' }, + })); + + const executablePath = testInfo.outputPath('echo.sh'); + await fs.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); + + const { client } = await startClient({ + args: [`--extension`, `--executable-path=${executablePath}`, `--user-data-dir=${path.join(userDataDir, 'Profile 1')}`], + }); + + client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } }).catch(() => {}); + await expect(async () => { + const output = await fs.readFile(testInfo.outputPath('output.txt'), 'utf8'); + expect(output).toContain(`--user-data-dir=${userDataDir} --profile-directory=Profile 1`); + }).toPass(); +}); + +test(`--user-data-dir named like a profile is not split`, async ({ startClient, server }, testInfo) => { + // No "Local State" next to it, so this is a user data dir, not a profile inside one. + const userDataDir = testInfo.outputPath('Profile 1'); + await fs.mkdir(path.join(userDataDir, 'Default', 'Extensions', extensionId), { recursive: true }); + + const executablePath = testInfo.outputPath('echo.sh'); + await fs.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); + + const { client } = await startClient({ + args: [`--extension`, `--executable-path=${executablePath}`, `--user-data-dir=${userDataDir}`], + }); + + client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } }).catch(() => {}); + await expect(async () => { + const output = await fs.readFile(testInfo.outputPath('output.txt'), 'utf8'); + expect(output).toContain(`--user-data-dir=${userDataDir} --profile-directory=Default`); + }).toPass(); +}); + test(`fails when extension is missing in custom userDataDir`, async ({ startClient, server }) => { const userDataDir = test.info().outputPath('empty-profile'); @@ -375,6 +419,19 @@ test(`navigate with extension via --user-data-dir`, { }); }); +test(`navigate with extension via --user-data-dir pointing at the profile`, async ({ browserWithExtension, startClient, server }) => { + const browserContext = await browserWithExtension.launch(); + + const { client } = await startClient({ + args: [`--extension`, `--user-data-dir=${path.join(browserWithExtension.userDataDir, 'Default')}`], + }); + + 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 }) => { From 1d0bef5f2594d4aa7183245f2e53d55e1e41ac27 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 2 Sep 2026 15:14:50 -0700 Subject: [PATCH 2/2] feat(mcp): replace profile path detection with --profile-dir-name A dedicated option (or PLAYWRIGHT_MCP_PROFILE_DIR_NAME) names the profile directory in the user data dir, instead of guessing from --user-data-dir. --- packages/extension/README.md | 7 ++-- .../src/tools/mcp/browserFactory.ts | 4 +-- .../playwright-core/src/tools/mcp/config.ts | 6 ++-- .../src/tools/mcp/extensionContextFactory.ts | 13 ++++---- .../playwright-core/src/tools/mcp/program.ts | 1 + .../src/tools/utils/extension.ts | 16 +-------- tests/extension/extension.spec.ts | 33 +++++-------------- 7 files changed, 27 insertions(+), 53 deletions(-) diff --git a/packages/extension/README.md b/packages/extension/README.md index 055d4bd2b5da8..9fece1a317427 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -73,7 +73,7 @@ This token is unique to your browser profile and provides secure authentication ### Selecting a Chrome Profile -If the extension is installed in several Chrome profiles, the connection is made to the one you used last. To always connect to a specific profile, pass its path, shown as "Profile Path" at `chrome://version`, via `--user-data-dir`: +If the extension is installed in several Chrome profiles, the connection is made to the one you used last. To always connect to a specific profile, pass the name of its directory, the last component of "Profile Path" at `chrome://version`, via `--profile-dir-name`: ```json { @@ -83,13 +83,14 @@ If the extension is installed in several Chrome profiles, the connection is made "args": [ "@playwright/mcp@latest", "--extension", - "--user-data-dir=/Users/me/Library/Application Support/Google/Chrome/Profile 2" + "--profile-dir-name", + "Profile 2" ] } } } ``` -The authentication token is specific to the profile, so use the one displayed in that profile. +The `PLAYWRIGHT_MCP_PROFILE_DIR_NAME` environment variable can be used instead of the option. The authentication token is specific to the profile, so use the one displayed in that profile. diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index 5ca1c20b7ae5b..113370799d96f 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -59,8 +59,8 @@ export async function createBrowserWithInfo(config: FullConfig, clientInfo: Clie canBind = true; ownership = 'own'; } else if (config.extension) { - const { channel, executablePath } = resolveExtensionOptions(cliOptions); - browser = await createExtensionBrowser(channel, executablePath, config.browser.userDataDir, clientInfo.clientName); + const { channel, executablePath, profileDirName } = resolveExtensionOptions(cliOptions); + browser = await createExtensionBrowser(channel, executablePath, config.browser.userDataDir, profileDirName, clientInfo.clientName); ownership = 'attached'; } else { browser = await createPersistentBrowser(config, clientInfo); diff --git a/packages/playwright-core/src/tools/mcp/config.ts b/packages/playwright-core/src/tools/mcp/config.ts index 80669dfbc5967..eed9066e37db9 100644 --- a/packages/playwright-core/src/tools/mcp/config.ts +++ b/packages/playwright-core/src/tools/mcp/config.ts @@ -63,6 +63,7 @@ export type CLIOptions = { outputDir?: string; outputMaxSize?: number; port?: number; + profileDirName?: string; proxyBypass?: string; proxyServer?: string; remoteHeader?: Record; @@ -208,11 +209,12 @@ export async function resolveCLIConfigForCLI(daemonProfilesDir: string, sessionN return { ...result, browser, configFile, skillMode: true }; } -export function resolveExtensionOptions(cliOptions: CLIOptions): { channel: string, executablePath?: string } { +export function resolveExtensionOptions(cliOptions: CLIOptions): { channel: string, executablePath?: string, profileDirName?: string } { const browser = cliOptions.browser ?? envToString(process.env.PLAYWRIGHT_MCP_BROWSER); const { channel } = resolveBrowserParam(browser); const executablePath = cliOptions.executablePath ?? envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH); - return { channel: channel ?? 'chrome', executablePath }; + const profileDirName = cliOptions.profileDirName ?? envToString(process.env.PLAYWRIGHT_MCP_PROFILE_DIR_NAME); + return { channel: channel ?? 'chrome', executablePath, profileDirName }; } async function validateBrowserConfig(browser: MergedConfig['browser']): Promise { diff --git a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts index a7127229dda47..ec08c8d981ca7 100644 --- a/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts +++ b/packages/playwright-core/src/tools/mcp/extensionContextFactory.ts @@ -19,23 +19,22 @@ import path from 'path'; import debug from 'debug'; import { defaultUserDataDirForChannel } from '@utils/chromiumChannels'; import { playwright } from '../../inprocess'; -import { findPlaywrightExtensionProfile, isExtensionInstalledInProfile, playwrightExtensionInstallUrl, splitProfilePath } from '../utils/extension'; +import { findPlaywrightExtensionProfile, isExtensionInstalledInProfile, 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, customUserDataDir: string | undefined, clientName: string): Promise { +export async function createExtensionBrowser(channel: string, executablePath: string | undefined, customUserDataDir: string | undefined, profileDirName: string | undefined, clientName: string): Promise { customUserDataDir ??= process.env.PWTEST_EXTENSION_USER_DATA_DIR; - const custom = customUserDataDir ? await splitProfilePath(customUserDataDir) : undefined; // 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. - const userDataDir = custom?.userDataDir ?? (executablePath ? undefined : defaultUserDataDirForChannel(channel)); - const profileDirectory = custom?.profileDirectory ?? (userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined); + const userDataDir = customUserDataDir ?? (executablePath ? undefined : defaultUserDataDirForChannel(channel)); + const profileDirectory = profileDirName ?? (userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined); if (userDataDir && !executablePath && (!profileDirectory || !await isExtensionInstalledInProfile(path.join(userDataDir, profileDirectory)))) - throw new Error(`Playwright Extension not found in "${customUserDataDir ?? userDataDir}". Install it from ${playwrightExtensionInstallUrl}, or set the PLAYWRIGHT_MCP_EXECUTABLE_PATH environment variable to use a browser at a custom location.`); + throw new Error(`Playwright Extension not found in "${profileDirectory ? path.join(userDataDir, profileDirectory) : 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, custom?.userDataDir, profileDirectory); + const relay = new CDPRelayServer(channel, executablePath, customUserDataDir, profileDirectory); await relay.start(); debugLogger(`CDP relay server started, extension endpoint: ${relay.extensionEndpoint()}.`); diff --git a/packages/playwright-core/src/tools/mcp/program.ts b/packages/playwright-core/src/tools/mcp/program.ts index 12fdf17f6ee4a..9203c9abdc372 100644 --- a/packages/playwright-core/src/tools/mcp/program.ts +++ b/packages/playwright-core/src/tools/mcp/program.ts @@ -62,6 +62,7 @@ export function decorateMCPCommand(command: Command) { .option('--output-dir ', 'path to the directory for output files.') .option('--output-max-size ', 'Threshold for evicting old output files, in bytes.', numberParser) .option('--port ', 'port to listen on for SSE transport.') + .option('--profile-dir-name ', 'name of the profile directory in the user data dir to connect to with --extension, for example "Profile 1". Defaults to the last used profile that has the extension installed.') .option('--proxy-bypass ', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"') .option('--proxy-server ', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"') .addOption(new ProgramOption('--remote-header ', 'headers to send with the remote endpoint connect request, multiple can be specified.').argParser(headerParser).hideHelp()) diff --git a/packages/playwright-core/src/tools/utils/extension.ts b/packages/playwright-core/src/tools/utils/extension.ts index c02e57532d039..48a217113e9a6 100644 --- a/packages/playwright-core/src/tools/utils/extension.ts +++ b/packages/playwright-core/src/tools/utils/extension.ts @@ -22,20 +22,6 @@ export const playwrightExtensionId = 'mmlmfjhmonkocbjadbfplnigmagldckm'; export const playwrightExtensionInstallUrl = `https://chromewebstore.google.com/detail/playwright-extension/${playwrightExtensionId}`; -// A path to a profile inside the user data dir, as shown at chrome://version, selects that profile explicitly. -// The parent must be a user data dir, so that a user data dir merely named like a profile is left alone. -export async function splitProfilePath(dir: string): Promise<{ userDataDir: string, profileDirectory?: string }> { - const name = path.basename(dir); - const userDataDir = path.dirname(dir); - if (isProfileDirectoryName(name) && await pathExists(path.join(userDataDir, 'Local State'))) - return { userDataDir, profileDirectory: name }; - return { userDataDir: dir }; -} - -function isProfileDirectoryName(name: string): boolean { - return name === 'Default' || /^Profile \d+$/.test(name); -} - export async function findPlaywrightExtensionProfile(userDataDir: string): Promise { const profiles = await listProfileDirectories(userDataDir); const lastUsed = await readLastUsedProfile(userDataDir); @@ -56,7 +42,7 @@ async function listProfileDirectories(userDataDir: string): Promise { } catch { return []; } - const profiles = entries.filter(isProfileDirectoryName); + const profiles = entries.filter(entry => entry === 'Default' || /^Profile \d+$/.test(entry)); profiles.sort((a, b) => profileRank(a) - profileRank(b)); return profiles; } diff --git a/tests/extension/extension.spec.ts b/tests/extension/extension.spec.ts index dee8d826013e8..7798b1731c103 100644 --- a/tests/extension/extension.spec.ts +++ b/tests/extension/extension.spec.ts @@ -343,10 +343,10 @@ test(`ignores orphaned preferences entries of an uninstalled extension`, { }).toPass(); }); -test(`--user-data-dir pointing at a profile selects it`, { +test(`--profile-dir-name selects the profile`, { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-mcp/issues/1732' }, }, async ({ startClient, server }, testInfo) => { - // Both profiles have the extension and Default was used last, so only the explicit path selects Profile 1. + // Both profiles have the extension and Default was used last, so only the option selects Profile 1. const userDataDir = testInfo.outputPath('multi-profile'); await fs.mkdir(path.join(userDataDir, 'Default', 'Extensions', extensionId), { recursive: true }); await fs.mkdir(path.join(userDataDir, 'Profile 1', 'Extensions', extensionId), { recursive: true }); @@ -358,7 +358,7 @@ test(`--user-data-dir pointing at a profile selects it`, { await fs.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); const { client } = await startClient({ - args: [`--extension`, `--executable-path=${executablePath}`, `--user-data-dir=${path.join(userDataDir, 'Profile 1')}`], + args: [`--extension`, `--executable-path=${executablePath}`, `--user-data-dir=${userDataDir}`, `--profile-dir-name=Profile 1`], }); client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } }).catch(() => {}); @@ -368,25 +368,6 @@ test(`--user-data-dir pointing at a profile selects it`, { }).toPass(); }); -test(`--user-data-dir named like a profile is not split`, async ({ startClient, server }, testInfo) => { - // No "Local State" next to it, so this is a user data dir, not a profile inside one. - const userDataDir = testInfo.outputPath('Profile 1'); - await fs.mkdir(path.join(userDataDir, 'Default', 'Extensions', extensionId), { recursive: true }); - - const executablePath = testInfo.outputPath('echo.sh'); - await fs.writeFile(executablePath, '#!/bin/bash\necho "Custom exec args: $@" > "$(dirname "$0")/output.txt"', { mode: 0o755 }); - - const { client } = await startClient({ - args: [`--extension`, `--executable-path=${executablePath}`, `--user-data-dir=${userDataDir}`], - }); - - client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } }).catch(() => {}); - await expect(async () => { - const output = await fs.readFile(testInfo.outputPath('output.txt'), 'utf8'); - expect(output).toContain(`--user-data-dir=${userDataDir} --profile-directory=Default`); - }).toPass(); -}); - test(`fails when extension is missing in custom userDataDir`, async ({ startClient, server }) => { const userDataDir = test.info().outputPath('empty-profile'); @@ -419,11 +400,15 @@ test(`navigate with extension via --user-data-dir`, { }); }); -test(`navigate with extension via --user-data-dir pointing at the profile`, async ({ browserWithExtension, startClient, server }) => { +test(`navigate with extension via PLAYWRIGHT_MCP_PROFILE_DIR_NAME`, async ({ browserWithExtension, startClient, server }) => { const browserContext = await browserWithExtension.launch(); const { client } = await startClient({ - args: [`--extension`, `--user-data-dir=${path.join(browserWithExtension.userDataDir, 'Default')}`], + args: [`--extension`], + env: { + PLAYWRIGHT_MCP_PROFILE_DIR_NAME: 'Default', + PWTEST_EXTENSION_USER_DATA_DIR: browserWithExtension.userDataDir, + }, }); const response = await connectAndNavigate(browserContext, client, server.HELLO_WORLD);