From 5bfff0a0598f77d8a40643580dccaea91f6d37cd Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 17 Jul 2025 14:58:44 -0700 Subject: [PATCH] chore: include recent console logs in results (#689) --- src/context.ts | 27 ++++++++++++++++++++------- src/pageSnapshot.ts | 2 +- src/tab.ts | 17 +++++++++++++++-- tests/cdp.spec.ts | 5 +++-- tests/click.spec.ts | 10 ++++++---- tests/console.spec.ts | 28 ++++++++++++++++++++++++++++ tests/core.spec.ts | 17 ++++++++++------- tests/dialogs.spec.ts | 22 ++++++++++++---------- tests/tabs.spec.ts | 26 ++++++++------------------ 9 files changed, 103 insertions(+), 51 deletions(-) diff --git a/src/context.ts b/src/context.ts index d252291..18fcc71 100644 --- a/src/context.ts +++ b/src/context.ts @@ -161,14 +161,13 @@ export class Context { }); const result: string[] = []; - result.push(`- Ran Playwright code: + result.push(`### Ran Playwright code \`\`\`js ${code.join('\n')} -\`\`\` -`); +\`\`\``); if (this.modalStates().length) { - result.push(...this.modalStatesMarkdown()); + result.push('', ...this.modalStatesMarkdown()); return { content: [{ 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) { result.push('', '### Downloads'); for (const entry of this._downloads) { @@ -185,15 +191,16 @@ ${code.join('\n')} else result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`); } - result.push(''); } if (captureSnapshot && tab.hasSnapshot()) { if (this.tabs().length > 1) - result.push(await this.listTabsMarkdown(), ''); + result.push('', await this.listTabsMarkdown()); if (this.tabs().length > 1) - result.push('### Current tab'); + result.push('', '### Current tab'); + else + result.push('', '### Page state'); result.push( `- Page URL: ${tab.page.url()}`, @@ -346,3 +353,9 @@ ${code.join('\n')} return result; } } + +function trim(text: string, maxLength: number) { + if (text.length <= maxLength) + return text; + return text.slice(0, maxLength) + '...'; +} diff --git a/src/pageSnapshot.ts b/src/pageSnapshot.ts index 5f2f07d..85f2587 100644 --- a/src/pageSnapshot.ts +++ b/src/pageSnapshot.ts @@ -42,7 +42,7 @@ export class PageSnapshot { private async _build() { const snapshot = await callOnPageNoTrace(this._page, page => (page as PageEx)._snapshotForAI()); this._text = [ - `- Page Snapshot`, + `- Page Snapshot:`, '```yaml', snapshot, '```', diff --git a/src/tab.ts b/src/tab.ts index d16f3ca..91746bd 100644 --- a/src/tab.ts +++ b/src/tab.ts @@ -26,6 +26,7 @@ export class Tab { readonly context: Context; readonly page: playwright.Page; private _consoleMessages: ConsoleMessage[] = []; + private _recentConsoleMessages: ConsoleMessage[] = []; private _requests: Map = new Map(); private _snapshot: PageSnapshot | undefined; private _onPageClose: (tab: Tab) => void; @@ -34,8 +35,8 @@ export class Tab { this.context = context; this.page = page; this._onPageClose = onPageClose; - page.on('console', event => this._consoleMessages.push(messageToConsoleMessage(event))); - page.on('pageerror', error => this._consoleMessages.push(pageErrorToConsoleMessage(error))); + page.on('console', event => this._handleConsoleMessage(messageToConsoleMessage(event))); + page.on('pageerror', error => this._handleConsoleMessage(pageErrorToConsoleMessage(error))); page.on('request', request => this._requests.set(request, null)); page.on('response', response => this._requests.set(response.request(), response)); page.on('close', () => this._onClose()); @@ -56,9 +57,15 @@ export class Tab { private _clearCollectedArtifacts() { this._consoleMessages.length = 0; + this._recentConsoleMessages.length = 0; this._requests.clear(); } + private _handleConsoleMessage(message: ConsoleMessage) { + this._consoleMessages.push(message); + this._recentConsoleMessages.push(message); + } + private _onClose() { this._clearCollectedArtifacts(); this._onPageClose(this); @@ -119,6 +126,12 @@ export class Tab { async captureSnapshot() { this._snapshot = await PageSnapshot.create(this.page); } + + takeRecentConsoleMessages(): ConsoleMessage[] { + const result = this._recentConsoleMessages.slice(); + this._recentConsoleMessages.length = 0; + return result; + } } export type ConsoleMessage = { diff --git a/tests/cdp.spec.ts b/tests/cdp.spec.ts index 32fb61c..57ce17a 100644 --- a/tests/cdp.spec.ts +++ b/tests/cdp.spec.ts @@ -46,14 +46,15 @@ test('cdp server reuse tab', async ({ cdpServer, startClient, server }) => { expect(await client.callTool({ name: 'browser_snapshot', })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // \`\`\` +### Page state - Page URL: ${server.HELLO_WORLD} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Hello, world! \`\`\` diff --git a/tests/click.spec.ts b/tests/click.spec.ts index b0132e3..4548375 100644 --- a/tests/click.spec.ts +++ b/tests/click.spec.ts @@ -34,15 +34,16 @@ test('browser_click', async ({ client, server, mcpBrowser }) => { ref: 'e2', }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // Click Submit button await page.getByRole('button', { name: 'Submit' }).click(); \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - button "Submit" ${mcpBrowser !== 'webkit' || process.platform === 'linux' ? '[active] ' : ''}[ref=e2] \`\`\` @@ -73,15 +74,16 @@ test('browser_click (double)', async ({ client, server }) => { doubleClick: true, }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // Double click Click me await page.getByRole('heading', { name: 'Click me' }).dblclick(); \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - heading "Double clicked" [level=1] [ref=e3] \`\`\` diff --git a/tests/console.spec.ts b/tests/console.spec.ts index 113f397..e94ecf7 100644 --- a/tests/console.spec.ts +++ b/tests/console.spec.ts @@ -66,3 +66,31 @@ test('browser_console_messages (page error)', async ({ client, server }) => { expect(resource).toHaveTextContent(/Error: Error in script/); expect(resource).toHaveTextContent(new RegExp(server.PREFIX)); }); + +test('recent console messages', async ({ client, server }) => { + server.setContent('/', ` + + + + + `, '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! @`); +}); diff --git a/tests/core.spec.ts b/tests/core.spec.ts index 091c5da..34b398b 100644 --- a/tests/core.spec.ts +++ b/tests/core.spec.ts @@ -21,15 +21,16 @@ test('browser_navigate', async ({ client, server }) => { name: 'browser_navigate', arguments: { url: server.HELLO_WORLD }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // Navigate to ${server.HELLO_WORLD} await page.goto('${server.HELLO_WORLD}'); \`\`\` +### Page state - Page URL: ${server.HELLO_WORLD} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Hello, world! \`\`\` @@ -59,15 +60,16 @@ test('browser_select_option', async ({ client, server }) => { values: ['bar'], }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // Select options [bar] in Select await page.getByRole('combobox').selectOption(['bar']); \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - combobox [ref=e2]: - option "Foo" @@ -99,15 +101,16 @@ test('browser_select_option (multiple)', async ({ client, server }) => { values: ['bar', 'baz'], }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // Select options [bar, baz] in Select await page.getByRole('listbox').selectOption(['bar', 'baz']); \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: Title -- Page Snapshot +- Page Snapshot: \`\`\`yaml - listbox [ref=e2]: - option "Foo" [ref=e3] @@ -196,7 +199,7 @@ test('browser_resize', async ({ client, server }) => { height: 780, }, }); - expect(response).toContainTextContent(`- Ran Playwright code: + expect(response).toContainTextContent(`### Ran Playwright code \`\`\`js // Resize browser window to 390x780 await page.setViewportSize({ width: 390, height: 780 }); diff --git a/tests/dialogs.spec.ts b/tests/dialogs.spec.ts index ecf7d04..15cfb8a 100644 --- a/tests/dialogs.spec.ts +++ b/tests/dialogs.spec.ts @@ -29,7 +29,7 @@ test('alert dialog', async ({ client, server }) => { element: 'Button', ref: 'e2', }, - })).toHaveTextContent(`- Ran Playwright code: + })).toHaveTextContent(`### Ran Playwright code \`\`\`js // Click Button 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).toContainTextContent(`- Ran Playwright code: + expect(result).toContainTextContent(`### Ran Playwright code \`\`\`js // \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: -- Page Snapshot +- Page Snapshot: \`\`\`yaml - button "Button"`); }); @@ -77,7 +78,7 @@ test('two alert dialogs', async ({ client, server }) => { element: 'Button', ref: 'e2', }, - })).toHaveTextContent(`- Ran Playwright code: + })).toHaveTextContent(`### Ran Playwright code \`\`\`js // Click Button 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).toContainTextContent('// '); - expect(result).toContainTextContent(`- Page Snapshot + expect(result).toContainTextContent(`- Page Snapshot: \`\`\`yaml - 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 - 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 - generic [active] [ref=e1]: Answer \`\`\``); @@ -228,7 +229,7 @@ test('alert dialog w/ race', async ({ client, server }) => { element: 'Button', ref: 'e2', }, - })).toHaveTextContent(`- Ran Playwright code: + })).toHaveTextContent(`### Ran Playwright code \`\`\`js // Click Button 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).toContainTextContent(`- Ran Playwright code: + expect(result).toContainTextContent(`### Ran Playwright code \`\`\`js // \`\`\` +### Page state - Page URL: ${server.PREFIX} - Page Title: -- Page Snapshot +- Page Snapshot: \`\`\`yaml - button "Button"`); }); diff --git a/tests/tabs.spec.ts b/tests/tabs.spec.ts index 0a1545e..174cef4 100644 --- a/tests/tabs.spec.ts +++ b/tests/tabs.spec.ts @@ -44,12 +44,7 @@ test('list first tab', async ({ client }) => { }); test('create new tab', async ({ client }) => { - expect(await createTab(client, 'Tab one', 'Body one')).toHaveTextContent(` -- Ran Playwright code: -\`\`\`js -// -\`\`\` - + expect(await createTab(client, 'Tab one', 'Body one')).toContainTextContent(` ### Open tabs - 0: [] (about:blank) - 1: (current) [Tab one] (data:text/html,Tab oneBody one) @@ -57,17 +52,12 @@ test('create new tab', async ({ client }) => { ### Current tab - Page URL: data:text/html,Tab oneBody one - Page Title: Tab one -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Body one \`\`\``); - expect(await createTab(client, 'Tab two', 'Body two')).toHaveTextContent(` -- Ran Playwright code: -\`\`\`js -// -\`\`\` - + expect(await createTab(client, 'Tab two', 'Body two')).toContainTextContent(` ### Open tabs - 0: [] (about:blank) - 1: [Tab one] (data:text/html,Tab oneBody one) @@ -76,7 +66,7 @@ test('create new tab', async ({ client }) => { ### Current tab - Page URL: data:text/html,Tab twoBody two - Page Title: Tab two -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Body two \`\`\``); @@ -91,7 +81,7 @@ test('select tab', async ({ client }) => { index: 1, }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // \`\`\` @@ -104,7 +94,7 @@ test('select tab', async ({ client }) => { ### Current tab - Page URL: data:text/html,Tab oneBody one - Page Title: Tab one -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Body one \`\`\``); @@ -119,7 +109,7 @@ test('close tab', async ({ client }) => { index: 2, }, })).toHaveTextContent(` -- Ran Playwright code: +### Ran Playwright code \`\`\`js // \`\`\` @@ -131,7 +121,7 @@ test('close tab', async ({ client }) => { ### Current tab - Page URL: data:text/html,Tab oneBody one - Page Title: Tab one -- Page Snapshot +- Page Snapshot: \`\`\`yaml - generic [active] [ref=e1]: Body one \`\`\``);