The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →If Puppeteer code runs more than once, first identify what is repeating: an event listener, a navigation or new-document hook, a page or browser-context workflow, or a request-interception handler. Log each entry point, then fix the lifecycle at that specific boundary. Do not assume Puppeteer has duplicated a single command: pages can navigate, frames can attach, and event handlers can be registered more than once.
Find the trigger before changing the code
“Runs twice” can describe different problems: a Node.js callback fires twice, code inside the browser executes again after a reload, or two pages perform the same work. These cases need different fixes. Start by recording the event that leads into the work, along with its URL, frame, page and timestamp.
- Give each Node-side workflow a run ID and log it at the beginning of every handler.
- Log the page URL and, where applicable, the frame URL at the point browser-side code starts.
- Count relevant listeners before and after setup.
- Check whether the page navigated, a popup opened, or a retry created another page.
Use this decision guide to narrow the cause:
| When the duplicate appears | Inspect first | Typical direction |
|---|---|---|
| Once per navigation | evaluateOnNewDocument, goto, reloads, redirects and history-driven navigation |
Decide whether the code is meant to run on every document; remove the hook when finished if not. |
| Once per event | Repeated page.on(...) calls, setup in loops, retries or functions called more than once |
Use a one-shot listener when appropriate, or make setup idempotent and clean it up. |
| Once per tab or context | browser.pages(), newPage(), popup handlers and browser-context loops |
Identify which page owns the work and whether additional pages are expected. |
| After a click that navigates | Ordering of click() and waitForNavigation() |
Start the navigation wait and click concurrently. |
| During request interception | Every handler that continues, aborts or responds to a request | Check whether another handler has already resolved it before resolving it yourself. |
Prevent event listeners from accumulating
page.on(event, handler) registers a persistent listener. If a setup function runs on every retry or for every job but never removes the previous listener, the same event can invoke several registered callbacks. The symptom may look like Puppeteer repeated one callback even though the code installed it repeatedly.
Use one-shot listeners only for one-shot work
For an action that should respond to one load event and then stop listening, use once:
#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
page.once('load', handler);
Puppeteer documents once(type, handler) as like on, except the listener fires once and is then removed. A one-shot listener is not a substitute for a persistent listener if the page is expected to produce multiple events.
Make persistent setup idempotent
When you need a persistent listener, retain the same function reference so you can remove that exact registration. Removing and re-adding the handler makes repeated setup safe:
function installLogging(page) {
const handler = msg => console.log('PAGE LOG:', msg.text());
page.off('console', handler);
page.on('console', handler);
return () => page.off('console', handler);
}
The returned cleanup function is useful when the page or workflow is done. The off call only removes a matching listener when it receives the same function reference; creating a new anonymous function for removal will not remove the old one.
To find out whether registrations are multiplying, inspect counts around setup:
console.log('console listeners before:', page.listenerCount('console'));
console.log('request listeners before:', page.listenerCount('request'));
installLogging(page);
console.log('console listeners after:', page.listenerCount('console'));
console.log('request listeners after:', page.listenerCount('request'));
Check the event that is actually duplicating, and compare counts before and after each setup path. A count above one is not automatically a bug: separate components may intentionally listen for the same event. It is evidence to connect to the handler names and registration paths.
Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
Coordinate clicks with navigation waits
A click that causes navigation can beat a separately awaited navigation wait. Puppeteer warns that waiting after the click may produce a race condition. Start both operations together so the wait is active before the navigation-triggering action can complete:
await page.goto(startUrl);
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click(submitSelector),
]);
console.log('Navigation response:', response?.status());
Use the wait condition that matches your task. domcontentloaded waits for the initial document to be parsed; a later readiness condition may be needed if your next operation depends on application data or a particular element. A navigation response can be null in cases where the transition does not produce a normal HTTP response, so do not treat a missing response alone as proof that your click ran twice.
If the click handler itself is registered repeatedly, correcting the wait order will not fix that separate listener problem. Log the click path and inspect its setup independently.
Recommended Free Tools
Understand code that runs on every new document
evaluateOnNewDocument is designed to run when the page navigates and when a child frame is attached or navigated. If its callback increments a counter or performs a visible action, seeing it again after navigation is expected behavior, not necessarily duplicate registration.
Register a new-document hook once for the lifetime in which you need it. Puppeteer returns an identifier that can be used to remove it when the hook is no longer needed:
Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
const injection = await page.evaluateOnNewDocument(() => {
window.__automationSetupCount = (window.__automationSetupCount || 0) + 1;
});
// Later, when this hook is no longer needed:
await page.removeScriptToEvaluateOnNewDocument(injection.identifier);
Use a browser-side marker like this for diagnosis, not as a universal guard against legitimate document initialization. If you need initialization on every new document, repeated invocation is the point of the API. If you only need it on one document, use a document-scoped evaluation instead of a persistent new-document hook.
Check how many pages your browser owns
One browser instance can contain multiple pages. A workflow that creates a page in each retry, loop iteration or context can therefore execute the same task in more than one tab. Inspect the page list and the code paths that create pages before treating one browser as one execution.
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 minuteconst pages = await browser.pages();
console.log('Open pages:', pages.length);
for (const [index, openPage] of pages.entries()) {
console.log(index, await openPage.url());
}
If a particular operation must use exactly one page, a diagnostic guard can expose unexpected page creation:
const pages = await browser.pages();
if (pages.length !== 1) {
throw new Error(`Expected one page, found ${pages.length}`);
}
const page = pages[0];
This is a diagnostic condition, not a production rule for every application. Multiple tabs and contexts are normal when the application intentionally opens them. Also inspect popup handling and browser.targets() when a new target may be opening outside the page-creation code you are watching.
Guard request-interception handlers
When request interception is enabled, multiple handlers or asynchronous work can race to resolve the same request. Before calling continue(), abort() or respond(), check isInterceptResolutionHandled(). If the handler awaits work, check again afterward: another handler may have resolved the request while this one was waiting.
Rank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) return;
const shouldBlock = await shouldBlockRequest(request);
// Another handler may have resolved it during the await.
if (request.isInterceptResolutionHandled()) return;
if (shouldBlock) {
await request.abort();
} else {
await request.continue();
}
});
Keep the final check immediately before resolution, and apply the same pattern in every handler that resolves intercepted requests. If interception is unnecessary for the task, disabling it is simpler than maintaining competing resolution paths.
Instrument Node and browser execution separately
Puppeteer code runs on both sides of the boundary: in Node.js and inside the page. A message printed by browser-side code may not appear in the Node console unless you forward it. Attach a console listener so page output is visible alongside your Node logs:
page.on('console', msg => {
console.log('PAGE LOG:', msg.type(), msg.text());
});
Use distinct labels and a monotonic run ID for Node-side entry points and browser-side work. When the second execution appears, compare its timestamp, URL, frame URL, page identity and run ID with the first. That often reveals whether a navigation, frame attachment, retry, popup or repeated listener registration is responsible.
If logs do not make the trigger clear, run with headless: false and optional slow motion to observe reloads, popups and retries. Puppeteer’s debugging guide covers forwarding browser logs and debugging browser-side execution: Puppeteer debugging guide.
Troubleshoot common duplicate-execution symptoms
The same event handler prints twice
- Likely cause: Setup runs more than once and registers a persistent listener each time.
- Check: Log the setup call and compare
page.listenerCount(eventName)before and after it. - Fix: Keep a stable handler reference, use idempotent setup, and call
offduring cleanup. Useonceonly when one event is genuinely enough.
Page code repeats after a reload or redirect
- Likely cause: A new-document hook is intentionally firing again, or the page navigated more than once.
- Check: Log the URL and frame URL each time the browser-side code begins; inspect calls to
goto, reload and navigation-triggering actions. - Fix: Remove the hook by its returned identifier when no longer needed, or move one-document work out of
evaluateOnNewDocument.
The task runs in two tabs
- Likely cause: A loop, retry or popup path created another page or context.
- Check: Inspect
browser.pages(), page URLs, popup handling and page-creation call sites. - Fix: Reuse the intended page or explicitly associate each task with its own page; do not impose a one-page rule if multiple tabs are part of the design.
The script seems to skip or repeat work around a click
- Likely cause: A navigation wait was attached after a fast navigation, or the click path itself has duplicate registrations.
- Check: Run
waitForNavigationandclickin the samePromise.all, and log the click handler separately. - Fix: Correct the wait ordering first; then investigate listener lifecycle if the click action still appears duplicated.
Interception reports that a request is already handled
- Likely cause: Two handlers resolve a request, or one handler resumes after an
awaitand acts on an already-resolved request. - Check: Find every interception handler and every resolution call.
- Fix: Check
isInterceptResolutionHandled()before and after asynchronous work, immediately before resolving.
Keep the fix reliable without hiding the cause
Choose a fix that matches the trigger’s lifecycle. A one-shot listener belongs to one event; a persistent listener belongs to a page or workflow and needs cleanup; a new-document hook belongs to navigation and should be removed by its identifier when its job ends; a page guard belongs to a workflow that truly requires one page. Overly broad cleanup, such as removing all listeners indiscriminately, can silence legitimate functionality while leaving the repeated setup path in place.
Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
For reliable runs, log the point where work begins and the page or frame that owns it. Keep retries explicit, avoid silently creating a new page on every retry, and clean up listeners and hooks when the owning task ends. These practices make a duplicate trigger observable rather than masking it with a counter or arbitrary delay.
Or skip the browser setup
If your goal is to get a website screenshot rather than debug a Puppeteer workflow, ScreenshotNeo provides a screenshot API and MCP server for developers. A single GET request can return a PNG, JPEG, WebP or PDF. The API docs list request parameters and options: ScreenshotNeo 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
ScreenshotNeo accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups and chat widgets before capture; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server offers 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; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteFrequently Asked Questions
Does Puppeteer automatically rerun my entire Node.js script after navigation?
No. A page navigation can rerun browser-side hooks registered with evaluateOnNewDocument, but it does not by itself restart the Node.js process or its script.
Should I use removeAllListeners() to stop duplicates?
Only when you own the listeners being removed and intend to remove all listeners for that event or emitter. For a specific handler, retaining its function reference and calling off is more targeted.
Can a child frame cause new-document code to run again?
Yes. Puppeteer documents that evaluateOnNewDocument runs when a child frame is attached or navigated as well as when the page navigates.
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.

