From 4694d60fc533c58bdb02b931ede74ce8740a8c79 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 5 May 2025 08:23:24 -0700 Subject: [PATCH] fix(config): allow specifying user data dir in config (#342) Fixes https://github.com/microsoft/playwright-mcp/issues/340 --- src/config.ts | 18 +----------------- src/context.ts | 25 +++++++++++++++++++++++-- tests/config.spec.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 tests/config.spec.ts diff --git a/src/config.ts b/src/config.ts index ee9e149..de1da56 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,7 +42,6 @@ export type CLIOptions = { const defaultConfig: Config = { browser: { browserName: 'chromium', - userDataDir: os.tmpdir(), launchOptions: { channel: 'chrome', headless: os.platform() === 'linux' && !process.env.DISPLAY, @@ -100,7 +99,7 @@ export async function configFromCLIOptions(cliOptions: CLIOptions): Promise { } } -async function createUserDataDir(options: { browserName: 'chromium' | 'firefox' | 'webkit', channel: string | undefined }) { - let cacheDirectory: string; - if (process.platform === 'linux') - cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'); - else if (process.platform === 'darwin') - cacheDirectory = path.join(os.homedir(), 'Library', 'Caches'); - else if (process.platform === 'win32') - cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); - else - throw new Error('Unsupported platform: ' + process.platform); - const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${options.channel ?? options.browserName}-profile`); - await fs.promises.mkdir(result, { recursive: true }); - return result; -} - export async function outputFile(config: Config, name: string): Promise { const result = config.outputDir ?? os.tmpdir(); await fs.promises.mkdir(result, { recursive: true }); diff --git a/src/context.ts b/src/context.ts index 313cb95..236015b 100644 --- a/src/context.ts +++ b/src/context.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + import * as playwright from 'playwright'; import { waitForCompletion } from './tools/utils.js'; @@ -333,8 +337,10 @@ ${code.join('\n')} async function launchPersistentContext(browserConfig: Config['browser']): Promise { try { - const browserType = browserConfig?.browserName ? playwright[browserConfig.browserName] : playwright.chromium; - return await browserType.launchPersistentContext(browserConfig?.userDataDir || '', { ...browserConfig?.launchOptions, ...browserConfig?.contextOptions }); + const browserName = browserConfig?.browserName ?? 'chromium'; + const userDataDir = browserConfig?.userDataDir ?? await createUserDataDir({ ...browserConfig, browserName }); + const browserType = playwright[browserName]; + return await browserType.launchPersistentContext(userDataDir, { ...browserConfig?.launchOptions, ...browserConfig?.contextOptions }); } catch (error: any) { if (error.message.includes('Executable doesn\'t exist')) throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`); @@ -342,6 +348,21 @@ async function launchPersistentContext(browserConfig: Config['browser']): Promis } } +async function createUserDataDir(browserConfig: Config['browser']) { + let cacheDirectory: string; + if (process.platform === 'linux') + cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'); + else if (process.platform === 'darwin') + cacheDirectory = path.join(os.homedir(), 'Library', 'Caches'); + else if (process.platform === 'win32') + cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); + else + throw new Error('Unsupported platform: ' + process.platform); + const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${browserConfig?.launchOptions.channel ?? browserConfig?.browserName}-profile`); + await fs.promises.mkdir(result, { recursive: true }); + return result; +} + export async function generateLocator(locator: playwright.Locator): Promise { return (locator as any)._generateLocatorString(); } diff --git a/tests/config.spec.ts b/tests/config.spec.ts new file mode 100644 index 0000000..8c54bc2 --- /dev/null +++ b/tests/config.spec.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'node:fs'; + +import { Config } from '../config.js'; +import { test, expect } from './fixtures.js'; + +test('config user data dir', async ({ startClient, mcpBrowser }, testInfo) => { + const config: Config = { + browser: { + userDataDir: testInfo.outputPath('user-data-dir'), + }, + }; + const configPath = testInfo.outputPath('config.json'); + await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2)); + + const client = await startClient({ args: ['--config', configPath] }); + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: 'data:text/html,Hello, world!', + }, + })).toContainTextContent(`Hello, world!`); + + const files = await fs.promises.readdir(config.browser!.userDataDir!); + expect(files.length).toBeGreaterThan(0); +});