How to Read Console Errors with Playwright
Capture Playwright console errors, uncaught exceptions, failed requests, and HTTP errors, then connect each message to the test action that caused it.
Playwright can expose several different kinds of browser failures. Use page.on('console') for messages written by page JavaScript, page.on('pageerror') for uncaught exceptions, and page.on('requestfailed') for requests that never receive an HTTP response. A 404 or 503 is an HTTP response, so inspect its status separately.
1. Capture console errors before navigation or interaction
Register listeners before the action you are investigating. This preserves messages emitted during page startup, redirects, hydration, and user interactions.
import { test } from '@playwright/test';
test('collect browser diagnostics', async ({ page }) => {
page.on('console', msg => {
if (msg.type() === 'error') {
console.error(`[browser console] ${msg.text()}`);
}
});
page.on('pageerror', error => {
console.error(`[uncaught page exception] ${error.message}`);
});
page.on('requestfailed', request => {
console.error(
`[request failed] ${request.url()} ${request.failure()?.errorText ?? ''}`
);
});
page.on('response', response => {
if (response.status() >= 400) {
console.error(`[HTTP ${response.status()}] ${response.url()}`);
}
});
await page.goto('https://example.com');
await page.getByRole('button', { name: 'Load data' }).click();
});
The console event fires when page JavaScript calls a console API method. Check msg.type() for values such as error, warning, log, info, and debug, then read msg.text() for the rendered message. The Page API documents the event and message methods.
Read structured console arguments
msg.text() is useful for logs, but it can lose object structure. Use msg.args() when the page logs multiple values or objects.
page.on('console', async msg => {
const values = [];
for (const arg of msg.args()) {
values.push(await arg.jsonValue().catch(() => '[unserializable]'));
}
console.log({ type: msg.type(), text: msg.text(), values });
});
Some browser objects cannot be converted to JSON. Keep the fallback and use msg.text() as the reliable summary.
2. Distinguish console messages, page exceptions, and network failures
| Signal | Event or API | What it means | Typical next step |
|---|---|---|---|
| Console call | page.on('console') |
Page code called console.error(), console.warn(), or another console method. |
Inspect msg.type(), msg.text(), and msg.args(). |
| Uncaught exception | page.on('pageerror') |
A JavaScript exception escaped page code without being caught. | Inspect the error message and stack; correlate it with the action that ran. |
| Transport failure | page.on('requestfailed') |
The request could not obtain an HTTP response, such as from a DNS, connection, or TLS failure. | Log request.url() and request.failure()?.errorText. |
| HTTP error status | page.on('response') |
The server returned a response with a 4xx or 5xx status. | Inspect response.status(), headers, and the response URL. |
A failed HTTP status does not trigger requestfailed. Playwright treats 404 and 503 responses as completed HTTP requests; the request can finish with requestfinished. The Request API explains this distinction.
Capture only errors
page.on('console', msg => {
if (msg.type() === 'error') console.error(msg.text());
});
For a broader diagnostic record, keep warnings and informational messages too. A warning immediately before an error often identifies the failing feature or missing configuration.
3. Retrieve recent messages after a test action
Recent Playwright versions provide bounded history APIs:
await page.goto('https://example.com');
await page.getByRole('button', { name: 'Save' }).click();
const consoleMessages = page.consoleMessages();
const pageErrors = page.pageErrors();
console.log(consoleMessages.map(msg => ({ type: msg.type(), text: msg.text() })));
console.log(pageErrors.map(error => ({ message: error.message, stack: error.stack })));
page.consoleMessages() and page.pageErrors() were added in Playwright v1.56. The all and since-navigation filtering options were added in v1.59. The buffers contain up to 200 entries each, so use live listeners when you must retain a complete stream or when a page is especially noisy. Check the version of the Page API installed in your project before using these methods.
Use a navigation-scoped history
const messages = page.consoleMessages({ since: 'since-navigation' });
const errors = page.pageErrors({ since: 'since-navigation' });
If your installed version does not support the filter, clear your own arrays after each navigation and continue using event listeners.
4. Monitor every page in a browser context
Use page listeners when one page matters. For popups, multiple tabs, or pages created by the application, attach listeners to the browser context.
const context = await browser.newContext();
context.on('console', msg => {
console.log(`[${msg.page().url()}] ${msg.type()}: ${msg.text()}`);
});
context.on('weberror', webError => {
console.error(`[${webError.page().url()}] ${webError.error().message}`);
});
const page = await context.newPage();
await page.goto('https://example.com');
The BrowserContext API provides context-wide console and web-error events. Include the page URL in your log so messages from a popup are not confused with the main page.
5. Connect an error to the test action that caused it
Console output alone rarely identifies the responsible click or navigation. Record an action label with each message, or use Playwright’s trace tooling.
async function withAction(label, action) {
console.log(`ACTION START: ${label}`);
try {
await action();
} finally {
console.log(`ACTION END: ${label}`);
}
}
await withAction('open checkout', () => page.getByRole('link', { name: 'Checkout' }).click());
await withAction('submit payment form', () => page.getByRole('button', { name: 'Pay' }).click());
For a post-run investigation, enable tracing and open the result in Trace Viewer. Select an action to filter its console output, then inspect the action log, source, and related network activity. See the Trace Viewer guide and Playwright debugging guide.
await context.tracing.start({ screenshots: true, snapshots: true });
// run the actions under investigation
await context.tracing.stop({ path: 'trace.zip' });
Trace Viewer is useful when an error appears only after a particular click, redirect, or asynchronous request. Browser messages and test-file logs are separate streams, which helps you determine whether the failure occurred in page code or in the test runner.
6. Inspect errors interactively
For live inspection, run Playwright with PWDEBUG=console, pause at a useful point, and use browser developer tools.
PWDEBUG=console npx playwright test tests/checkout.spec.ts
await page.pause();
UI Mode also exposes console and network inspection, including request and response details. Open it with npx playwright test --ui; see the UI Mode documentation.
7. A complete diagnostic fixture
A fixture keeps the same instrumentation across tests and writes a compact record for CI.
import { test as base } from '@playwright/test';
export const test = base.extend({
page: async ({ page }, use, testInfo) => {
const diagnostics = [];
page.on('console', msg => {
diagnostics.push({ kind: 'console', type: msg.type(), text: msg.text(), url: page.url() });
});
page.on('pageerror', error => {
diagnostics.push({ kind: 'pageerror', message: error.message, stack: error.stack, url: page.url() });
});
page.on('requestfailed', request => {
diagnostics.push({ kind: 'requestfailed', url: request.url(), error: request.failure()?.errorText ?? '' });
});
page.on('response', response => {
if (response.status() >= 400) {
diagnostics.push({ kind: 'http', status: response.status(), url: response.url() });
}
});
await use(page);
await testInfo.attach('browser-diagnostics', {
body: JSON.stringify(diagnostics, null, 2),
contentType: 'application/json'
});
}
});
Keep the listener registration before use(page). The fixture then captures startup errors as well as errors produced by the test body.
8. Troubleshooting common problems
| Symptom | Cause | Fix |
|---|---|---|
| No console errors appear | The listener was attached after navigation or after the failing action. | Register it immediately after creating the page, before goto() or interaction. |
A 404 is missing from requestfailed |
404 is an HTTP response, not a transport failure. | Listen for response and check response.status(). |
| The message is incomplete | Objects and multiple arguments are being flattened into text. | Iterate over msg.args() and call jsonValue() where possible. |
| Only one tab is recorded | Listeners were attached to one page while the app opened another. | Use browserContext.on('console') and context.on('weberror'). |
| Recent history is empty | The installed Playwright version predates the history APIs, or the 200-entry buffer was overwritten. | Upgrade if appropriate, or use live listeners and store your own array. |
| The error appears only in CI | Timing, browser engine, environment variables, or external requests differ. | Save a trace, include URL and status in logs, and compare the failing action in Trace Viewer. |
| Network failure has no useful text | The browser supplied no detailed transport reason. | Log the URL, failure object, and surrounding action; reproduce with debug mode or UI Mode. |
9. Reliability, performance, and log hygiene
- Attach listeners once per page or context. Re-registering them inside loops can duplicate every message.
- Filter noisy categories in CI, but retain the original URL and action label for errors.
- Do not assume every browser warning is identical across Chromium, Firefox, and WebKit; engine behavior and wording can differ.
- Limit serialized console arguments and truncate very large values before uploading artifacts.
- Use trace capture selectively because screenshots, snapshots, and network data increase artifact size and runtime.
- For high-volume suites, write newline-delimited JSON or attach one summary artifact per test instead of printing every debug log to standard output.
10. Or skip the browser setup
If your goal is a clean visual record of a page rather than a Playwright debugging session, ScreenshotNeo provides a single screenshot API request. Its capture flow accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for all options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also supports full-page capture, CSS selector element capture, dark mode, device presets, custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs, webhooks, bulk capture, a usage API, an OpenAPI specification, and an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
There are 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
11. FAQ
Does console.error() fail a Playwright test?
No. It emits a browser console message. Make the test fail explicitly if a console error should be considered a test failure.
Should I use pageerror or console?
Use pageerror for uncaught JavaScript exceptions and console for messages deliberately written through the console API. Many diagnostics need both.
Can I capture messages from a popup?
Yes. Attach listeners to the browser context, or wait for the popup and attach page-level listeners before its navigation completes.
How much history does Playwright retain?
The Page API’s recent console-message and page-error buffers retain up to 200 entries each. Live listeners are safer for complete records.
What should I inspect for a 503?
Inspect the response status, URL, headers, and body separately. A 503 is an HTTP response and does not by itself produce requestfailed.


