Puppeteer Page Events: Reference and Examples
Learn Puppeteer page events, listener cleanup, network lifecycle, dialogs, popups, and frames with runnable JavaScript examples.
Puppeteer page events let you observe navigation, network activity, JavaScript diagnostics, dialogs, popups, frames, and workers. Subscribe with page.on(), use page.once() for a one-time event, and remove recurring listeners with page.off() and the same callback reference. For network debugging, distinguish transport failures (requestfailed) from HTTP error statuses such as 404, which are completed responses.
The examples below use JavaScript and Puppeteer. Event names and payloads are defined by Puppeteer’s Page API and PageEvent reference. The latter is a /next page, so verify newly added or experimental events against the version installed in your project.
1. Install Puppeteer and run a page-event example
Install Puppeteer in a Node.js project:
npm install puppeteer
Save this as page-events.mjs and run node page-events.mjs. It registers listeners before navigation so early lifecycle and network events are not missed, then closes the browser even if navigation fails.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
page.on('domcontentloaded', () => console.log('DOM parsed'));
page.once('load', () => console.log('Load event fired'));
page.on('console', message => {
console.log(`[console:${message.type()}]`, message.text());
});
page.on('pageerror', error => {
console.error('Uncaught page exception:', error.message);
});
page.on('request', request => {
console.log('Request:', request.method(), request.url());
});
page.on('response', response => {
console.log('Response:', response.status(), response.url());
});
page.on('requestfailed', request => {
console.error('Transport failure:', request.url(), request.failure()?.errorText);
});
await page.goto('https://example.com', { waitUntil: 'load' });
} finally {
await browser.close();
}
domcontentloaded means the initial document was parsed; it does not mean every image or subresource finished. load is a page lifecycle event, while page.goto() also has its own navigation completion behavior. Choose the event that matches what your workflow needs rather than treating “loaded” as a single universal state.
2. Page event reference
The documented PageEvent inventory groups naturally by the question you are investigating. Payload details and availability can vary by Puppeteer version; consult the API reference for the version you run.
| Group | Events | Typical use |
|---|---|---|
| Lifecycle | domcontentloaded, load, close |
Know when the document is parsed, load fires, or the page closes. |
| JavaScript diagnostics | console, pageerror, error |
Collect console output, uncaught page exceptions, and page crashes. |
| Dialogs and new tabs | dialog, popup |
Resolve JavaScript dialogs and attach automation to an opened page. |
| Frames | frameattached, framenavigated, framedetached |
Observe iframe lifecycle and navigation. |
| Network | request, response, requestfinished, requestfailed, requestservedfromcache |
Trace request issuance, response arrival, completion, failures, and cache service. |
| Workers and diagnostics | workercreated, workerdestroyed, metrics, experimental issue |
Inspect worker lifecycle and diagnostic signals. |
The console event covers page calls to console methods and can include page errors or warnings. Use pageerror to handle uncaught exceptions as exceptions. The popup event supplies a Page for the new tab or window. Treat experimental events as version-sensitive.
3. Listener patterns and cleanup
Use a named function when you may need to remove a listener. An inline anonymous function is convenient for short-lived callbacks, but you cannot later pass the same function reference to off() unless you saved it.
function logRequest(request) {
console.log('A request was made:', request.url());
}
page.on('request', logRequest); // Every matching event
page.once('load', () => console.log('Page loaded once'));
// Later, remove just this listener:
page.off('request', logRequest);
Register a listener before the action that may trigger it. This matters for fast events such as popups and dialogs: waiting for an event after clicking can miss it if the event has already fired. If several events could arrive during a long-lived run, remove temporary listeners when their job is done to avoid duplicate handling and retained references.
4. Network events: request, response, completion, and failure
A typical request emits request when issued, then response when response headers arrive, followed by requestfinished when the response body has been downloaded. If the request fails before completion, Puppeteer emits requestfailed instead of requestfinished. A redirect completes one request and starts another for the redirected URL.
| Event | What it answers | Useful data |
|---|---|---|
request |
What did the page ask for? | URL, method, resource type, request headers. |
response |
Did a server response arrive, and what status did it return? | Status, URL, response headers. |
requestfinished |
Did the request complete and its body download? | Request associated with the completed exchange. |
requestfailed |
Did the request fail before completion? | Request and, when available, failure text. |
requestservedfromcache |
Was a request served from browser cache? | Request. |
A 404 or 503 is an HTTP response, not a transport failure. It normally produces a response and then requestfinished. Check response.status() to classify HTTP errors. Use requestfailed for failures such as connection or loading errors; request.failure()?.errorText can help diagnose them, but Puppeteer does not guarantee a failure string for every case. See the official HTTPRequest reference.
page.on('response', response => {
if (response.status() >= 400) {
console.error('HTTP error response:', response.status(), response.url());
}
});
page.on('requestfailed', request => {
console.error(
'Request did not complete:',
request.url(),
request.failure()?.errorText ?? '(no failure text provided)',
);
});
5. Dialogs, popups, and frames
Handle JavaScript dialogs
alert, prompt, confirm, and beforeunload dialogs emit dialog. A dialog can block page execution until accepted or dismissed, so decide explicitly what your automation should do.
page.on('dialog', async dialog => {
console.log('Dialog:', dialog.type(), dialog.message());
await dialog.dismiss();
});
await page.goto('https://example.com');
// Trigger an action that may open a dialog only after installing the listener.
To accept instead, call await dialog.accept(). For a prompt, pass a string to accept(promptText) when needed. See the Dialog API.
Wait for a popup
The popup event provides the new Page. Set up the wait before the click or script that opens the tab:
const popupPromise = new Promise(resolve => page.once('popup', resolve));
await page.click('a[target="_blank"]');
const popup = await popupPromise;
await popup.waitForLoadState?.('load'); // Use the navigation/wait method supported by your Puppeteer version.
console.log('Popup URL:', popup.url());
Puppeteer’s Page API is not Playwright’s API: for broad version compatibility, use a popup event and then wait using Puppeteer’s documented navigation or lifecycle methods for your installed release. For example, if navigation is still in progress, use await popup.waitForNavigation({ waitUntil: 'domcontentloaded' }) with an appropriate timeout. The popup remains in the opener’s BrowserContext, as described by the BrowserContext API.
Observe iframe lifecycle
Frame lifecycle events are emitted on the parent Page and carry a Frame. Use the frame object to identify its URL or inspect the frame tree; the Frame API documents its properties and methods.
page.on('frameattached', frame => console.log('Attached:', frame.url()));
page.on('framenavigated', frame => console.log('Navigated:', frame.url()));
page.on('framedetached', frame => console.log('Detached:', frame.url()));
6. Request interception: modify traffic carefully
Do not enable interception just to observe requests; the ordinary request event is sufficient for logging. With page.setRequestInterception(true), each request stalls until a handler continues, responds, or aborts it, unless the browser cache completes it. An unresolved request can hang loading or navigation. The official request interception reference documents this behavior.
await page.setRequestInterception(true);
page.on('request', request => {
const action = request.url().endsWith('.png')
? request.abort()
: request.continue();
void action.catch(error => {
console.error('Could not resolve intercepted request:', request.url(), error);
});
});
Install the request handler as part of setting up interception, and make sure every branch resolves the request. If other code or libraries also intercept requests, understand Puppeteer’s cooperative interception behavior and handle action promise rejections. Prefer a targeted condition and avoid interception when you only need telemetry.
7. Practical reliability and performance
- Register early: attach listeners before navigation, clicking, or script evaluation can trigger the event.
- Keep callbacks short: do not perform slow serial work in a high-volume request callback. Queue records or aggregate them, and handle asynchronous errors explicitly.
- Limit retained data: network events can be numerous. Store only fields needed for the diagnostic and bound any in-memory log.
- Separate HTTP status from transport failure: monitor response status codes as well as
requestfailed. - Set a completion condition: choose a lifecycle event or a specific selector/state that matches the task. A page can continue issuing requests after the initial load.
- Close resources: remove temporary listeners and close pages or the browser in cleanup paths, including error handling.
- Use interception only for changes: every intercepted request needs a resolution action and interception can add work to the loading path.
Page events add no separate per-event charge in Puppeteer itself; operational cost comes from the browser runtime and the work your listeners perform. No general performance benchmark applies across sites and environments. Measure the workload you care about and avoid turning verbose network logging on for every production capture if you do not need it.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| An expected event never arrives | The listener was attached after the triggering action, or the wrong event was chosen. | Register before navigation/click; confirm the event and payload in the installed version’s PageEvent documentation. |
A 404 does not trigger requestfailed |
404 is an HTTP response, so the exchange completed. | Listen for response and check status() >= 400. |
| Navigation hangs after enabling interception | A request branch did not call continue(), abort(), or respond(), or the action failed. |
Resolve every intercepted request and catch action promise rejections. Disable interception if modification is unnecessary. |
| The page appears frozen after an alert | The dialog is still open and blocks page execution. | Register a dialog handler before triggering it and accept or dismiss deliberately. |
| Popup wait times out | The action did not open a new page, the event was missed, or the site reused the current tab. | Install the one-time listener before the action and verify the link/script behavior. Handle same-page navigation separately. |
| Listener output appears more than once | The same setup ran repeatedly, or old listeners remain attached. | Use once() for one-shot work; retain callback references and call off() during cleanup. |
request.failure() is empty |
Failure text is not guaranteed for every failed request. | Log the request URL and surrounding navigation context; do not make logic depend on a specific failure string. |
| An event named in an online example is unknown | The example targets a different or newer Puppeteer release. | Check the installed package version and its matching API reference; be especially cautious with experimental issue. |
9. Or skip the browser setup
If your goal is a screenshot rather than browser event instrumentation, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; see the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are not billed; response headers say the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card.
10. FAQ
Does load mean every request on the page is finished?
No. It is a document lifecycle event. Pages can continue making requests afterward, so observe the relevant request or wait for the specific state your task needs.
Can I use page.off() with a new arrow function?
No. Supply the same callback reference used to subscribe. Store the function in a variable or use a named function.
Which event should I use to capture page JavaScript exceptions?
Use pageerror for uncaught page exceptions. Use console when you also want console messages and warnings.
Are all events listed in the next API reference available in my release?
Not necessarily. Match documentation to your installed Puppeteer version, especially for experimental or recently added events.


