Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →page.on(eventName, handler) attaches a persistent listener to a Playwright Page. Use it when a test must observe events such as requests, responses, console messages, dialogs, popups, downloads, or page errors. Use page.once() or page.waitForEvent() for one occurrence, and remove long-lived listeners with the same function reference via page.removeListener().
This guide shows the correct timing, payloads, cleanup, network lifecycle, dialog safety, popup and download patterns, troubleshooting, and when event observation should be replaced with request routing.
What page.on() does
A Playwright Page represents a browser tab (and, in Chromium extensions, a background page). It emits named events. The JavaScript API follows Node’s EventEmitter style:
page.on('eventName', handler);
The handler remains active until the page is closed or you remove it. The event name determines both when the callback runs and which object it receives. The complete, release-specific list is in the Playwright Page API reference.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minute#1 Best Overall
Persistent, one-time, and removable listeners
function logRequest(request) {
console.log('A request was made:', request.url());
}
page.on('request', logRequest);
// Later, remove exactly this function reference.
page.removeListener('request', logRequest);
// For a single future occurrence:
page.once('load', () => console.log('The next load completed'));
An inline arrow function cannot be removed later unless you saved it in a variable. Prefer named functions for listeners that live longer than one action.
Choose the event that matches the signal
| Need | Event | Payload | What it tells you |
|---|---|---|---|
| Page lifecycle | load, domcontentloaded, close |
Usually the page or no argument | Document milestones or tab closure |
| Browser console output | console |
ConsoleMessage |
A page called a console method |
| Uncaught page exception | pageerror |
Error |
JavaScript escaped the page without being caught |
| JavaScript dialog | dialog |
Dialog |
alert, confirm, or prompt |
| New tab or window | popup |
Page |
A page opened by this page |
| File download | download |
Download |
A download was created |
| Outgoing network request | request |
Request |
The page issued a request |
| Response headers and status | response |
Response |
A server response arrived |
| Body finished downloading | requestfinished |
Request |
A request completed successfully |
| Transport failure | requestfailed |
Request |
DNS, connection, TLS, or other network failure |
Events are observational. A Request supplied to a request listener is read-only; it does not let you change headers or the response.
Network event handlers
Log requests and responses
page.on('request', request => {
console.log('→', request.method(), request.url());
});
page.on('response', response => {
console.log('←', response.status(), response.url());
});
page.on('requestfailed', request => {
console.error('✕', request.url(), request.failure()?.errorText);
});
For a successful request, Playwright reports request, then response when status and headers arrive, then requestfinished after the body downloads. A transport failure produces requestfailed instead of successful completion. An HTTP 404 or 503 is still a response; inspect response.status() rather than expecting requestfailed.
Observe versus intercept
Use listeners to log, measure, or assert what happened. To modify, fulfill, or abort a request, use page.route() or browserContext.route():
await page.route('**/api/**', async route => {
const request = route.request();
if (request.method() === 'GET') {
await route.continue();
} else {
await route.abort();
}
});
Once routing matches a request, your handler must explicitly continue, fulfill, or abort it. Routing changes behavior; page.on('request') does not.
Rank #2
Dialogs: always resolve them
A dialog listener must call accept() or dismiss(). An unresolved dialog can block clicks and navigation.
page.on('dialog', async dialog => {
console.log(dialog.type(), dialog.message());
if (dialog.type() === 'prompt') {
await dialog.accept('value supplied by the test');
} else {
await dialog.accept();
}
});
If neither the page nor its browser context has a dialog listener, Playwright automatically dismisses dialogs. Register a listener only when the test needs to inspect or control the dialog.
Popups and downloads: wait before the action
For a one-off event caused by a click, create the promise first. Waiting after the click can miss a fast event.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Popup
const popupPromise = page.waitForEvent('popup');
await page.getByText('Open popup').click();
const popup = await popupPromise;
await popup.waitForLoadState();
console.log('Popup URL:', popup.url());
A popup becomes available when it has navigated to its initial URL and begun receiving a response. If you must observe or route that initial request, attach listeners or routing at the browser-context level.
Download
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export' }).click();
const download = await downloadPromise;
await download.saveAs('artifacts/export.csv');
Use a test-specific path and check download.failure() when diagnosing a missing file.
Console messages and page errors
console and pageerror answer different debugging questions:
page.on('console', message => {
console.log(`[browser:${message.type()}]`, message.text());
});
page.on('pageerror', error => {
console.error('Uncaught page exception:', error.message);
});
A console message is an intentional call such as console.log or console.error. A page error is an uncaught exception. Do not treat ordinary console output as proof that the page threw an exception.
Lifecycle and frame-related listeners
Lifecycle events help diagnose navigation timing:
page.on('domcontentloaded', () => console.log('DOM parsed'));
page.on('load', () => console.log('Subresources loaded'));
page.on('close', () => console.log('Tab closed'));
page.on('frameattached', frame => console.log('Attached:', frame.url()));
page.on('framedetached', frame => console.log('Detached:', frame.url()));
page.on('framenavigated', frame => console.log('Navigated:', frame.url()));
Choose the milestone your assertion needs. load does not mean that every later API call or lazy resource has completed.
A maintainable listener pattern
- Register early. Attach persistent diagnostics immediately after creating the page, before navigation.
- Use narrow handlers. Filter by URL, method, message type, or frame instead of logging every event in a large suite.
- Keep handlers non-blocking. If asynchronous work is required, await it deliberately and avoid unbounded queues.
- Clean up. Remove temporary listeners after the assertion, especially when reusing a page in a worker.
- Fail intentionally. Throwing inside an event callback can produce confusing asynchronous failures; capture the relevant data and make the test assertion in the main flow when practical.
function onApiResponse(response) {
if (response.url().includes('/api/orders')) {
console.log('Orders status:', response.status());
}
}
page.on('response', onApiResponse);
try {
await page.getByRole('button', { name: 'Refresh orders' }).click();
} finally {
page.removeListener('response', onApiResponse);
}
Common problems and fixes
The event never fires
- Confirm the exact event name and register before navigation or the triggering action.
- For a popup or download, use
waitForEvent()before clicking. - Check that the action really targets this page; a popup may be owned by another page in the context.
The test hangs after an alert
Your dialog handler probably never resolved the dialog. Call accept() or dismiss() on every branch, or remove the listener to restore Playwright’s automatic dismissal.
A 404 appears as a network failure
It should not. Log response.status() for HTTP errors. Reserve requestfailed for transport-level failures.
Rank #4
Changing a request in page.on('request') does nothing
Listeners cannot mutate requests. Move the logic to page.route() or browserContext.route(), and ensure every routed request is continued, fulfilled, or aborted.
Duplicate log lines appear
The same listener may have been registered repeatedly, or a page is reused across tests. Keep one function reference, remove it in cleanup, and avoid registering inside loops unless that is intentional.
A newer event is unavailable
Event availability depends on the installed Playwright release. The API reference marks consoleMessages as added in v1.56 and dialogclosed in v1.63; verify your project’s version before using them.
Performance, reliability, and test design
Event callbacks run while Playwright processes browser activity. Logging every request and response can create substantial output and slow a suite, so filter URLs and disable verbose listeners outside diagnosis. Prefer a one-off waitForEvent() for a single expected signal; reserve persistent page.on() listeners for cross-step diagnostics or assertions.
Do not use event timing as a substitute for a condition the user actually needs. For UI readiness, a locator assertion or explicit application signal is usually more stable than assuming that load means all asynchronous work is finished. Set realistic timeouts on event waits and include the URL or action in timeout diagnostics.
Best Value
Or skip the browser setup
If your goal is simply a clean website image rather than browser-event testing, ScreenshotNeo provides a single screenshot request. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for all options, including full-page and element captures, device and retina settings, PDF output, custom scripts, request blocking, cookies, headers, geolocation, caching, signed links, asynchronous jobs, bulk capture, and the usage API. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can I register the same Playwright event more than once?
Yes. Each registration runs independently, so repeated setup can produce duplicate callbacks. Store and remove handlers deliberately when a page is reused.
Should I use page.on(‘response’) to assert an API returned 200?
You can inspect response.status(), but make the assertion in the main test flow or use a targeted wait so the failure is tied clearly to the action that triggered the API call.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWhat is the difference between page.once() and page.waitForEvent()?
once() installs a callback for the next event. waitForEvent() returns a promise that you can await and coordinate with the action producing the event.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

