Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Use an element handle, not page.screenshot({fullPage: true}). Wait for the target node, then call ElementHandle.screenshot():
const element = await page.waitForSelector('#target');
await element.screenshot({ path: 'element.png' });
Puppeteer scrolls the selected element into view when necessary and captures its rendered bounds, including content outside the current viewport. A full-page screenshot is a different operation: it captures the document, not one element.
Element screenshots and full-page screenshots are different
Puppeteer has two scopes:
- Element scope: an
ElementHandlefollowed byelement.screenshot(). Use this for one card, article, chart, component, or other DOM node. - Document scope:
page.screenshot({ fullPage: true }). Use this for the entire scrollable page.
The fullPage option is page-level and defaults to false. It does not expand a selected element. If the requirement is “the whole element,” select that element and capture its handle.
Minimal runnable Puppeteer example
This script opens a page, waits for a target, captures it, and always closes the browser:
#1 Best Overall
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const element = await page.waitForSelector('#target');
if (!element) throw new Error('Target element was not found');
await element.screenshot({ path: 'target.png' });
} finally {
await browser.close();
}
Run it in an environment configured for ECMAScript modules, or adapt the import to your project’s module system. Replace https://example.com and #target with the page and selector you need.
Make the captured pixels final before taking the shot
waitForSelector confirms that a node exists; it does not guarantee that its fonts, images, animations, or client-side data have finished rendering. Add waits that match the application:
Wait for a specific selector
await page.waitForSelector('#report.ready');
const element = await page.$('#report');
if (!element) throw new Error('Report is missing');
await element.screenshot({ path: 'report.png' });
Wait for images to decode
await page.waitForSelector('#report');
await page.evaluate(async () => {
const images = Array.from(document.querySelectorAll('#report img'));
await Promise.all(images.map(img => {
if (img.complete) return img.decode?.().catch(() => {});
return new Promise(resolve => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
});
}));
});
const element = await page.$('#report');
if (!element) throw new Error('Report is missing');
await element.screenshot({ path: 'report.png' });
Wait for fonts
await page.evaluate(async () => {
if (document.fonts?.ready) await document.fonts.ready;
});
For data loaded after navigation, wait for the application’s “ready” selector or a page-specific promise rather than relying only on a fixed delay. A delay can be useful for an animation, but a state-based wait is usually less brittle.
Dynamic pages: avoid detached element handles
When a framework rerenders a component, the original handle can point to a node that is no longer attached to the DOM. Puppeteer throws when an element handle has been detached. Query the selector again immediately before capture, after the render that matters:
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 →Rank #2
await page.waitForSelector('#target');
await page.waitForFunction(() => {
const node = document.querySelector('#target');
return node && node.isConnected && node.getBoundingClientRect().height > 0;
});
const element = await page.$('#target');
if (!element) throw new Error('Target disappeared');
await element.screenshot({ path: 'target.png' });
If the page can rerender during capture, pause the state change (for example, finish a transition or wait for a stable application state) and reacquire the handle just before calling screenshot().
Screenshot options that matter
ElementHandle.screenshot() accepts the screenshot options used by Puppeteer’s page screenshot machinery. Choose only the options that match your output requirement.
| Option | What it does | Important detail |
|---|---|---|
path |
Writes the image to a file. | Omit it to receive screenshot bytes instead. |
encoding: 'base64' |
Returns a base64 string. | Useful when an in-memory transport expects text rather than a file. |
type |
Selects png or jpeg. |
PNG is lossless; JPEG is smaller for many photographic images. |
quality |
Controls JPEG quality. | It applies to JPEG, not PNG. |
clip |
Captures a manual page rectangle. | This changes the geometry from automatic element bounds to your supplied rectangle. |
captureBeyondViewport |
Controls whether a clipped region may extend beyond the viewport. | The documented default depends on whether clip is present. |
omitBackground |
Hides the default white background. | Use it when you need transparency-capable output. |
Save a JPEG with quality
const element = await page.waitForSelector('#hero');
if (!element) throw new Error('Hero not found');
await element.screenshot({
path: 'hero.jpg',
type: 'jpeg',
quality: 85
});
Keep bytes in memory
const element = await page.waitForSelector('#chart');
if (!element) throw new Error('Chart not found');
const pngBytes = await element.screenshot({ type: 'png' });
// Send pngBytes to object storage, an HTTP response, or another service.
Request base64
const element = await page.waitForSelector('#thumbnail');
if (!element) throw new Error('Thumbnail not found');
const base64 = await element.screenshot({ encoding: 'base64' });
Geometry, scrolling, and layout edge cases
Content taller than the viewport
The element method scrolls the node into view and captures the element rather than merely cropping the visible viewport. This is the normal solution for a long component. If the element uses an internal scroll container such as overflow: auto, its screenshot reflects the rendered box; content hidden by that container is not automatically turned into a longer document. To capture all rows, first expand the container or remove the limiting overflow in a controlled page-side style.
Sticky and fixed descendants
Sticky headers, fixed toolbars, and overlays can appear according to their position at capture time. Hide or restyle them before the shot if they obscure the target:
await page.addStyleTag({
content: '.cookie-banner, .chat-widget { display: none !important; }'
});
Transforms and fractional pixels
CSS transforms can make an element’s visual bounds differ from its untransformed layout box. If exact geometry matters, inspect the rendered rectangle and consider a manual clip only when you deliberately want page coordinates. A selector-based element capture is preferable for ordinary components because Puppeteer calculates the target’s bounds for you.
Responsive output
Set the viewport before navigation so responsive breakpoints, wrapping, and lazy content are deterministic:
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
For high-density output, increase deviceScaleFactor; the resulting bitmap dimensions and file size will increase.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot read properties of null or a missing handle |
The selector did not match yet, or the node was removed. | Use waitForSelector, check the returned handle, and verify the selector and frame. |
| Detached element exception | A client-side rerender replaced the node. | Wait for the render to settle and query the selector again immediately before capture. |
| Image, font, or data is missing | The node exists before its visual content is ready. | Wait for the relevant ready state, image decode, font readiness, or application request. |
| Only the visible portion appears | You used a page crop, an internal scroll container, or a manual clip. | Use element.screenshot(); expand internal overflow content if “all rows” are required. |
| The whole page was captured | page.screenshot({ fullPage: true }) was used. |
Replace it with a handle to the desired node and call the handle’s screenshot method. |
| Unexpected cookie or chat overlay | A consent banner or widget covers the page. | Handle the banner in the page workflow or hide the overlay before selecting the target. |
| Blank or navigation timeout | The URL, network, authentication, or page scripts did not complete. | Check navigation errors, credentials, resource blocking, and use a wait condition appropriate to the site. |
Reusable helper for production scripts
Wrapping the repeated checks in a helper makes failures explicit and keeps cleanup in the caller:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
async function captureElement(page, selector, options = {}) {
await page.waitForSelector(selector);
const handle = await page.$(selector);
if (!handle) throw new Error(`Element not found: ${selector}`);
return handle.screenshot(options);
}
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com/dashboard', { waitUntil: 'networkidle2' });
await page.waitForFunction(() => document.fonts?.status === 'loaded');
await captureElement(page, '#dashboard-card', {
path: 'dashboard-card.png',
type: 'png'
});
} finally {
await browser.close();
}
Performance, reliability, and cost considerations
Reduce unnecessary work
- Reuse a browser process for multiple pages instead of launching Chromium for every element.
- Set the viewport and navigation wait condition deliberately; waiting for every network request can delay pages that keep analytics connections open.
- Use PNG for text and sharp UI; use JPEG with a suitable quality when a smaller photographic image is acceptable.
- Capture only the required node rather than the full document when downstream systems need a component.
Make jobs reproducible
- Pin the Puppeteer version used by your project and run captures with a consistent browser executable.
- Set locale, timezone, viewport, and authentication explicitly when they affect layout.
- Record the URL, selector, viewport, and wait condition with each artifact so a mismatch can be diagnosed.
- Close pages and browsers in
finallyblocks, including when navigation or capture throws.
Security boundaries
Only visit URLs and execute page-side code that your workflow trusts. Treat screenshots and returned bytes as potentially sensitive, especially when the page contains account data. Keep credentials out of selectors, filenames, logs, and generated HTML.
Rank #4
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF, and its element capture accepts a CSS selector. It can load lazy images, wait for a selector, delay, or network idle, and apply custom CSS or JavaScript when a page needs preparation.
Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, 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. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
See the parameter reference and examples in the ScreenshotNeo documentation. A direct request looks like this (replace the URL and key):
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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
ScreenshotNeo also supports dark mode, device presets and custom viewports, retina scale, PDF paper and page-range controls, click-before-capture actions, hidden selectors, blocked ads or resource types, custom headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.
Every feature is available on every plan: Free includes 1,000 shots per month with no card; Starter is $5 for 3,000; Growth $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. Sign up for the free plan to get 1,000 screenshots a month without a card.
Best Value
- Used Book in Good Condition
FAQ
Does an element screenshot include content below the viewport?
Yes, when that content belongs to the element’s rendered box. Puppeteer scrolls the element into view and captures it; content clipped by an internal scroll container still requires you to expand that container.
Can I return the screenshot without writing a file?
Yes. Omit path to receive bytes, or request encoding: 'base64' when a text transport is required.
Free tools Windows power users keep installed
One-click scans. No signup required.
Why does my selector work in DevTools but not in Puppeteer?
The element may be inside an iframe, appear after client-side rendering, or use a different URL state. Wait for it, select the correct frame, and confirm the page has reached the expected route before querying.
When should I use a manual clip?
Use clip when you intentionally need a page-coordinate rectangle. For a normal DOM component, an element handle avoids maintaining those coordinates yourself.
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.




