Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/playwright-core/src/tools/cli-daemon/config.default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Configuration for Playwright CLI and MCP.
{
// The browser to use.
"browser": {
// The type of browser to use: "chromium", "firefox", or "webkit".
// "browserName": "chromium",

// Keep the browser profile in memory, do not save it to disk.
// "isolated": false,

// Path to a user data directory for browser profile persistence.
// "userDataDir": "/path/to/user-data-dir",

// Launch options passed to browserType.launchPersistentContext().
// See: https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context
"launchOptions": {
// "channel": "chrome",
// "headless": false,
// "executablePath": "/path/to/browser",
},

// Context options for the browser context.
"contextOptions": {
// "viewport": { "width": 1280, "height": 720 },
},

// Chrome DevTools Protocol endpoint to connect to an existing browser instance.
// "cdpEndpoint": "http://localhost:9222",

// Remote endpoint to connect to an existing Playwright server.
// "remoteEndpoint": "ws://localhost:3000",

// Paths to JavaScript files to add as initialization scripts.
// See https://github.com/microsoft/playwright-mcp#initial-state
// "initScript": [],

// Paths to TypeScript files to add as initialization scripts for Playwright page.
// See https://github.com/microsoft/playwright-mcp#initial-state
// "initPage": [],
},

// Connect to a running browser instance via the Playwright MCP Bridge extension.
// "extension": false,

// Secrets to redact from tool responses.
// "secrets": {},

// The directory to save output files.
// "outputDir": "./.playwright-cli",

"console": {
// Level of console messages to return: "error", "warning", "info", or "debug".
// "level": "info",
},

"network": {
// Origins to allow the browser to request. Default is to allow all.
// Formats: "https://example.com:8080", "http://localhost:*"
// "allowedOrigins": [],

// Origins to block. Overrides allowedOrigins for matching entries.
// "blockedOrigins": [],
},

// Attribute to use for test ids.
// "testIdAttribute": "data-testid",

"timeouts": {
// Default action timeout in ms.
// "action": 5000,

// Default navigation timeout in ms.
// "navigation": 60000,

// Default expect timeout in ms.
// "expect": 5000,
},

"snapshot": {
// Snapshot mode for responses: "full" or "none".
// "mode": "full",
},

// Enable to allow file access outside the workspace.
// "allowUnrestrictedFileAccess": false,

// Language for code generation: "typescript" or "none".
// "codegen": "typescript",
}
15 changes: 6 additions & 9 deletions packages/playwright-core/src/tools/cli-daemon/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ async function ensureConfiguredBrowserInstalled() {
await resolveAndInstall(channel ?? browserName);
} else {
const channel = await findOrInstallDefaultBrowser();
if (channel !== 'chrome')
await createDefaultConfig(channel);
await createDefaultConfig(channel);
}
}

Expand All @@ -152,12 +151,10 @@ async function resolveAndInstall(nameOrChannel: string) {
}

async function createDefaultConfig(channel: string) {
const config = {
browser: {
browserName: 'chromium',
launchOptions: { channel },
},
};
await fs.promises.writeFile(defaultConfigFile(), JSON.stringify(config, null, 2));
const templatePath = libPath('tools', 'cli-daemon', 'config.default.json');
let template = await fs.promises.readFile(templatePath, 'utf-8');
if (channel !== 'chrome')
template = template.replace(`// "channel": "chrome",`, `"channel": "${channel}",`);
await fs.promises.writeFile(defaultConfigFile(), template);
console.log(`✅ Created default config for ${channel} at ${path.relative(process.cwd(), defaultConfigFile())}.`);
}
4 changes: 4 additions & 0 deletions packages/playwright-core/src/tools/mcp/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

import type * as playwright from '../../..';

// This file contains type declarations for the MCP and CLI config. Please keep the following files in sync:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this file is copied verbatim to microsoft/playwright-mcp repo, so let's not have internal comments inside.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we need the comment so that agents know to keep the other one updated. Why are you concerned about adding it to the other repo?

// - ../cli-daemon/config.default.json - default config template that is copied to the user's config file upon workspace initialization
// - configIni.ts - LonghandType type for parsing comes from here

export type ToolCapability =
'config' |
'core' |
Expand Down
10 changes: 4 additions & 6 deletions packages/playwright-core/src/tools/mcp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import path from 'path';
import os from 'os';

import dotenv from 'dotenv';
import { json5 } from '../../utilsBundle';
import { playwright } from '../../inprocess';
import { configFromIniFile } from './configIni';

Expand Down Expand Up @@ -389,12 +390,9 @@ export async function loadConfig(configFile: string | undefined): Promise<Config
if (configFile.endsWith('.ini'))
return configFromIniFile(configFile);

try {
const data = await fs.promises.readFile(configFile, 'utf8');
return JSON.parse(data.charCodeAt(0) === 0xFEFF ? data.slice(1) : data);
} catch {
return configFromIniFile(configFile);
}
const data = await fs.promises.readFile(configFile, 'utf8');
const text = data.charCodeAt(0) === 0xFEFF ? data.slice(1) : data;
return json5.parse(text);
}

function pickDefined<T extends object>(obj: T | undefined): Partial<T> {
Expand Down
6 changes: 6 additions & 0 deletions tests/mcp/cli-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,9 @@ test('project config overrides global config', async ({ cli, server }, testInfo)
const { output } = await cli('eval', 'window.innerWidth + "x" + window.innerHeight', { env });
expect(output).toContain('1024x768');
});

test('config parsing error', async ({ cli }, testInfo) => {
await fs.promises.writeFile(testInfo.outputPath('.playwright', 'cli.config.json'), `not a JSON file`);
const { error } = await cli('open');
expect(error).toContain(`invalid character 'o'`);
});
10 changes: 7 additions & 3 deletions tests/mcp/cli-misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@ test('install workspace w/--skills=agents', async ({ cli }, testInfo) => {
expect(fs.existsSync(skillFile)).toBe(true);
});

test('install handles browser detection', async ({ cli }) => {
test('install creates default config', async ({ cli, mcpBrowser }) => {
const { output } = await cli('install');
// Verify that one of the browser detection outcomes occurred
const foundMatch = output.match(/Found ((?:chrome|msedge)[\w-]*), will use it as the default browser\./m);
if (foundMatch?.[1] !== 'chrome')
expect(output).toContain(`Created default config for ${foundMatch?.[1] ?? 'chromium'}.`);
expect(output).toContain(`Created default config for ${foundMatch?.[1]} at .playwright${path.sep}cli.config.json.`);

if (mcpBrowser !== 'firefox' && mcpBrowser !== 'webkit') {
const config = fs.readFileSync(test.info().outputPath('.playwright', 'cli.config.json'), 'utf-8');
expect(config).toContain(`"channel": "${foundMatch?.[1]}"`);
}
});
11 changes: 7 additions & 4 deletions tests/mcp/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,15 @@ export const test = serverTest.extend<TestFixtures & TestOptions, WorkerFixtures
if (mcpBrowser)
args.push(`--browser=${mcpBrowser}`);
if (options?.config) {
const configFile = testInfo.outputPath('config.json');
if (typeof options.config === 'object')
if (typeof options.config === 'object') {
const configFile = testInfo.outputPath('config.json');
await fs.promises.writeFile(configFile, JSON.stringify(options.config, null, 2));
else if (typeof options.config === 'string')
args.push(`--config=${path.relative(configDir, configFile)}`);
} else if (typeof options.config === 'string') {
const configFile = testInfo.outputPath('config.ini');
await fs.promises.writeFile(configFile, options.config.trim());
args.push(`--config=${path.relative(configDir, configFile)}`);
args.push(`--config=${path.relative(configDir, configFile)}`);
}
}
if (!options?.noTimeoutForTest)
args.push('--timeout-action=10000');
Expand Down
Loading