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
2 changes: 1 addition & 1 deletion docs/src/api/class-consolemessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ The text of the console message.
## method: ConsoleMessage.type
* since: v1.8
* langs: js, python
- returns: <[ConsoleMessageType]<"log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"timeEnd">>
- returns: <[ConsoleMessageType]<"log"|"debug"|"info"|"error"|"warning"|"dir"|"dirxml"|"table"|"trace"|"clear"|"startGroup"|"startGroupCollapsed"|"endGroup"|"assert"|"profile"|"profileEnd"|"count"|"time"|"timeEnd">>

## method: ConsoleMessage.type
* since: v1.8
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18861,7 +18861,7 @@ export interface ConsoleMessage {
*/
text(): string;

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

/**
* The web worker or service worker that produced this console message, if any. Note that console messages from web
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18861,7 +18861,7 @@ export interface ConsoleMessage {
*/
text(): string;

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

/**
* The web worker or service worker that produced this console message, if any. Note that console messages from web
Expand Down
26 changes: 24 additions & 2 deletions packages/playwright/src/mcp/browser/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type CLIOptions = {
cdpEndpoint?: string;
cdpHeader?: Record<string, string>;
config?: string;
consoleLevel?: 'error' | 'warning' | 'info' | 'debug';
device?: string;
executablePath?: string;
grantPermissions?: string[];
Expand Down Expand Up @@ -81,6 +82,9 @@ export const defaultConfig: FullConfig = {
viewport: null,
},
},
console: {
level: 'info',
},
network: {
allowedOrigins: undefined,
blockedOrigins: undefined,
Expand All @@ -104,6 +108,9 @@ export type FullConfig = Config & {
launchOptions: NonNullable<BrowserUserConfig['launchOptions']>;
contextOptions: NonNullable<BrowserUserConfig['contextOptions']>;
},
console: {
level: 'error' | 'warning' | 'info' | 'debug';
},
network: NonNullable<Config['network']>,
saveTrace: boolean;
server: NonNullable<Config['server']>,
Expand Down Expand Up @@ -241,6 +248,9 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config {
allowedHosts: cliOptions.allowedHosts,
},
capabilities: cliOptions.caps as ToolCapability[],
console: {
level: cliOptions.consoleLevel,
},
network: {
allowedOrigins: cliOptions.allowedOrigins,
blockedOrigins: cliOptions.blockedOrigins,
Expand Down Expand Up @@ -274,6 +284,8 @@ function configFromEnv(): Config {
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
options.consoleLevel = enumParser<'error' | 'warning' | 'info' | 'debug'>('--console-level', ['error', 'warning', 'info', 'debug'], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
Expand All @@ -287,8 +299,8 @@ function configFromEnv(): Config {
if (initScript)
options.initScript = [initScript];
options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED);
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES === 'omit')
options.imageResponses = 'omit';
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
options.imageResponses = enumParser<'allow' | 'omit'>('--image-responses', ['allow', 'omit'], process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
Copy link
Member

Choose a reason for hiding this comment

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

ts should be able to infer the return type of the template based on const strings array

options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX);
options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR);
options.port = numberParser(process.env.PLAYWRIGHT_MCP_PORT);
Expand Down Expand Up @@ -385,6 +397,10 @@ function mergeConfig(base: FullConfig, overrides: Config): FullConfig {
...pickDefined(base),
...pickDefined(overrides),
browser,
console: {
...pickDefined(base.console),
...pickDefined(overrides.console),
},
network: {
...pickDefined(base.network),
...pickDefined(overrides.network),
Expand Down Expand Up @@ -458,6 +474,12 @@ export function headerParser(arg: string | undefined, previous?: Record<string,
return result;
}

export function enumParser<T extends string>(name: string, options: T[], value: string): T {
if (!options.includes(value as T))
throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(', ')}`);
return value as T;
}

function envToBoolean(value: string | undefined): boolean | undefined {
if (value === 'true' || value === '1')
return true;
Expand Down
47 changes: 43 additions & 4 deletions packages/playwright/src/mcp/browser/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ export class Tab extends EventEmitter<TabEventsInterface> {
await this.waitForLoadState('load', { timeout: 5000 });
}

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

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

export type ConsoleMessage = {
type: ReturnType<playwright.ConsoleMessage['type']> | undefined;
type: ReturnType<playwright.ConsoleMessage['type']>;
text: string;
toString(): string;
};
Expand Down Expand Up @@ -354,4 +354,43 @@ export function renderModalStates(modalStates: ModalState[]): string[] {
return result;
}

type ConsoleMessageType = ReturnType<playwright.ConsoleMessage['type']>;
type ConsoleMessageLevel = 'error' | 'warning' | 'info' | 'debug';
const consoleMessageLevels: ConsoleMessageLevel[] = ['error', 'warning', 'info', 'debug'];

function shouldIncludeMessage(thresholdLevel: ConsoleMessageLevel, type: ConsoleMessageType): boolean {
const messageLevel = consoleLevelForMessageType(type);
return consoleMessageLevels.indexOf(messageLevel) <= consoleMessageLevels.indexOf(thresholdLevel);
}

function consoleLevelForMessageType(type: ConsoleMessageType): ConsoleMessageLevel {
switch (type) {
case 'assert':
case 'error':
return 'error';
case 'warning':
return 'warning';
case 'count':
case 'dir':
case 'dirxml':
case 'info':
case 'log':
case 'table':
case 'time':
case 'timeEnd':
return 'info';
case 'clear':
case 'debug':
case 'endGroup':
case 'profile':
case 'profileEnd':
case 'startGroup':
case 'startGroupCollapsed':
case 'trace':
return 'debug';
default:
return 'info';
}
}

const tabSymbol = Symbol('tabSymbol');
4 changes: 2 additions & 2 deletions packages/playwright/src/mcp/browser/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ const console = defineTabTool({
title: 'Get console messages',
description: 'Returns all console messages',
inputSchema: z.object({
onlyErrors: z.boolean().optional().describe('Only return error messages'),
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".'),
}),
type: 'readOnly',
},
handle: async (tab, params, response) => {
const messages = await tab.consoleMessages(params.onlyErrors ? 'error' : undefined);
const messages = await tab.consoleMessages(params.level);
messages.map(message => response.addResult(message.toString()));
},
});
Expand Down
7 changes: 7 additions & 0 deletions packages/playwright/src/mcp/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ export type Config = {
*/
outputDir?: string;

console?: {
/**
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
*/
level?: 'error' | 'warning' | 'info' | 'debug';
},

network?: {
/**
* List of origins to allow the browser to request. Default is to allow all. Origins matching both `allowedOrigins` and `blockedOrigins` will be blocked.
Expand Down
5 changes: 3 additions & 2 deletions packages/playwright/src/mcp/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { colors, ProgramOption } from 'playwright-core/lib/utilsBundle';
import { registry } from 'playwright-core/lib/server';

import * as mcpServer from './sdk/server';
import { commaSeparatedList, dotenvFileLoader, headerParser, numberParser, resolutionParser, resolveCLIConfig, semicolonSeparatedList } from './browser/config';
import { commaSeparatedList, dotenvFileLoader, enumParser, headerParser, numberParser, resolutionParser, resolveCLIConfig, semicolonSeparatedList } from './browser/config';
import { setupExitWatchdog } from './browser/watchdog';
import { contextFactory } from './browser/browserContextFactory';
import { ProxyBackend } from './sdk/proxyBackend';
Expand All @@ -43,6 +43,7 @@ export function decorateCommand(command: Command, version: string) {
.option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
.option('--cdp-header <headers...>', 'CDP headers to send with the connect request, multiple can be specified.', headerParser)
.option('--config <path>', 'path to the configuration file.')
.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']))
.option('--device <device>', 'device to emulate, for example: "iPhone 15"')
.option('--executable-path <path>', 'path to the browser executable.')
.option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.')
Expand All @@ -53,7 +54,7 @@ export function decorateCommand(command: Command, version: string) {
.option('--init-page <path...>', 'path to TypeScript file to evaluate on Playwright page object')
.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.')
.option('--isolated', 'keep the browser profile in memory, do not save it to disk.')
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".')
.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']))
.option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.')
.option('--output-dir <path>', 'path to the directory for output files.')
.option('--port <port>', 'port to listen on for SSE transport.')
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/mcp/test/browserBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async function generatePausedMessage(testInfo: TestInfo, context: playwright.Bro
);
// Only print console errors when pausing on error, not when everything works as expected.
let console = testInfo.errors.length ? await Tab.collectConsoleMessages(page) : [];
console = console.filter(msg => !msg.type || msg.type === 'error');
console = console.filter(msg => msg.type === 'error');
if (console.length) {
lines.push('- Console Messages:');
for (const message of console)
Expand Down
Loading
Loading