Skip to content

Commit dae228f

Browse files
Merge pull request #115 from charrywong/charry
feat: capture uncaught exceptions and unhandled Promise rejections in browser logs
2 parents 8b4850a + 94d53fb commit dae228f

File tree

5 files changed

+132
-23
lines changed

5 files changed

+132
-23
lines changed

docs/docs/playwright-web/Supported-Tools.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ Execute JavaScript in the browser console.
209209

210210
### Playwright_console_logs
211211
Retrieve console logs from the browser with filtering options
212-
Supports Retrieval of logs like - all, error, warning, log, info, debug
212+
Supports Retrieval of logs like - all, error, warning, log, info, debug, exception
213213

214214
- **`search`** *(string)*:
215215
Text to search for in logs (handles text with square brackets).
@@ -218,7 +218,7 @@ Supports Retrieval of logs like - all, error, warning, log, info, debug
218218
Maximum number of logs to retrieve.
219219

220220
- **`type`** *(string)*:
221-
Type of logs to retrieve (all, error, warning, log, info, debug).
221+
Type of logs to retrieve (all, error, warning, log, info, debug, exception).
222222

223223
- **`clear`** *(boolean)*:
224224
Whether to clear logs after retrieval (default: false).

docs/docs/release.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ are supported.[More Detail available here](/docs/playwright-web/Console-Logging)
7373
- `warn`
7474
- `error`
7575
- `debug`
76+
- `exception`
7677
- `all`
7778

7879

src/__tests__/toolHandler.test.ts

+81-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { handleToolCall, getConsoleLogs, getScreenshots } from '../toolHandler.js';
1+
import { handleToolCall, getConsoleLogs, getScreenshots, registerConsoleMessage } from '../toolHandler.js';
22
import { Browser, Page, chromium, firefox, webkit } from 'playwright';
33
import { jest } from '@jest/globals';
44

@@ -49,7 +49,8 @@ jest.mock('playwright', () => {
4949
on: mockOn,
5050
frames: mockFrames,
5151
locator: mockLocator,
52-
isClosed: mockIsClosed
52+
isClosed: mockIsClosed,
53+
addInitScript: jest.fn()
5354
};
5455

5556
const mockNewPage = jest.fn().mockImplementation(() => Promise.resolve(mockPage));
@@ -290,4 +291,81 @@ describe('Tool Handler', () => {
290291
const screenshots = getScreenshots();
291292
expect(screenshots instanceof Map).toBe(true);
292293
});
293-
});
294+
295+
describe('registerConsoleMessage', () => {
296+
let mockPage: any;
297+
298+
beforeEach(() => {
299+
mockPage = {
300+
on: jest.fn(),
301+
addInitScript: jest.fn()
302+
};
303+
304+
// clean console logs
305+
const logs = getConsoleLogs();
306+
logs.length = 0;
307+
});
308+
309+
test('should handle console messages of different types', async () => {
310+
await handleToolCall('playwright_navigate', { url: 'about:blank' }, mockServer);
311+
312+
// Setup mock handlers
313+
const mockHandlers: Record<string, jest.Mock> = {};
314+
mockPage.on.mockImplementation((event: string, handler: (arg: any) => void) => {
315+
mockHandlers[event] = jest.fn(handler);
316+
});
317+
318+
await registerConsoleMessage(mockPage);
319+
320+
// Test log message
321+
mockHandlers['console']({
322+
type: jest.fn().mockReturnValue('log'),
323+
text: jest.fn().mockReturnValue('test log message')
324+
});
325+
326+
// Test error message
327+
mockHandlers['console']({
328+
type: jest.fn().mockReturnValue('error'),
329+
text: jest.fn().mockReturnValue('test error message')
330+
});
331+
332+
// Test page error
333+
const mockError = new Error('test error');
334+
mockError.stack = 'test stack';
335+
mockHandlers['pageerror'](mockError);
336+
337+
const logs = getConsoleLogs();
338+
expect(logs).toEqual([
339+
'[log] test log message',
340+
'[error] test error message',
341+
'[exception] test error\ntest stack'
342+
]);
343+
});
344+
345+
test('should handle unhandled promise rejection with detailed info', async () => {
346+
await handleToolCall('playwright_navigate', { url: 'about:blank' }, mockServer);
347+
348+
mockPage.on.mockImplementation((event: string, handler: (arg: any) => void) => {
349+
if (event === 'console') {
350+
handler({
351+
type: jest.fn().mockReturnValue('error'),
352+
text: jest.fn().mockReturnValue(
353+
'[Playwright][Unhandled Rejection In Promise] test rejection\n' +
354+
'Error: Something went wrong\n' +
355+
' at test.js:10:15'
356+
)
357+
});
358+
}
359+
});
360+
361+
await registerConsoleMessage(mockPage);
362+
363+
const logs = getConsoleLogs();
364+
expect(logs).toEqual([
365+
'[exception] [Unhandled Rejection In Promise] test rejection\n' +
366+
'Error: Something went wrong\n' +
367+
' at test.js:10:15'
368+
]);
369+
});
370+
});
371+
});

src/toolHandler.ts

+46-16
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,46 @@ interface BrowserSettings {
108108
browserType?: 'chromium' | 'firefox' | 'webkit';
109109
}
110110

111+
async function registerConsoleMessage(page) {
112+
page.on("console", (msg) => {
113+
if (consoleLogsTool) {
114+
const type = msg.type();
115+
const text = msg.text();
116+
117+
// "Unhandled Rejection In Promise" we injected
118+
if (text.startsWith("[Playwright]")) {
119+
const payload = text.replace("[Playwright]", "");
120+
consoleLogsTool.registerConsoleMessage("exception", payload);
121+
} else {
122+
consoleLogsTool.registerConsoleMessage(type, text);
123+
}
124+
}
125+
});
126+
127+
// Uncaught exception
128+
page.on("pageerror", (error) => {
129+
if (consoleLogsTool) {
130+
const message = error.message;
131+
const stack = error.stack || "";
132+
consoleLogsTool.registerConsoleMessage("exception", `${message}\n${stack}`);
133+
}
134+
});
135+
136+
// Unhandled rejection in promise
137+
await page.addInitScript(() => {
138+
window.addEventListener("unhandledrejection", (event) => {
139+
const reason = event.reason;
140+
const message = typeof reason === "object" && reason !== null
141+
? reason.message || JSON.stringify(reason)
142+
: String(reason);
143+
144+
const stack = reason?.stack || "";
145+
// Use console.error get "Unhandled Rejection In Promise"
146+
console.error(`[Playwright][Unhandled Rejection In Promise] ${message}\n${stack}`);
147+
});
148+
});
149+
}
150+
111151
/**
112152
* Ensures a browser is launched and returns the page
113153
*/
@@ -178,11 +218,7 @@ async function ensureBrowser(browserSettings?: BrowserSettings) {
178218
page = await context.newPage();
179219

180220
// Register console message handler
181-
page.on("console", (msg) => {
182-
if (consoleLogsTool) {
183-
consoleLogsTool.registerConsoleMessage(msg.type(), msg.text());
184-
}
185-
});
221+
await registerConsoleMessage(page);
186222
}
187223

188224
// Verify page is still valid
@@ -193,11 +229,7 @@ async function ensureBrowser(browserSettings?: BrowserSettings) {
193229
page = await context.newPage();
194230

195231
// Re-register console message handler
196-
page.on("console", (msg) => {
197-
if (consoleLogsTool) {
198-
consoleLogsTool.registerConsoleMessage(msg.type(), msg.text());
199-
}
200-
});
232+
await registerConsoleMessage(page);
201233
}
202234

203235
return page!;
@@ -252,11 +284,7 @@ async function ensureBrowser(browserSettings?: BrowserSettings) {
252284

253285
page = await context.newPage();
254286

255-
page.on("console", (msg) => {
256-
if (consoleLogsTool) {
257-
consoleLogsTool.registerConsoleMessage(msg.type(), msg.text());
258-
}
259-
});
287+
await registerConsoleMessage(page);
260288

261289
return page!;
262290
}
@@ -584,4 +612,6 @@ export function getConsoleLogs(): string[] {
584612
*/
585613
export function getScreenshots(): Map<string, string> {
586614
return screenshotTool?.getScreenshots() ?? new Map();
587-
}
615+
}
616+
617+
export { registerConsoleMessage };

src/tools.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ export function createToolDefinitions() {
187187
properties: {
188188
type: {
189189
type: "string",
190-
description: "Type of logs to retrieve (all, error, warning, log, info, debug)",
191-
enum: ["all", "error", "warning", "log", "info", "debug"]
190+
description: "Type of logs to retrieve (all, error, warning, log, info, debug, exception)",
191+
enum: ["all", "error", "warning", "log", "info", "debug", "exception"]
192192
},
193193
search: {
194194
type: "string",

0 commit comments

Comments
 (0)