Skip to content

fix(mcp): launch the Chrome profile that has the extension installed#41939

Merged
dcrousso merged 1 commit into
microsoft:mainfrom
dcrousso:fix-41916
Jul 22, 2026
Merged

fix(mcp): launch the Chrome profile that has the extension installed#41939
dcrousso merged 1 commit into
microsoft:mainfrom
dcrousso:fix-41916

Conversation

@dcrousso

Copy link
Copy Markdown
Contributor

extension mode validated that the playwright extension was installed in any of Chrome's profiles under the user data directory, but then launched without --profile-directory, meaning it could open a different profile than the one that was checked

when the opened profile lacks the extension, connect.html can't load and the connection silently hangs for ~120s

resolve the profile that actually has the extension (preferring the last-used profile from Local State so we don't switch away from the user's session unnecessarily) and pass it as --profile-directory

fixes #41916

@dcrousso
dcrousso requested a review from yury-s July 22, 2026 17:04
args.push(`--user-data-dir=${testUserDataDir}`);
const userDataDir = testUserDataDir ?? this._userDataDir;
const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined;
if (profileDirectory)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work in edge too?

Comment thread tests/extension/extension.spec.ts Outdated
const userDataDir = test.info().outputPath('multi-profile');
await fs.mkdir(path.join(userDataDir, 'Profile 5', 'Extensions', extensionId), { recursive: true });

const executablePath = test.info().outputPath('echo.sh');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do e2e test instead and run chrome with 2 real profiles?

Comment thread tests/extension/extension.spec.ts Outdated
const userDataDir = test.info().outputPath('multi-profile-last-used');
await fs.mkdir(path.join(userDataDir, 'Default', 'Extensions', extensionId), { recursive: true });
await fs.mkdir(path.join(userDataDir, 'Profile 5', 'Extensions', extensionId), { recursive: true });
await fs.writeFile(path.join(userDataDir, 'Local State'), JSON.stringify({ profile: { last_used: 'Profile 5' } }));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto. We already parse chromium private data structures and we want to know if it breaks.

const userDataDir = testUserDataDir ?? this._userDataDir;
const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined;
if (profileDirectory)
args.push(`--profile-directory=${profileDirectory}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should accept an option for explicit profile selection (the directory names don't match profile names in the ui though)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a --profile-directory option and PLAYWRIGHT_MCP_PROFILE_DIRECTORY environment variable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented below, let's remove it for now.

@github-actions

This comment has been minimized.

@yury-s yury-s left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's drop --profile-directory option for now, otherwise looks good

.option('--output-dir <path>', 'path to the directory for output files.')
.option('--output-max-size <bytes>', 'Threshold for evicting old output files, in bytes.', numberParser)
.option('--port <port>', 'port to listen on for SSE transport.')
.option('--profile-directory <directory>', 'in extension mode, the browser profile directory to connect to (for example "Default" or "Profile 1"). Defaults to the profile that has the extension installed.')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's hold off, we already have --user-data-dir option and this just adds to the confusion (there is no easy way for the user to guess which of "Profile N" corresponds to the user profile "Foo"). If there is demand we can add it later. We'd also need to expose it as an option in playwright-cli.

const userDataDir = testUserDataDir ?? this._userDataDir;
const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : undefined;
if (profileDirectory)
args.push(`--profile-directory=${profileDirectory}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented below, let's remove it for now.

Comment thread tests/extension/extension.spec.ts Outdated
headless: false,
ignoreDefaultArgs: ['--enable-automation'],
args: [
`--profile-directory=${profile}`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it actually create a new profile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe so, but given your comment below im removing this

Comment thread tests/extension/extension.spec.ts Outdated
await createProfile(userDataDir, 'Default', mcpBrowser);
await createProfile(userDataDir, 'Profile 1', mcpBrowser, pathToExtension);

const executablePath = testInfo.outputPath('echo.sh');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still just a unit test for the logic in our code that constructs the parameters, in that case let's just keep one simple test and it's okay to create mock profiles manually

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

extension mode validated that the playwright extension was installed in any of Chrome's profiles under the user data directory, but then launched without `--profile-directory`, meaning it could open a different profile than the one that was checked

when the opened profile lacks the extension, `connect.html` can't load and the connection silently hangs for ~120s

resolve the profile that actually has the extension (preferring the last-used profile from Local State so we don't switch away from the user's session unnecessarily) and pass it as `--profile-directory`
@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

1 failed
❌ [firefox] › mcp/cli-devtools.spec.ts:173 › response-body returns just the body @mcp-windows-latest-firefox

7785 passed, 1262 skipped


Merge workflow run.

@dcrousso
dcrousso merged commit 0edafe4 into microsoft:main Jul 22, 2026
17 of 18 checks passed
@dcrousso
dcrousso deleted the fix-41916 branch July 22, 2026 21:04
@github-actions

Copy link
Copy Markdown
Contributor

🟡 One failure I can't pin down — leaning unrelated

Hi, I'm the Playwright bot and I took a first look at the CI failures.

The latest run has a single failure: [firefox] › mcp/cli-devtools.spec.ts:173 › response-body returns just the body on mcp-windows-latest-firefox. Your diff shouldn't be able to reach it — every change here is extension-mode-only, and this is the plain (non-extension) firefox network path. But it's never failed anywhere else in the results DB, so I can't call it a proven flake. Uncertain, leaning unrelated.

Details

Overall: this PR only touches MCP extension mode. All four changed paths — cdpRelay.ts (_openConnectPageInBrowser), extensionContextFactory.ts (createExtensionBrowser), utils/extension.ts (findPlaywrightExtensionProfile), and tests/extension/extension.spec.ts — run behind the extension branch. The failing test doesn't exercise extension mode.

Uncertain

  • [firefox] › mcp/cli-devtools.spec.ts:173 › response-body returns just the body (mcp-windows-latest-firefox) — failed with expect(received).not.toBeNull() / Received: null: the response body came back null. In the results DB this test has passed every other run — chrome 1200/1200, chromium 1200/1200, firefox 1172/1173, webkit 1210/1210, msedge 396/396 across 400+ SHAs each — and the only failure is this one, on your PR's SHA (23ae27b). Because I can't cite it failing anywhere the PR isn't responsible for, I won't label it a flake. But the diff is entirely extension-gated and this is the ordinary firefox devtools/network flow, and a lone null-body on one OS/engine reads like a startup or timing hiccup. To settle it, re-run the job: green on retry with no code change confirms it's not yours.

Triaged by the Playwright bot - agent run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@playwright/mcp 0.0.78 --extension: preflight validation and browser launch can target different Chrome profiles

2 participants