Skip to content

Commit 408ee0f

Browse files
authored
feat(mcp): allow passing --console-level (#38439)
1 parent 8146b8e commit 408ee0f

File tree

13 files changed

+448
-360
lines changed

13 files changed

+448
-360
lines changed

docs/src/api/class-consolemessage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ The text of the console message.
140140
## method: ConsoleMessage.type
141141
* since: v1.8
142142
* langs: js, python
143-
- returns: <[ConsoleMessageType]<"log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd">>
143+
- returns: <[ConsoleMessageType]<"log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"time"|"timeEnd">>
144144

145145
## method: ConsoleMessage.type
146146
* since: v1.8

packages/playwright-client/types/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18861,7 +18861,7 @@ export interface ConsoleMessage {
1886118861
*/
1886218862
text(): string;
1886318863

18864-
type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd";
18864+
type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"time"|"timeEnd";
1886518865

1886618866
/**
1886718867
* The web worker or service worker that produced this console message, if any. Note that console messages from web

packages/playwright-core/types/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18861,7 +18861,7 @@ export interface ConsoleMessage {
1886118861
*/
1886218862
text(): string;
1886318863

18864-
type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd";
18864+
type(): "log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"time"|"timeEnd";
1886518865

1886618866
/**
1886718867
* The web worker or service worker that produced this console message, if any. Note that console messages from web

packages/playwright/src/mcp/browser/config.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type CLIOptions = {
3939
cdpEndpoint?: string;
4040
cdpHeader?: Record<string, string>;
4141
config?: string;
42+
consoleLevel?: 'error' | 'warning' | 'info' | 'debug';
4243
device?: string;
4344
executablePath?: string;
4445
grantPermissions?: string[];
@@ -81,6 +82,9 @@ export const defaultConfig: FullConfig = {
8182
viewport: null,
8283
},
8384
},
85+
console: {
86+
level: 'info',
87+
},
8488
network: {
8589
allowedOrigins: undefined,
8690
blockedOrigins: undefined,
@@ -104,6 +108,9 @@ export type FullConfig = Config & {
104108
launchOptions: NonNullable<BrowserUserConfig['launchOptions']>;
105109
contextOptions: NonNullable<BrowserUserConfig['contextOptions']>;
106110
},
111+
console: {
112+
level: 'error' | 'warning' | 'info' | 'debug';
113+
},
107114
network: NonNullable<Config['network']>,
108115
saveTrace: boolean;
109116
server: NonNullable<Config['server']>,
@@ -241,6 +248,9 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config {
241248
allowedHosts: cliOptions.allowedHosts,
242249
},
243250
capabilities: cliOptions.caps as ToolCapability[],
251+
console: {
252+
level: cliOptions.consoleLevel,
253+
},
244254
network: {
245255
allowedOrigins: cliOptions.allowedOrigins,
246256
blockedOrigins: cliOptions.blockedOrigins,
@@ -274,6 +284,8 @@ function configFromEnv(): Config {
274284
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
275285
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
276286
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
287+
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
288+
options.consoleLevel = enumParser<'error' | 'warning' | 'info' | 'debug'>('--console-level', ['error', 'warning', 'info', 'debug'], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
277289
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
278290
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
279291
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
@@ -287,8 +299,8 @@ function configFromEnv(): Config {
287299
if (initScript)
288300
options.initScript = [initScript];
289301
options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED);
290-
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES === 'omit')
291-
options.imageResponses = 'omit';
302+
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
303+
options.imageResponses = enumParser<'allow' | 'omit'>('--image-responses', ['allow', 'omit'], process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
292304
options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX);
293305
options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR);
294306
options.port = numberParser(process.env.PLAYWRIGHT_MCP_PORT);
@@ -385,6 +397,10 @@ function mergeConfig(base: FullConfig, overrides: Config): FullConfig {
385397
...pickDefined(base),
386398
...pickDefined(overrides),
387399
browser,
400+
console: {
401+
...pickDefined(base.console),
402+
...pickDefined(overrides.console),
403+
},
388404
network: {
389405
...pickDefined(base.network),
390406
...pickDefined(overrides.network),
@@ -458,6 +474,12 @@ export function headerParser(arg: string | undefined, previous?: Record<string,
458474
return result;
459475
}
460476

477+
export function enumParser<T extends string>(name: string, options: T[], value: string): T {
478+
if (!options.includes(value as T))
479+
throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(', ')}`);
480+
return value as T;
481+
}
482+
461483
function envToBoolean(value: string | undefined): boolean | undefined {
462484
if (value === 'true' || value === '1')
463485
return true;

packages/playwright/src/mcp/browser/tab.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,9 @@ export class Tab extends EventEmitter<TabEventsInterface> {
217217
await this.waitForLoadState('load', { timeout: 5000 });
218218
}
219219

220-
async consoleMessages(type?: 'error'): Promise<ConsoleMessage[]> {
220+
async consoleMessages(level: ConsoleMessageLevel): Promise<ConsoleMessage[]> {
221221
await this._initializedPromise;
222-
return this._consoleMessages.filter(message => type ? message.type === type : true);
222+
return this._consoleMessages.filter(message => shouldIncludeMessage(level, message.type));
223223
}
224224

225225
async requests(): Promise<Set<playwright.Request>> {
@@ -244,7 +244,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
244244
});
245245
if (tabSnapshot) {
246246
// Assign console message late so that we did not lose any to modal state.
247-
tabSnapshot.consoleMessages = this._recentConsoleMessages;
247+
tabSnapshot.consoleMessages = this._recentConsoleMessages.filter(message => shouldIncludeMessage(this.context.config.console.level, message.type));
248248
this._recentConsoleMessages = [];
249249
}
250250
// If we failed to capture a snapshot this time, make sure we do a full one next time,
@@ -317,7 +317,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
317317
}
318318

319319
export type ConsoleMessage = {
320-
type: ReturnType<playwright.ConsoleMessage['type']> | undefined;
320+
type: ReturnType<playwright.ConsoleMessage['type']>;
321321
text: string;
322322
toString(): string;
323323
};
@@ -354,4 +354,43 @@ export function renderModalStates(modalStates: ModalState[]): string[] {
354354
return result;
355355
}
356356

357+
type ConsoleMessageType = ReturnType<playwright.ConsoleMessage['type']>;
358+
type ConsoleMessageLevel = 'error' | 'warning' | 'info' | 'debug';
359+
const consoleMessageLevels: ConsoleMessageLevel[] = ['error', 'warning', 'info', 'debug'];
360+
361+
function shouldIncludeMessage(thresholdLevel: ConsoleMessageLevel, type: ConsoleMessageType): boolean {
362+
const messageLevel = consoleLevelForMessageType(type);
363+
return consoleMessageLevels.indexOf(messageLevel) <= consoleMessageLevels.indexOf(thresholdLevel);
364+
}
365+
366+
function consoleLevelForMessageType(type: ConsoleMessageType): ConsoleMessageLevel {
367+
switch (type) {
368+
case 'assert':
369+
case 'error':
370+
return 'error';
371+
case 'warning':
372+
return 'warning';
373+
case 'count':
374+
case 'dir':
375+
case 'dirxml':
376+
case 'info':
377+
case 'log':
378+
case 'table':
379+
case 'time':
380+
case 'timeEnd':
381+
return 'info';
382+
case 'clear':
383+
case 'debug':
384+
case 'endGroup':
385+
case 'profile':
386+
case 'profileEnd':
387+
case 'startGroup':
388+
case 'startGroupCollapsed':
389+
case 'trace':
390+
return 'debug';
391+
default:
392+
return 'info';
393+
}
394+
}
395+
357396
const tabSymbol = Symbol('tabSymbol');

packages/playwright/src/mcp/browser/tools/console.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ const console = defineTabTool({
2424
title: 'Get console messages',
2525
description: 'Returns all console messages',
2626
inputSchema: z.object({
27-
onlyErrors: z.boolean().optional().describe('Only return error messages'),
27+
level: z.enum(['error', 'warning', 'info', 'debug']).default('info').describe('Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info".'),
2828
}),
2929
type: 'readOnly',
3030
},
3131
handle: async (tab, params, response) => {
32-
const messages = await tab.consoleMessages(params.onlyErrors ? 'error' : undefined);
32+
const messages = await tab.consoleMessages(params.level);
3333
messages.map(message => response.addResult(message.toString()));
3434
},
3535
});

packages/playwright/src/mcp/config.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ export type Config = {
142142
*/
143143
outputDir?: string;
144144

145+
console?: {
146+
/**
147+
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
148+
*/
149+
level?: 'error' | 'warning' | 'info' | 'debug';
150+
},
151+
145152
network?: {
146153
/**
147154
* List of origins to allow the browser to request. Default is to allow all. Origins matching both `allowedOrigins` and `blockedOrigins` will be blocked.

packages/playwright/src/mcp/program.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { colors, ProgramOption } from 'playwright-core/lib/utilsBundle';
2222
import { registry } from 'playwright-core/lib/server';
2323

2424
import * as mcpServer from './sdk/server';
25-
import { commaSeparatedList, dotenvFileLoader, headerParser, numberParser, resolutionParser, resolveCLIConfig, semicolonSeparatedList } from './browser/config';
25+
import { commaSeparatedList, dotenvFileLoader, enumParser, headerParser, numberParser, resolutionParser, resolveCLIConfig, semicolonSeparatedList } from './browser/config';
2626
import { setupExitWatchdog } from './browser/watchdog';
2727
import { contextFactory } from './browser/browserContextFactory';
2828
import { ProxyBackend } from './sdk/proxyBackend';
@@ -43,6 +43,7 @@ export function decorateCommand(command: Command, version: string) {
4343
.option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
4444
.option('--cdp-header <headers...>', 'CDP headers to send with the connect request, multiple can be specified.', headerParser)
4545
.option('--config <path>', 'path to the configuration file.')
46+
.option('--console-level <level>', 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', enumParser.bind(null, '--console-level', ['error', 'warning', 'info', 'debug']))
4647
.option('--device <device>', 'device to emulate, for example: "iPhone 15"')
4748
.option('--executable-path <path>', 'path to the browser executable.')
4849
.option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.')
@@ -53,7 +54,7 @@ export function decorateCommand(command: Command, version: string) {
5354
.option('--init-page <path...>', 'path to TypeScript file to evaluate on Playwright page object')
5455
.option('--init-script <path...>', 'path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page\'s scripts. Can be specified multiple times.')
5556
.option('--isolated', 'keep the browser profile in memory, do not save it to disk.')
56-
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".')
57+
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".', enumParser.bind(null, '--image-responses', ['allow', 'omit']))
5758
.option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.')
5859
.option('--output-dir <path>', 'path to the directory for output files.')
5960
.option('--port <port>', 'port to listen on for SSE transport.')

packages/playwright/src/mcp/test/browserBackend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async function generatePausedMessage(testInfo: TestInfo, context: playwright.Bro
9595
);
9696
// Only print console errors when pausing on error, not when everything works as expected.
9797
let console = testInfo.errors.length ? await Tab.collectConsoleMessages(page) : [];
98-
console = console.filter(msg => !msg.type || msg.type === 'error');
98+
console = console.filter(msg => msg.type === 'error');
9999
if (console.length) {
100100
lines.push('- Console Messages:');
101101
for (const message of console)

0 commit comments

Comments
 (0)