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
8 changes: 4 additions & 4 deletions packages/playwright/src/mcp/browser/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ export class Tab extends EventEmitter<TabEventsInterface> {
await this.waitForLoadState('load', { timeout: 5000 });
}

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

async requests(): Promise<Set<playwright.Request>> {
Expand Down Expand Up @@ -314,13 +314,13 @@ function messageToConsoleMessage(message: playwright.ConsoleMessage): ConsoleMes
function pageErrorToConsoleMessage(errorOrValue: Error | any): ConsoleMessage {
if (errorOrValue instanceof Error) {
return {
type: undefined,
type: 'error',
text: errorOrValue.message,
toString: () => errorOrValue.stack || errorOrValue.message,
};
}
return {
type: undefined,
type: 'error',
text: String(errorOrValue),
toString: () => String(errorOrValue),
};
Expand Down
6 changes: 4 additions & 2 deletions packages/playwright/src/mcp/browser/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ const console = defineTabTool({
name: 'browser_console_messages',
title: 'Get console messages',
description: 'Returns all console messages',
inputSchema: z.object({}),
inputSchema: z.object({
onlyErrors: z.boolean().optional().describe('Only return error messages'),
}),
type: 'readOnly',
},
handle: async (tab, params, response) => {
const messages = await tab.consoleMessages();
const messages = await tab.consoleMessages(params.onlyErrors ? 'error' : undefined);
messages.map(message => response.addResult(message.toString()));
},
});
Expand Down
12 changes: 8 additions & 4 deletions packages/playwright/src/mcp/browser/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ export async function waitForCompletion<R>(tab: Tab, callback: () => Promise<R>)
let waitCallback: () => void = () => {};
const waitBarrier = new Promise<void>(f => { waitCallback = f; });

const requestListener = (request: playwright.Request) => requests.add(request);
const requestFinishedListener = (request: playwright.Request) => {
const responseListener = (request: playwright.Request) => {
requests.delete(request);
if (!requests.size)
waitCallback();
};

const requestListener = (request: playwright.Request) => {
requests.add(request);
void request.response().then(() => responseListener(request)).catch(() => {});
Copy link
Member

Choose a reason for hiding this comment

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

just add requestFinishedListener for both requestfinished/requestfailed

Copy link
Member Author

Choose a reason for hiding this comment

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

Those would not arrive with response, that's what I am fixing...

};

const frameNavigateListener = (frame: playwright.Frame) => {
if (frame.parentFrame())
return;
Expand All @@ -47,13 +51,13 @@ export async function waitForCompletion<R>(tab: Tab, callback: () => Promise<R>)
};

tab.page.on('request', requestListener);
tab.page.on('requestfinished', requestFinishedListener);
tab.page.on('requestfailed', responseListener);
tab.page.on('framenavigated', frameNavigateListener);
const timeout = setTimeout(onTimeout, 10000);

const dispose = () => {
tab.page.off('request', requestListener);
tab.page.off('requestfinished', requestFinishedListener);
tab.page.off('requestfailed', responseListener);
tab.page.off('framenavigated', frameNavigateListener);
clearTimeout(timeout);
};
Expand Down
39 changes: 38 additions & 1 deletion tests/mcp/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { test, expect } from './fixtures';
import { test, expect, parseResponse } from './fixtures';

test('browser_console_messages', async ({ client, server }) => {
server.setContent('/', `
Expand Down Expand Up @@ -98,3 +98,40 @@ test('recent console messages', async ({ client, server }) => {
consoleMessages: expect.stringContaining(`- [LOG] Hello, world! @`),
});
});

test('browser_console_messages errors only', async ({ client, server }) => {
await client.callTool({
name: 'browser_navigate',
arguments: {
url: server.HELLO_WORLD,
},
});

console.log(performance.now());
await client.callTool({
name: 'browser_evaluate',
arguments: {
function: `async () => {
console.log("console.log");
console.warn("console.warn");
console.error("console.error");
setTimeout(() => { throw new Error("unhandled"); }, 0);
await fetch('/missing');
}`,
},
});
console.log(performance.now());

const response = parseResponse(await client.callTool({
name: 'browser_console_messages',
arguments: {
onlyErrors: true,
},
}));
console.log(performance.now());
expect.soft(response.result).toContain('console.error');
expect.soft(response.result).toContain('Error: unhandled');
expect.soft(response.result).toContain('404');
expect.soft(response.result).not.toContain('console.log');
expect.soft(response.result).not.toContain('console.warn');
});
2 changes: 1 addition & 1 deletion tests/mcp/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function formatOutput(output: string): string[] {
return output.split('\n').map(line => line.replace(/^pw:mcp:test /, '').replace(/user data dir.*/, 'user data dir').trim()).filter(Boolean);
}

function parseResponse(response: any) {
export function parseResponse(response: any) {
const text = response.content[0].text;
const sections = parseSections(text);

Expand Down
Loading