Outdated 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 matchWindows 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 reinstallWait for the page to become idle, then explicitly decode every image currently in the document before calling page.screenshot(). Puppeteer’s page.evaluate() waits for a promise returned by the browser-side function, so image readiness can be enforced in the page before capture.
await page.evaluate(async () => {
await document.fonts.ready;
await Promise.all(
Array.from(document.images, async (image) => {
await image.decode();
if (!image.naturalWidth) {
throw new Error(`Broken image: ${image.src}`);
}
}),
);
});
await page.screenshot({ path: 'page.png' });
This checks decoded, usable <img> elements that exist when the check runs. Lazy-loaded content, images inserted later, and CSS background images need additional handling.
Why network idle alone can still produce missing images
page.waitForNetworkIdle() describes network activity; it does not guarantee that every image has been decoded and painted. A request can finish while the browser is still decoding the bitmap, and a page can add more images after the network becomes quiet. A screenshot taken at that point may therefore contain empty image boxes or partially rendered content.
Use navigation or network-idle waits as a first stage, not as the final image-readiness test. The reliable sequence is:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
- Navigate to the page and wait for the state your application requires.
- Trigger lazy content and other dynamic sections that should appear in the capture.
- Wait for fonts and image decoding.
- Validate failures and enforce a finite timeout.
- Capture the viewport, an element, or the full page.
A complete Puppeteer implementation
Basic decode check for images already in the DOM
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.evaluate(async () => {
await document.fonts.ready;
await Promise.all(
Array.from(document.images, async (image) => {
await image.decode();
if (!image.naturalWidth) {
throw new Error(`Broken image: ${image.src}`);
}
}),
);
});
await page.screenshot({ path: 'page.png' });
await browser.close();
image.decode() resolves after an image is decoded for rendering. Checking naturalWidth prevents a failed resource from being treated as ready. A decode rejection is also a failure signal. In this example any failed image aborts the job, which is appropriate when a complete visual is required.
Use a bounded wait and return diagnostics
Never let an automation worker wait forever for a resource that will never succeed. The following helper applies a timeout and reports the URLs that failed or remained incomplete.
async function waitForImages(page, timeoutMs = 30_000) {
return page.evaluate(async (timeout) => {
const images = Array.from(document.images);
const work = Promise.all(images.map(async (image) => {
try {
await image.decode();
} catch (error) {
return {
src: image.currentSrc || image.src,
status: 'decode-error',
error: String(error),
};
}
return {
src: image.currentSrc || image.src,
status: image.naturalWidth ? 'ready' : 'broken',
};
}));
let timer;
const deadline = new Promise((resolve) => {
timer = setTimeout(() => resolve({ timedOut: true }), timeout);
});
const result = await Promise.race([
work.then((items) => ({ timedOut: false, items })),
deadline,
]);
clearTimeout(timer);
return result;
}, timeoutMs);
}
const result = await waitForImages(page);
if (result.timedOut) {
throw new Error('Timed out while waiting for images');
}
const failed = result.items.filter((item) => item.status !== 'ready');
if (failed.length) {
throw new Error(`Images not ready: ${JSON.stringify(failed)}`);
}
await page.screenshot({ path: 'page.png' });
Decide your policy explicitly. For invoices, reports, visual regression tests, and archival captures, aborting with a URL list is usually safer than silently producing an incomplete file. For a best-effort thumbnail service, you may continue while logging the missing resources and exposing that status to callers.
Handling lazy-loaded and dynamically inserted images
The decode loop only sees images present when it executes. Modern pages often use loading="lazy", intersection observers, client-side hydration, or infinite scrolling. Scroll through the page (or otherwise trigger the component) before collecting images.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Rank #2
await page.evaluate(async () => {
const step = Math.max(window.innerHeight, 400);
for (let y = 0; y < document.body.scrollHeight; y += step) {
window.scrollTo(0, y);
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
}
window.scrollTo(0, 0);
});
await page.waitForNetworkIdle({ idleTime: 500, timeout: 30_000 });
await waitForImages(page, 30_000);
await page.screenshot({ path: 'full-page.png', fullPage: true });
Scrolling once may not be enough for an infinite feed. Set a content limit, repeat until the expected selector exists, or stop when the document height no longer grows. If JavaScript inserts images after your readiness check, run the check again immediately before capture.
Wait for a known selector when the page has a clear milestone
await page.waitForSelector('.article-gallery img', { visible: true, timeout: 30_000 });
await page.waitForFunction(
() => [...document.querySelectorAll('.article-gallery img')]
.every((img) => img.complete),
{ timeout: 30_000 },
);
await waitForImages(page, 30_000);
complete indicates that loading has finished, but it does not by itself prove that the image is usable. Keep the decode and naturalWidth checks for the final decision.
CSS backgrounds, posters, and other visual assets
document.images does not include CSS background-image URLs, SVG backgrounds, or video poster frames. If those assets matter, identify the elements and wait for their resources separately. A practical approach is to inspect computed styles, extract URL values, and preload them with browser Image objects.
await page.evaluate(async () => {
const urls = new Set();
for (const element of document.querySelectorAll('*')) {
const style = getComputedStyle(element);
const match = style.backgroundImage.match(/url(["']?(.*?)["']?)/);
if (match) urls.add(match[1]);
}
await Promise.all([...urls].map((src) => new Promise((resolve, reject) => {
const image = new Image();
image.onload = resolve;
image.onerror = () => reject(new Error(`Background failed: ${src}`));
image.src = src;
})));
});
This is an application-specific check: gradients, data URLs, multiple backgrounds, responsive images, and cross-origin policies require more parsing. If your screenshot contract includes these assets, define which selectors and resource types are mandatory rather than assuming that an <img> check covers the page.
Viewport, element, and full-page captures
Image readiness is independent of screenshot extent. A viewport screenshot captures what is visible; an element screenshot targets one node; fullPage: true captures the page’s full scrollable extent and defaults to false. Choose the extent only after preparing the content.
await page.screenshot({ path: 'viewport.png' });
await page.locator('.hero').screenshot({ path: 'hero.png' });
await page.screenshot({ path: 'document.png', fullPage: true });
For full-page work, set the viewport before navigation, trigger all lazy sections, and run the final readiness check after scrolling. A full-page option does not itself wait for images.
Failure modes and fixes
The screenshot is taken before images appear
Cause: capture follows navigation or a selector wait without decoding. Fix: call the page-side image.decode() loop immediately before screenshot().
decode() rejects
Cause: the response is invalid, blocked, canceled, or otherwise unusable. Fix: record currentSrc, inspect the browser console and network response, then retry or fail according to your completeness policy. Do not hide the rejection.
Rank #4
naturalWidth is zero
Cause: the image is broken or has not produced a usable bitmap. Fix: verify the final URL, authentication, redirects, content type, and server response. If the image is intentionally empty, exclude that selector from your required set.
Lazy images remain blank
Cause: they were not inserted or requested when the check ran. Fix: scroll or interact to trigger them, wait for the resulting network activity, then rerun the image check.
A timeout hangs the worker
Cause: an image request or decode never completes. Fix: use a finite timeout, cancel or close the page on failure, and return diagnostics. The correct timeout depends on your page and network; there is no universal value.
Fonts or layout shift change the result
Cause: text reflows while fonts load. Fix: await document.fonts.ready before image checks and capture. If client-side rendering continues, also wait for the application’s own ready marker.
Best Value
- Used Book in Good Condition
Performance and reliability practices
- Use one browser page per job and close it in a
finallyblock. - Set navigation, selector, network-idle, and image-readiness timeouts separately so failures identify their stage.
- Capture the image URL, status, and elapsed time in logs; this makes intermittent CDN or authentication failures diagnosable.
- Do not add an arbitrary sleep as your only solution. A sleep can be too short on a slow run and wasteful on a fast one.
- For retries, reload only after recording the first failure and cap the number of attempts.
- Use stable test pages or application readiness markers for visual regression jobs.
- Limit infinite-scroll expansion to a known amount of content to avoid unbounded memory and capture times.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF output, while its capture flow accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Each step can be turned off.
Only clean shots are billed: bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. The response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
For API options such as full-page capture with lazy images loaded, CSS-selector element capture, custom JavaScript, waits, blocking rules, device presets, retina scale, cookies, headers, geolocation, caching, signed links, asynchronous jobs, bulk capture, and PDF controls, see 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
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}`);
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.
FAQ
Does fullPage: true wait for every image?
No. It changes the capture extent only. Prepare and validate images separately.
Should a single broken image fail the entire job?
For complete documents, yes; for thumbnails, you may continue if the missing URL and status are reported.
Can I wait for only images inside one component?
Yes. Query that component’s image elements and apply the same decode and width checks to that subset.
Is a fixed delay ever sufficient?
It can mask a timing issue but cannot prove readiness. Prefer event-based checks with a finite deadline.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsQuick 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.

