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 reinstallUse Puppeteer’s page.screenshot() method after launching a browser, opening a page and waiting for the state you need. The smallest working capture is:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'hn.png' });
await browser.close();
This saves a PNG to hn.png. Add fullPage, clip, an element handle, output settings and a page-specific readiness check when your capture needs more control.
Set up Puppeteer
Use a current Node.js installation and create a project directory. Install Puppeteer with npm:
mkdir puppeteer-shots
cd puppeteer-shots
npm init -y
npm install puppeteer
The official documentation surfaced version 25.12.0 on September 29, 2026. Puppeteer and Chromium behavior can change, so check the API documentation matching the version installed in your project when an option is version-sensitive.
#1 Best Overall
Save the following as capture.mjs and run it with node capture.mjs. The browser is closed in a finally block so a navigation or screenshot error does not leave a process running.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://news.ycombinator.com';
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'page.png' });
console.log('Saved page.png');
} finally {
if (browser) await browser.close();
}
Page.screenshot() returns screenshot bytes by default. Supplying path writes a file; if type is omitted, the file extension determines the image type and PNG is the documented default.
Choose what to capture
Viewport screenshot
With no scope option, Puppeteer captures the currently visible viewport:
await page.screenshot({ path: 'viewport.png' });
Set the viewport before navigation when a repeatable desktop or mobile composition matters:
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'desktop.png' });
Full-page screenshot
Use fullPage: true to capture the page’s complete scrollable height:
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
Full-page mode is useful for documentation and visual regression, but very long pages produce large images. Pages that continually append content can also make the final height unstable; wait for the content you actually need before calling the screenshot.
Rank #2
Clip a rectangle
Pass a clip rectangle with x, y, width and height to capture a defined region:
await page.screenshot({
path: 'hero.png',
clip: { x: 0, y: 0, width: 1200, height: 500 }
});
captureBeyondViewport controls whether the clipped region may extend outside the viewport. Its documented default is false without a clip and true with a clip.
Free tools Windows power users keep installed
One-click scans. No signup required.
Capture one element
Find the element, then call ElementHandle.screenshot(). Puppeteer scrolls the element into view when necessary:
const element = await page.waitForSelector('main');
if (!element) throw new Error('main was not found');
await element.screenshot({ path: 'main.png' });
The operation throws if the element has been detached from the DOM. Reacquire the handle after a framework re-render and capture again.
Wait for the page state you actually need
waitUntil: 'networkidle2' in the guide example is a useful starting point, not a universal guarantee that every visual element is ready. A page can finish network activity before client-side rendering, fonts or images settle. Combine navigation with a condition tied to the target page:
await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await page.waitForSelector('[data-ready="true"]', { timeout: 30000 });
await page.screenshot({ path: 'dashboard.png' });
For a known animation or delayed widget, use a bounded delay only when necessary:
Recommended Free Tools
await new Promise(resolve => setTimeout(resolve, 1000));
Inspect the resulting image rather than assuming that a lifecycle event means visual readiness. If a site’s content is personalized, set the required cookies or authentication before waiting for the readiness selector.
Control image format, quality and transparency
The output options let you tune storage and appearance:
await page.screenshot({
path: 'card.webp',
type: 'webp',
quality: 82
});
await page.screenshot({
path: 'transparent.png',
omitBackground: true
});
typeselects PNG, JPEG or WebP where supported by the installed Puppeteer/ browser combination.qualityranges from 0 to 100 and does not apply to PNG.omitBackground: trueremoves the default white background and permits transparency.encodingdefaults to binary. Setencoding: 'base64'when you need a base64 string.
To keep bytes in memory instead of writing a file:
const bytes = await page.screenshot({ type: 'png' });
console.log(`Captured ${bytes.length} bytes`);
For an HTTP response, send those bytes with an image/png, image/jpeg or image/webp content type. For base64:
const base64 = await page.screenshot({ encoding: 'base64' });
A complete reusable capture script
This example accepts a URL, chooses full-page or viewport mode, waits for an optional selector and writes a WebP image:
import puppeteer from 'puppeteer';
const target = process.argv[2] ?? 'https://example.com';
const selector = process.env.READY_SELECTOR;
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1365, height: 768, deviceScaleFactor: 1 });
await page.goto(target, { waitUntil: 'networkidle2', timeout: 60000 });
if (selector) await page.waitForSelector(selector, { timeout: 30000 });
await page.screenshot({
path: 'capture.webp',
type: 'webp',
quality: 85,
fullPage: process.env.FULL_PAGE === '1'
});
console.log('Saved capture.webp');
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
if (browser) await browser.close();
}
Run a full-page capture that waits for a known state with:
READY_SELECTOR='main' FULL_PAGE=1 node capture.mjs https://example.com
Common failures and fixes
Navigation timeout
Symptom: page.goto() times out. Cause: the page keeps requests open, is slow, or is unreachable from the machine running Chromium. Fix: verify the URL from that machine, set an explicit timeout appropriate to the site, and use domcontentloaded followed by a targeted selector when network idle never occurs. A timeout does not prove that no pixels loaded; do not save the result unless your own readiness check passed.
Rank #4
Blank or partially rendered image
Symptom: the file exists but content is missing. Cause: capture happened before client-side rendering, lazy images or fonts completed. Fix: wait for a page-specific element or state, and inspect the image. For lazy-loaded content, scroll or use full-page capture only after the page has had time to populate.
Element handle detached
Symptom: elementHandle.screenshot() throws because the node was detached. Cause: a framework replaced the element between lookup and capture. Fix: wait for the stable state, reacquire the selector immediately before the screenshot, and retry within a bounded loop.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Wrong crop
Symptom: a clipped image is offset or truncated. Cause: clip coordinates are viewport coordinates, while responsive layouts may change after navigation. Fix: set the viewport first, measure the element with getBoundingClientRect(), and use those values for the clip or capture the element handle directly.
Unexpected file format or size
Symptom: output is PNG when you expected JPEG/WebP, or quality has no effect. Cause: omitted type, an extension-driven format, or applying quality to PNG. Fix: set type explicitly and use quality only for lossy formats.
Browser process remains after an error
Symptom: Node does not exit cleanly. Cause: the browser was not closed on an exception. Fix: keep browser.close() in finally, as in the reusable script.
Reliability and performance practices
- Reuse one browser process for a batch, but create a fresh page per capture when isolation matters.
- Set explicit navigation and selector timeouts so a failed site cannot consume a worker indefinitely.
- Use a stable viewport and device scale factor for comparable output.
- Prefer a semantic readiness selector over a large arbitrary delay.
- Save diagnostic HTML, console messages and a failure screenshot separately when investigating a flaky page.
- Close pages and browsers after work; long-running services otherwise accumulate resources.
- Keep full-page captures bounded on pages with unbounded feeds or animations.
There is no single wait strategy that is correct for every site. Treat readiness as part of the page-specific capture contract and verify representative outputs before automating at scale.
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 →Best Value
Or skip the browser setup
ScreenshotNeo provides a hosted website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, so your application does not need to install or manage Chromium.
cURL:
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}`);
See the ScreenshotNeo documentation for parameters and response details. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets; 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 (X-Page-Verdict and X-Billed).
For automation by AI tools, its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients. The service also supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper sizes/margins/landscape/page ranges, HTML/CSS-to-image, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, blocking ads/trackers/requests/resource types, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | $0, no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Yearly billing gives two months free, and every feature is available on every plan. Start with 1,000 free screenshots a month—no card required.
Frequently Asked Questions
Should I use full-page mode for a page with infinite scrolling?
Not by itself. Infinite feeds have no stable final height; define the content boundary you need, wait for that state, then capture a viewport, clip or specific element.
Can I return screenshot data without creating an image file?
Yes. Omit path and use the bytes returned by page.screenshot(), or request encoding: 'base64' when a text representation is required.
Why might two captures of the same URL differ?
Responsive layout, asynchronous rendering, personalization, animations and changing network content can all alter pixels. Fix the viewport and readiness condition, and disable or wait for moving content when reproducibility matters.
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.




