Skip to content
Merged
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
12 changes: 9 additions & 3 deletions packages/playwright-core/src/tools/mcp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,16 @@ export async function loadConfig(configFile: string | undefined): Promise<Config
if (configFile.endsWith('.ini'))
return configFromIniFile(configFile);

const raw = await fs.promises.readFile(configFile, 'utf8');
const data = raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw;
try {
const data = await fs.promises.readFile(configFile, 'utf8');
return JSON.parse(data.charCodeAt(0) === 0xFEFF ? data.slice(1) : data);
} catch {
return JSON.parse(data);
} catch (jsonError) {
// A JSON config is always an object, so JSON-looking input must surface its
// parse error rather than silently falling back to INI (and the default
// config). A leading `[` stays with INI — it is a `[section]` header there.
if (/^\s*\{/.test(data))
throw jsonError;
return configFromIniFile(configFile);
}
}
Expand Down
19 changes: 19 additions & 0 deletions tests/mcp/config-resolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ test.describe('browserName and channel', () => {
expect(config.browser.launchOptions.channel).toBeUndefined();
});

test('malformed JSON config throws instead of falling back to INI', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41893' },
}, async ({}, testInfo) => {
const configFile = testInfo.outputPath('config.json');
// Trailing comma makes this invalid JSON; it must not be silently parsed as INI.
await fs.promises.writeFile(configFile, '{ "browser": { "browserName": "firefox", } }');
await expect(resolveCLIConfigForMCP({ config: configFile }, emptyEnv)).rejects.toThrow();
});

test('INI config starting with a section header still parses', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41893' },
}, async ({}, testInfo) => {
const configFile = testInfo.outputPath('config.cfg');
// A leading `[section]` is INI, not JSON — it must not be treated as malformed JSON.
await fs.promises.writeFile(configFile, '[browser]\nbrowserName = firefox\n');
const config = await resolveCLIConfigForMCP({ config: configFile }, emptyEnv);
expect(config.browser.browserName).toBe('firefox');
});

test('config file browserName + channel are both preserved', async ({}, testInfo) => {
const configFile = testInfo.outputPath('config.json');
const fileConfig: Config = {
Expand Down
Loading