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 reinstallOutdated 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 matchUse a condition that matches what your script needs. For ordinary navigation, page.goto(url) waits for Puppeteer’s default load lifecycle event. If your page renders results after that event, wait for the result element or application state explicitly. Network-idle waits are useful when network quiet is itself part of the requirement, but they do not prove that animations, timers, or future application work have ended.
The reliable pattern is therefore: navigate, wait for a task-specific readiness signal, then interact with or capture the page. The examples below target Puppeteer 25.12.0 API behavior documented on 2026-09-29; timeout and lifecycle details can change between releases.
What “fully loaded” means in Puppeteer
There is no single browser milestone that guarantees every page is finished. A lifecycle event describes navigation progress, while an application condition describes whether the content your code needs is ready.
| Requirement | Recommended wait | What it observes |
|---|---|---|
| Ordinary navigation | page.goto(url) or waitUntil: 'load' |
The documented default load lifecycle event |
| DOM parsed before all subresources finish | waitUntil: 'domcontentloaded' |
Document parsing, not application readiness |
| Network quiet is part of your requirement | waitUntil: 'networkidle2' or page.waitForNetworkIdle() |
A configured period of network idleness |
| A result, button, or view must exist | page.waitForSelector() or a Locator |
The actual DOM element or interaction state your task needs |
| A click causes navigation | Promise.all([page.waitForNavigation(), page.click(...)]) |
Navigation registered before the click can trigger it |
waitUntil accepts one lifecycle event or an array. When you pass an array, every named event must fire. Puppeteer’s documented default navigation timeout is 30,000 milliseconds; set a task-specific timeout when a slower page is expected.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitches#1 Best Overall
Start with the default navigation wait
For a static page, the shortest correct solution is:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();
})();
Because no waitUntil option is supplied, Puppeteer uses 'load'. Make the choice explicit when it matters:
await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000
});
Use 'domcontentloaded' when your code only needs the parsed document and can tolerate images or other subresources still loading. Use 'load' when the navigation should include the page’s normal load lifecycle. Neither choice tells you that a client-side framework has finished fetching and rendering its data.
Wait for the content your script actually uses
Wait for a required selector
If a results panel is inserted after navigation, wait for that panel rather than adding an arbitrary sleep:
Recommended Free Tools
await page.goto('https://example.com/search?q=puppeteer');
await page.waitForSelector('[data-testid="results"]', {
visible: true,
timeout: 30000
});
const text = await page.$eval('[data-testid="results"]', el => el.textContent);
console.log(text);
page.waitForSelector() resolves when a matching element is added to the DOM. visible: true additionally requires it to be visible. Its documented default timeout is 30 seconds, and a selector that does not appear before the timeout causes the wait to throw. A hidden wait can resolve with null when the selector is absent, which is useful for optional elements.
Rank #2
Use a Locator for interactions
Puppeteer recommends Locators for selecting and interacting with elements. A Locator waits for the element and the relevant interaction state, so it is generally a better fit for actions such as clicking a button than manually querying and clicking immediately. Use waitForSelector when you need a lower-level presence or visibility check.
const submit = page.locator('button[type="submit"]');
await submit.click();
Wait for an application condition
Sometimes the element exists immediately but changes from “Loading…” to usable content later. In that case, wait for the state your script can verify:
await page.waitForFunction(() => {
const status = document.querySelector('[data-testid="status"]');
return status && status.textContent.trim() === 'Ready';
}, { timeout: 30000 });
Keep the predicate specific. A broad condition such as “the body is non-empty” usually becomes true before the application has rendered useful data.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Use network-idle waits deliberately
Navigation with networkidle2
Puppeteer’s screenshot guide demonstrates networkidle2 before taking a screenshot:
await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'page.png', fullPage: true });
This is appropriate when your task depends on a period of network quiet. It can be a poor universal “fully loaded” switch: analytics, polling, advertisements, sockets, or other long-lived activity may prevent the condition from matching, while a quiet network does not guarantee that an animation or delayed state transition has completed.
Rank #3
Wait for idleness after navigation
You can separate navigation from the network-idle check:
await page.goto(url);
await page.waitForNetworkIdle();
The method resolves after the network is idle for at least the configured idle time. The current options reference documents a default idle time of 500 milliseconds and a default concurrency of zero. Configure those values only when they represent your application’s definition of quiet:
await page.waitForNetworkIdle({
idleTime: 1000,
concurrency: 0,
timeout: 30000
});
Prefer a selector or application predicate when you know exactly what “ready” means; use network idleness as one part of a synchronization contract, not as proof that all future work has stopped.
Avoid click-and-navigation races
Register the navigation wait before performing a click that can navigate. Waiting afterward can miss a fast navigation and leave your script hanging:
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'load' }),
page.click('a.next-page')
]);
if (response) {
console.log('HTTP status:', response.status());
}
await page.waitForSelector('[data-testid="results"]', { visible: true });
The same pattern applies to form submissions and other interactions that replace the document. If the click updates the current page without navigation, wait for the resulting selector or application state instead.
Rank #4
Combine waits without waiting for irrelevant work
A robust scraper or screenshot script often has two stages: a navigation milestone followed by the exact content check.
Free tools Windows power users keep installed
One-click scans. No signup required.
const response = await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 45000
});
await page.waitForSelector('.product-grid article', {
visible: true,
timeout: 30000
});
if (response && response.status() >= 400) {
throw new Error(`Navigation returned HTTP ${response.status()}`);
}
This avoids forcing every page to satisfy a global network-idle rule. For a page where network quiet is independently important, perform waitForNetworkIdle() as an additional, explicit step.
Timeouts, failures, and recovery
Navigation timeout
WaitForOptions documents a 30,000-millisecond default timeout. Increase it for a known slow route, or pass timeout: 0 to disable the timeout when you have an external cancellation strategy. Disabling timeouts without another deadline can leave a worker stuck indefinitely.
await page.goto(url, {
waitUntil: ['domcontentloaded', 'load'],
timeout: 60000
});
Selector timeout
A selector timeout usually means the selector is wrong, the page is still on a different route, the element is inside a frame, or the application never reached the expected state. Capture the current URL and a diagnostic screenshot before retrying:
try {
await page.waitForSelector('[data-testid="results"]', {
visible: true,
timeout: 15000
});
} catch (error) {
console.error('URL at failure:', page.url());
await page.screenshot({ path: 'wait-failure.png', fullPage: true });
throw error;
}
HTTP errors and headless shell behavior
Do not assume that a resolved navigation means the server returned a successful status. Puppeteer’s Page documentation notes that headless shell mode may return without throwing for HTTP errors such as 404 or 500. Inspect the response status when it affects your result, as shown in the combined-wait example.
Best Value
Network-idle never resolves
- Replace a global network-idle wait with a known result selector if the page polls or keeps a connection open.
- Use a finite timeout and report the URL, status, and last observed application state.
- If the page has optional third-party requests, do not make those requests part of your readiness contract.
The selector appears too early
Change the condition from presence to visibility, or wait for a text or attribute value that identifies the completed state. An element can be present while its data, disabled state, or child nodes are still being populated.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and reliability practices
- Use the narrowest condition. A specific selector normally finishes sooner and fails more clearly than waiting for unrelated requests.
- Keep navigation and content timeouts separate. A slow document load and a missing results element are different failures and should produce different diagnostics.
- Reuse a browser process carefully. Reusing one launched browser while creating isolated pages reduces startup work, but close pages and browsers on all success and failure paths.
- Record the observed status. Log the URL, response status, selected wait condition, and elapsed time so a timeout can be reproduced.
- Do not use fixed delays as a readiness test. A delay may be too short on a slow run and wasteful on a fast one; an observed selector or application state expresses the actual requirement.
Complete example: load, wait, verify, and capture
This script uses a lifecycle wait for navigation, a visible selector for application readiness, and explicit status checking before capture:
const puppeteer = require('puppeteer');
async function capture(url) {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const response = await page.goto(url, {
waitUntil: 'load',
timeout: 30000
});
if (response && response.status() >= 400) {
throw new Error(`Navigation failed with HTTP ${response.status()}`);
}
await page.waitForSelector('[data-testid="page-ready"]', {
visible: true,
timeout: 30000
});
await page.screenshot({ path: 'page.webp', fullPage: true });
} finally {
await browser.close();
}
}
capture('https://example.com').catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace [data-testid="page-ready"] with a selector that your page sets only when the content needed by the capture or extraction task is usable. If there is no such marker, use a stable result element or an application predicate instead.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when you do not need to manage Chromium, waits, and cleanup yourself. Before the capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, 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.
One-call cURL request
See the parameter reference in the ScreenshotNeo documentation.
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 offers full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF output, custom CSS and JavaScript, click-before-capture, selector or delay waits, network-idle waits, request and resource blocking, headers, cookies, user agents, Authorization, timezone and geolocation controls, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to 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 screenshots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.
Frequently Asked Questions
Is a fixed sleep ever equivalent to waiting for readiness?
No. A fixed delay observes elapsed time, not the page’s state. It can finish before a slow response arrives or delay every fast run. Prefer a selector, an application predicate, or a deliberately configured network-idle condition.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Can I require both a lifecycle event and a content marker?
Yes. Pass the lifecycle condition to page.goto(), then call page.waitForSelector() or page.waitForFunction() for the marker. This gives navigation and application readiness separate, diagnosable timeouts.
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.




