chore: include recent console logs in results (#689)

This commit is contained in:
Pavel Feldman 2025-07-17 14:58:44 -07:00 committed by GitHub
parent c97bc6e2ae
commit 5bfff0a059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 103 additions and 51 deletions

View File

@ -161,14 +161,13 @@ export class Context {
}); });
const result: string[] = []; const result: string[] = [];
result.push(`- Ran Playwright code: result.push(`### Ran Playwright code
\`\`\`js \`\`\`js
${code.join('\n')} ${code.join('\n')}
\`\`\` \`\`\``);
`);
if (this.modalStates().length) { if (this.modalStates().length) {
result.push(...this.modalStatesMarkdown()); result.push('', ...this.modalStatesMarkdown());
return { return {
content: [{ content: [{
type: 'text', type: 'text',
@ -177,6 +176,13 @@ ${code.join('\n')}
}; };
} }
const messages = tab.takeRecentConsoleMessages();
if (messages.length) {
result.push('', `### New console messages`);
for (const message of messages)
result.push(`- ${trim(message.toString(), 100)}`);
}
if (this._downloads.length) { if (this._downloads.length) {
result.push('', '### Downloads'); result.push('', '### Downloads');
for (const entry of this._downloads) { for (const entry of this._downloads) {
@ -185,15 +191,16 @@ ${code.join('\n')}
else else
result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`); result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
} }
result.push('');
} }
if (captureSnapshot && tab.hasSnapshot()) { if (captureSnapshot && tab.hasSnapshot()) {
if (this.tabs().length > 1) if (this.tabs().length > 1)
result.push(await this.listTabsMarkdown(), ''); result.push('', await this.listTabsMarkdown());
if (this.tabs().length > 1) if (this.tabs().length > 1)
result.push('### Current tab'); result.push('', '### Current tab');
else
result.push('', '### Page state');
result.push( result.push(
`- Page URL: ${tab.page.url()}`, `- Page URL: ${tab.page.url()}`,
@ -346,3 +353,9 @@ ${code.join('\n')}
return result; return result;
} }
} }
function trim(text: string, maxLength: number) {
if (text.length <= maxLength)
return text;
return text.slice(0, maxLength) + '...';
}

View File

@ -42,7 +42,7 @@ export class PageSnapshot {
private async _build() { private async _build() {
const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI()); const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI());
this._text = [ this._text = [
`- Page Snapshot`, `- Page Snapshot:`,
'```yaml', '```yaml',
snapshot, snapshot,
'```', '```',

View File

@ -26,6 +26,7 @@ export class Tab {
readonly context: Context; readonly context: Context;
readonly page: playwright.Page; readonly page: playwright.Page;
private _consoleMessages: ConsoleMessage[] = []; private _consoleMessages: ConsoleMessage[] = [];
private _recentConsoleMessages: ConsoleMessage[] = [];
private _requests: Map<playwright.Request, playwright.Response | null> = new Map(); private _requests: Map<playwright.Request, playwright.Response | null> = new Map();
private _snapshot: PageSnapshot | undefined; private _snapshot: PageSnapshot | undefined;
private _onPageClose: (tab: Tab) => void; private _onPageClose: (tab: Tab) => void;
@ -34,8 +35,8 @@ export class Tab {
this.context = context; this.context = context;
this.page = page; this.page = page;
this._onPageClose = onPageClose; this._onPageClose = onPageClose;
page.on('console', event => this._consoleMessages.push(messageToConsoleMessage(event))); page.on('console', event => this._handleConsoleMessage(messageToConsoleMessage(event)));
page.on('pageerror', error => this._consoleMessages.push(pageErrorToConsoleMessage(error))); page.on('pageerror', error => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
page.on('request', request => this._requests.set(request, null)); page.on('request', request => this._requests.set(request, null));
page.on('response', response => this._requests.set(response.request(), response)); page.on('response', response => this._requests.set(response.request(), response));
page.on('close', () => this._onClose()); page.on('close', () => this._onClose());
@ -56,9 +57,15 @@ export class Tab {
private _clearCollectedArtifacts() { private _clearCollectedArtifacts() {
this._consoleMessages.length = 0; this._consoleMessages.length = 0;
this._recentConsoleMessages.length = 0;
this._requests.clear(); this._requests.clear();
} }
private _handleConsoleMessage(message: ConsoleMessage) {
this._consoleMessages.push(message);
this._recentConsoleMessages.push(message);
}
private _onClose() { private _onClose() {
this._clearCollectedArtifacts(); this._clearCollectedArtifacts();
this._onPageClose(this); this._onPageClose(this);
@ -119,6 +126,12 @@ export class Tab {
async captureSnapshot() { async captureSnapshot() {
this._snapshot = await PageSnapshot.create(this.page); this._snapshot = await PageSnapshot.create(this.page);
} }
takeRecentConsoleMessages(): ConsoleMessage[] {
const result = this._recentConsoleMessages.slice();
this._recentConsoleMessages.length = 0;
return result;
}
} }
export type ConsoleMessage = { export type ConsoleMessage = {

View File

@ -46,14 +46,15 @@ test('cdp server reuse tab', async ({ cdpServer, startClient, server }) => {
expect(await client.callTool({ expect(await client.callTool({
name: 'browser_snapshot', name: 'browser_snapshot',
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// <internal code to capture accessibility snapshot> // <internal code to capture accessibility snapshot>
\`\`\` \`\`\`
### Page state
- Page URL: ${server.HELLO_WORLD} - Page URL: ${server.HELLO_WORLD}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Hello, world! - generic [active] [ref=e1]: Hello, world!
\`\`\` \`\`\`

View File

@ -34,15 +34,16 @@ test('browser_click', async ({ client, server, mcpBrowser }) => {
ref: 'e2', ref: 'e2',
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// Click Submit button // Click Submit button
await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Submit' }).click();
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- button "Submit" ${mcpBrowser !== 'webkit' || process.platform === 'linux' ? '[active] ' : ''}[ref=e2] - button "Submit" ${mcpBrowser !== 'webkit' || process.platform === 'linux' ? '[active] ' : ''}[ref=e2]
\`\`\` \`\`\`
@ -73,15 +74,16 @@ test('browser_click (double)', async ({ client, server }) => {
doubleClick: true, doubleClick: true,
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// Double click Click me // Double click Click me
await page.getByRole('heading', { name: 'Click me' }).dblclick(); await page.getByRole('heading', { name: 'Click me' }).dblclick();
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- heading "Double clicked" [level=1] [ref=e3] - heading "Double clicked" [level=1] [ref=e3]
\`\`\` \`\`\`

View File

@ -66,3 +66,31 @@ test('browser_console_messages (page error)', async ({ client, server }) => {
expect(resource).toHaveTextContent(/Error: Error in script/); expect(resource).toHaveTextContent(/Error: Error in script/);
expect(resource).toHaveTextContent(new RegExp(server.PREFIX)); expect(resource).toHaveTextContent(new RegExp(server.PREFIX));
}); });
test('recent console messages', async ({ client, server }) => {
server.setContent('/', `
<!DOCTYPE html>
<html>
<button onclick="console.log('Hello, world!');">Click me</button>
</html>
`, 'text/html');
await client.callTool({
name: 'browser_navigate',
arguments: {
url: server.PREFIX,
},
});
const response = await client.callTool({
name: 'browser_click',
arguments: {
element: 'Click me',
ref: 'e2',
},
});
expect(response).toContainTextContent(`
### New console messages
- [LOG] Hello, world! @`);
});

View File

@ -21,15 +21,16 @@ test('browser_navigate', async ({ client, server }) => {
name: 'browser_navigate', name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD }, arguments: { url: server.HELLO_WORLD },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// Navigate to ${server.HELLO_WORLD} // Navigate to ${server.HELLO_WORLD}
await page.goto('${server.HELLO_WORLD}'); await page.goto('${server.HELLO_WORLD}');
\`\`\` \`\`\`
### Page state
- Page URL: ${server.HELLO_WORLD} - Page URL: ${server.HELLO_WORLD}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Hello, world! - generic [active] [ref=e1]: Hello, world!
\`\`\` \`\`\`
@ -59,15 +60,16 @@ test('browser_select_option', async ({ client, server }) => {
values: ['bar'], values: ['bar'],
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// Select options [bar] in Select // Select options [bar] in Select
await page.getByRole('combobox').selectOption(['bar']); await page.getByRole('combobox').selectOption(['bar']);
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- combobox [ref=e2]: - combobox [ref=e2]:
- option "Foo" - option "Foo"
@ -99,15 +101,16 @@ test('browser_select_option (multiple)', async ({ client, server }) => {
values: ['bar', 'baz'], values: ['bar', 'baz'],
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// Select options [bar, baz] in Select // Select options [bar, baz] in Select
await page.getByRole('listbox').selectOption(['bar', 'baz']); await page.getByRole('listbox').selectOption(['bar', 'baz']);
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: Title - Page Title: Title
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- listbox [ref=e2]: - listbox [ref=e2]:
- option "Foo" [ref=e3] - option "Foo" [ref=e3]
@ -196,7 +199,7 @@ test('browser_resize', async ({ client, server }) => {
height: 780, height: 780,
}, },
}); });
expect(response).toContainTextContent(`- Ran Playwright code: expect(response).toContainTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// Resize browser window to 390x780 // Resize browser window to 390x780
await page.setViewportSize({ width: 390, height: 780 }); await page.setViewportSize({ width: 390, height: 780 });

View File

@ -29,7 +29,7 @@ test('alert dialog', async ({ client, server }) => {
element: 'Button', element: 'Button',
ref: 'e2', ref: 'e2',
}, },
})).toHaveTextContent(`- Ran Playwright code: })).toHaveTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// Click Button // Click Button
await page.getByRole('button', { name: 'Button' }).click(); await page.getByRole('button', { name: 'Button' }).click();
@ -46,14 +46,15 @@ await page.getByRole('button', { name: 'Button' }).click();
}); });
expect(result).not.toContainTextContent('### Modal state'); expect(result).not.toContainTextContent('### Modal state');
expect(result).toContainTextContent(`- Ran Playwright code: expect(result).toContainTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// <internal code to handle "alert" dialog> // <internal code to handle "alert" dialog>
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: - Page Title:
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- button "Button"`); - button "Button"`);
}); });
@ -77,7 +78,7 @@ test('two alert dialogs', async ({ client, server }) => {
element: 'Button', element: 'Button',
ref: 'e2', ref: 'e2',
}, },
})).toHaveTextContent(`- Ran Playwright code: })).toHaveTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// Click Button // Click Button
await page.getByRole('button', { name: 'Button' }).click(); await page.getByRole('button', { name: 'Button' }).click();
@ -138,7 +139,7 @@ test('confirm dialog (true)', async ({ client, server }) => {
expect(result).not.toContainTextContent('### Modal state'); expect(result).not.toContainTextContent('### Modal state');
expect(result).toContainTextContent('// <internal code to handle "confirm" dialog>'); expect(result).toContainTextContent('// <internal code to handle "confirm" dialog>');
expect(result).toContainTextContent(`- Page Snapshot expect(result).toContainTextContent(`- Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: "true" - generic [active] [ref=e1]: "true"
\`\`\``); \`\`\``);
@ -173,7 +174,7 @@ test('confirm dialog (false)', async ({ client, server }) => {
}, },
}); });
expect(result).toContainTextContent(`- Page Snapshot expect(result).toContainTextContent(`- Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: "false" - generic [active] [ref=e1]: "false"
\`\`\``); \`\`\``);
@ -209,7 +210,7 @@ test('prompt dialog', async ({ client, server }) => {
}, },
}); });
expect(result).toContainTextContent(`- Page Snapshot expect(result).toContainTextContent(`- Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Answer - generic [active] [ref=e1]: Answer
\`\`\``); \`\`\``);
@ -228,7 +229,7 @@ test('alert dialog w/ race', async ({ client, server }) => {
element: 'Button', element: 'Button',
ref: 'e2', ref: 'e2',
}, },
})).toHaveTextContent(`- Ran Playwright code: })).toHaveTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// Click Button // Click Button
await page.getByRole('button', { name: 'Button' }).click(); await page.getByRole('button', { name: 'Button' }).click();
@ -245,14 +246,15 @@ await page.getByRole('button', { name: 'Button' }).click();
}); });
expect(result).not.toContainTextContent('### Modal state'); expect(result).not.toContainTextContent('### Modal state');
expect(result).toContainTextContent(`- Ran Playwright code: expect(result).toContainTextContent(`### Ran Playwright code
\`\`\`js \`\`\`js
// <internal code to handle "alert" dialog> // <internal code to handle "alert" dialog>
\`\`\` \`\`\`
### Page state
- Page URL: ${server.PREFIX} - Page URL: ${server.PREFIX}
- Page Title: - Page Title:
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- button "Button"`); - button "Button"`);
}); });

View File

@ -44,12 +44,7 @@ test('list first tab', async ({ client }) => {
}); });
test('create new tab', async ({ client }) => { test('create new tab', async ({ client }) => {
expect(await createTab(client, 'Tab one', 'Body one')).toHaveTextContent(` expect(await createTab(client, 'Tab one', 'Body one')).toContainTextContent(`
- Ran Playwright code:
\`\`\`js
// <internal code to open a new tab>
\`\`\`
### Open tabs ### Open tabs
- 0: [] (about:blank) - 0: [] (about:blank)
- 1: (current) [Tab one] (data:text/html,<title>Tab one</title><body>Body one</body>) - 1: (current) [Tab one] (data:text/html,<title>Tab one</title><body>Body one</body>)
@ -57,17 +52,12 @@ test('create new tab', async ({ client }) => {
### Current tab ### Current tab
- Page URL: data:text/html,<title>Tab one</title><body>Body one</body> - Page URL: data:text/html,<title>Tab one</title><body>Body one</body>
- Page Title: Tab one - Page Title: Tab one
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Body one - generic [active] [ref=e1]: Body one
\`\`\``); \`\`\``);
expect(await createTab(client, 'Tab two', 'Body two')).toHaveTextContent(` expect(await createTab(client, 'Tab two', 'Body two')).toContainTextContent(`
- Ran Playwright code:
\`\`\`js
// <internal code to open a new tab>
\`\`\`
### Open tabs ### Open tabs
- 0: [] (about:blank) - 0: [] (about:blank)
- 1: [Tab one] (data:text/html,<title>Tab one</title><body>Body one</body>) - 1: [Tab one] (data:text/html,<title>Tab one</title><body>Body one</body>)
@ -76,7 +66,7 @@ test('create new tab', async ({ client }) => {
### Current tab ### Current tab
- Page URL: data:text/html,<title>Tab two</title><body>Body two</body> - Page URL: data:text/html,<title>Tab two</title><body>Body two</body>
- Page Title: Tab two - Page Title: Tab two
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Body two - generic [active] [ref=e1]: Body two
\`\`\``); \`\`\``);
@ -91,7 +81,7 @@ test('select tab', async ({ client }) => {
index: 1, index: 1,
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// <internal code to select tab 1> // <internal code to select tab 1>
\`\`\` \`\`\`
@ -104,7 +94,7 @@ test('select tab', async ({ client }) => {
### Current tab ### Current tab
- Page URL: data:text/html,<title>Tab one</title><body>Body one</body> - Page URL: data:text/html,<title>Tab one</title><body>Body one</body>
- Page Title: Tab one - Page Title: Tab one
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Body one - generic [active] [ref=e1]: Body one
\`\`\``); \`\`\``);
@ -119,7 +109,7 @@ test('close tab', async ({ client }) => {
index: 2, index: 2,
}, },
})).toHaveTextContent(` })).toHaveTextContent(`
- Ran Playwright code: ### Ran Playwright code
\`\`\`js \`\`\`js
// <internal code to close tab 2> // <internal code to close tab 2>
\`\`\` \`\`\`
@ -131,7 +121,7 @@ test('close tab', async ({ client }) => {
### Current tab ### Current tab
- Page URL: data:text/html,<title>Tab one</title><body>Body one</body> - Page URL: data:text/html,<title>Tab one</title><body>Body one</body>
- Page Title: Tab one - Page Title: Tab one
- Page Snapshot - Page Snapshot:
\`\`\`yaml \`\`\`yaml
- generic [active] [ref=e1]: Body one - generic [active] [ref=e1]: Body one
\`\`\``); \`\`\``);