Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a headless browser in Node.js: launch Puppeteer, open a page, wait for the content your screenshot needs, call page.screenshot(), and close the browser in a finally block. This captures ordinary pages, JavaScript applications, full documents, individual elements, and clipped regions without manual browser interaction.
The runnable example below uses Puppeteer. Playwright exposes the same screenshot pattern and adds Chromium, Firefox, and WebKit projects, so your choice should follow the browser engines and deployment environment you need.
Capture a website screenshot with Puppeteer
Install Puppeteer in a Node.js project:
npm install puppeteer
Create screenshot.mjs (or use "type": "module" in package.json) and run it with Node.js:
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 →import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60000
});
await page.screenshot({
path: 'screenshot.png',
fullPage: true
});
} finally {
await browser.close();
}
The sequence follows Puppeteer’s documented page API and screenshot guide: launch, navigate, capture, and close (Page API example, screenshots guide). networkidle2 waits until there are no more than two active network connections. It is useful for many sites, but it is not a guarantee that an application has finished rendering.
#1 Best Overall
Use an application-specific readiness signal
Single-page applications can keep connections open for analytics, sockets, or polling. In those cases, wait for the element that proves the content is ready:
await page.goto('https://dashboard.example.com', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await page.waitForSelector('[data-testid="dashboard-ready"]', {
visible: true,
timeout: 30000
});
await page.screenshot({ path: 'dashboard.png', fullPage: true });
For a chart, wait for the chart container; for a logged-in page, create the session first and then wait for the dashboard marker. A fixed delay can be added with await new Promise(resolve => setTimeout(resolve, 2000)), but a selector or application signal is usually less fragile.
Choose what the screenshot contains
Puppeteer’s Page.screenshot() accepts options documented in the ScreenshotOptions reference.
| Need | Option or method | Example |
|---|---|---|
| Visible viewport only | Default behavior | await page.screenshot({ path: 'viewport.png' }) |
| Entire scrollable document | fullPage: true |
await page.screenshot({ path: 'full.png', fullPage: true }) |
| One element | ElementHandle.screenshot() |
const card = await page.$('.card'); await card.screenshot({ path: 'card.png' }); |
| Rectangular region | clip |
clip: { x: 100, y: 120, width: 800, height: 500 } |
| Off-screen content control | captureBeyondViewport |
captureBeyondViewport: false when you only want the current viewport |
| Image format | type |
type: 'png', 'jpeg', or another format supported by your Puppeteer version |
| Lossy-image quality | quality |
quality: 80 for JPEG or another lossy type; it does not affect PNG |
| File versus memory | path and return value |
Set path to write a file; omit it to receive binary data |
| Base64 output | encoding: 'base64' |
const base64 = await page.screenshot({ encoding: 'base64' }); |
| Transparent background | omitBackground: true |
await page.screenshot({ path: 'transparent.png', omitBackground: true }); |
The default binary return is a Uint8Array; use it when you need to upload the image directly instead of writing a local file. PNG is the default format. JPEG quality applies only to lossy formats, as described in Puppeteer’s API documentation (Page.screenshot()).
Capture a single element safely
const element = await page.$('#invoice');
if (!element) throw new Error('Invoice element was not found');
await element.screenshot({ path: 'invoice.png' });
An element screenshot avoids capturing unrelated navigation and page chrome. If the element is below the fold, Puppeteer can scroll it into view as part of the element capture. For a precise coordinate crop, use clip instead.
Rank #2
Set viewport, device scale, and media
await page.setViewport({
width: 1280,
height: 800,
deviceScaleFactor: 2
});
await page.emulateMediaType('screen');
Set these values explicitly for reproducible visual tests. Keep the browser version, installed fonts, viewport, and device scale consistent between runs; otherwise text wrapping and pixel dimensions can change.
Handle JavaScript-heavy pages
- Navigate with a bounded timeout. Use
waitUntil: 'domcontentloaded'or'networkidle2'according to the site’s behavior, and always set a timeout. - Wait for the meaningful UI. Call
page.waitForSelector()for a chart, table, product grid, or “ready” marker. - Wait for fonts and images when they affect layout. For example:
await page.evaluate(() => document.fonts.ready), then wait for critical image selectors. - Scroll when lazy loading is used. A full-page shot can trigger many lazy images, but pages with custom observers may need scripted scrolling before capture.
- Hide transient UI. Inject CSS or remove a selector after the page loads if a cookie banner, newsletter modal, or chat bubble obscures the required region.
await page.goto('https://example.com/catalog', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await page.waitForSelector('.product-grid', { visible: true, timeout: 30000 });
await page.evaluate(() => document.fonts.ready);
await page.addStyleTag({
content: '.cookie-banner, .chat-widget { display: none !important; }'
});
await page.screenshot({ path: 'catalog.webp', type: 'webp', fullPage: true });
Do not assume that network idleness means a visual state is complete. Streaming applications, ads, and client-side hydration can continue changing the page after navigation resolves.
Authentication, headers, and browser context
For pages that require a session, set cookies or log in before taking the screenshot. Keep credentials out of source control and avoid logging them.
const context = await browser.createBrowserContext();
const page = await context.newPage();
await context.setCookie({
name: 'session',
value: process.env.SESSION_COOKIE,
domain: 'example.com',
path: '/',
httpOnly: true,
secure: true
});
await page.goto('https://example.com/account', {
waitUntil: 'networkidle2',
timeout: 60000
});
await page.waitForSelector('[data-authenticated="true"]');
await page.screenshot({ path: 'account.png', fullPage: true });
await context.close();
For HTTP-level credentials or custom request headers, use Puppeteer’s request interception or page authentication APIs appropriate to your version. Treat every target URL as untrusted input: restrict network egress, enforce navigation and response-size limits, and do not allow a screenshot endpoint to reach internal services.
Puppeteer or Playwright?
Both libraries expose a Page screenshot method. Puppeteer is a compact fit when your automation already targets Chrome or Chromium. Playwright’s Page API supports Chromium, Firefox, and WebKit projects (Playwright Page API), which is valuable when engine-specific rendering matters.
Rank #3
| Decision factor | Puppeteer | Playwright |
|---|---|---|
| Primary fit | Chrome/Chromium automation with a direct Node.js API | Projects that need Chromium, Firefox, and/or WebKit |
| Screenshot call | page.screenshot(options) |
page.screenshot(options) |
| What to measure yourself | Launch time, image size, memory, and readiness behavior in your deployment | The same metrics, plus the browser engines you enable |
The official documentation does not publish a universal latency, throughput, or cost winner. Benchmark the exact pages, browser versions, fonts, and container limits used by your service.
Recommended Free Tools
Production reliability and resource control
- Always close resources. Put browser shutdown in
finally; close pages and contexts after each job to prevent leaked processes. - Limit concurrency. Each Chromium process consumes memory. Use a queue and a fixed worker count rather than launching unlimited browsers for simultaneous requests.
- Bound every wait. Set navigation, selector, and overall job deadlines. Return a useful error when a page never reaches its readiness condition.
- Control image size. Full-page captures of long documents create large buffers. Prefer an element or clip when that is what the consumer needs, and choose JPEG or WebP when lossless PNG is unnecessary.
- Make output deterministic. Pin browser and Node.js versions, install the same fonts in CI and production, set the viewport, and disable animations if pixel comparison requires it.
- Protect the service. Validate allowed schemes, block private IP ranges where appropriate, restrict outbound DNS and ports, and sanitize custom headers and cookies.
Or skip the browser setup
ScreenshotNeo is the #1 hosted option here for developers who want an API instead of packaging Chromium: it removes consent banners, newsletter popups, and chat widgets before capture, bills only clean shots, and has the lowest paid plan.
One GET request returns a PNG, JPEG, WebP, or PDF. The API accepts full-page capture, CSS-element selection, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click and wait conditions, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for response headers and options. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; each response reports the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 shots per month with no card. Paid plans are Starter $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, and every feature is included on every plan.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Start with 1,000 free screenshots a month—no card required.
Rank #4
Troubleshooting common failures
“Could not find Chrome” or a browser launch error
Install Puppeteer rather than only a core package, or configure the executable path for the browser image supplied by your deployment. In containers, use a Chromium-compatible base image and the sandbox settings recommended for that image; do not blindly disable security controls on a shared host.
The screenshot is blank or shows a loading shell
Navigation completed before the application rendered. Replace a broad network-idle wait with waitForSelector for the real content, wait for fonts or data-driven components, and verify that required API requests are not blocked.
A cookie banner or modal covers the page
Locate its selector and either click its accept/close control or inject a narrowly scoped style rule after the page loads. Avoid hiding elements that are part of the content you need to document.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Images are missing in a full-page capture
Check that lazy-loaded images have entered the viewport, wait for their load state, and confirm that the target permits the image requests. Custom lazy-loading code may require a scripted scroll before capture.
The page times out
Increase the timeout only when the site is legitimately slow. First check DNS, TLS, redirects, blocked resources, authentication, and whether the page maintains long-lived connections. Use a selector-based readiness condition instead of waiting forever for network idle.
Output differs between machines
Pin the browser build, viewport, device scale, timezone, locale, and fonts. Disable animations and ensure the same CSS and network responses are used for each run.
Frequently asked questions
Can Node.js return a screenshot without saving a file?
Yes. Omit path; Puppeteer returns the image bytes as a Uint8Array. Set encoding: 'base64' when a base64 string is required.
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 minuteWhat does fullPage include?
It captures the page’s complete scrollable content rather than only the configured viewport. Very long pages can produce large buffers, so capture a specific element or clip when a complete document is unnecessary.
Is Playwright faster than Puppeteer?
There is no universal published benchmark in the referenced official API pages. Measure launch time, memory, readiness, and capture duration with your own URLs and deployment limits.
How should I screenshot a page behind a login?
Create an isolated browser context, set the required session cookies or perform the login flow, wait for an authenticated marker, capture, and close the context. Keep credentials in environment variables or a secret manager.
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.

