Free tools Windows power users keep installed
One-click scans. No signup required.
Use Puppeteer’s Page.screenshot() method and pass a path, such as await page.screenshot({ path: 'screenshot.png' }). The file is written relative to the Node.js process’s current working directory unless you provide an absolute path. The complete setup, format options, full-page and element captures, troubleshooting, and a no-browser alternative are below.
Minimal working script
Install Puppeteer, launch a browser, navigate to a URL, save the image, and always close the browser. This follows the sequence in the official Screenshots guide and the Page reference.
- Create a project and install Puppeteer:
mkdir shot-demo && cd shot-demo, thennpm init -yandnpm install puppeteer. - Save this as
capture.mjs:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
- Run
node capture.mjs. You should findscreenshot.pngin the directory from which you ran the command.
Puppeteer documents Page.screenshot() as the primary page-capture method. Supplying path writes the image directly to disk; omitting it leaves the image in memory instead. See the Page.screenshot() API.
Where the file is written
A relative path is resolved against process.cwd(), the process’s current working directory, not necessarily the folder containing your script. This distinction matters in Docker containers, CI jobs, npm scripts, and services started by a process manager. Use an absolute path when the destination must be deterministic.
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 →#1 Best Overall
import path from 'node:path';
const output = path.resolve(process.cwd(), 'artifacts', 'home.webp');
await page.screenshot({ path: output, type: 'webp' });
Create the destination directory before capturing; Puppeteer does not create missing parent directories for you.
import fs from 'node:fs/promises';
await fs.mkdir('artifacts', { recursive: true });
await page.screenshot({ path: 'artifacts/home.png' });
The extension determines the image type when you use path. The ScreenshotOptions reference documents PNG as the default.
Choose the capture scope and output
| Goal | Option or method | What it does |
|---|---|---|
| Visible viewport | page.screenshot({ path }) |
Captures the currently visible viewport. |
| Entire document | fullPage: true |
Extends the capture to the full scrollable page. |
| Rectangular region | clip: { x, y, width, height } |
Captures only the specified viewport coordinates. |
| One DOM element | elementHandle.screenshot({ path }) |
Scrolls the element into view and captures its bounds. |
| Transparent background | omitBackground: true |
Hides the default page background where transparency is supported. |
| Image format | type: 'png' | 'jpeg' | 'webp' |
Selects the encoded output format; matching the filename extension avoids confusion. |
| JPEG/WebP compression | quality: 0–100 |
Controls quality for formats that support it; it does not apply to PNG. |
Full-page capture
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
Very long pages can require more memory and time than a viewport shot, especially when images are lazy-loaded or animated. Wait for the page state you need before calling the method.
Capture a clipped region
await page.screenshot({
path: 'hero.webp',
type: 'webp',
clip: { x: 0, y: 0, width: 1280, height: 720 }
});
The clip rectangle uses CSS pixels in the page’s viewport. Set the viewport explicitly when reproducible dimensions matter:
Rank #2
await page.setViewportSize?.({ width: 1280, height: 720 });
If your Puppeteer version does not expose that helper, set the viewport when creating the page with await page.setViewport({ width: 1280, height: 720 }).
Capture one element
const card = await page.waitForSelector('.pricing-card');
if (!card) throw new Error('Pricing card not found');
await card.screenshot({ path: 'pricing-card.png' });
ElementHandle.screenshot() scrolls the element into view if necessary. It throws when the element has been detached from the DOM; re-query the selector after page updates. Details are in the ElementHandle screenshot API.
Control timing so the saved image is complete
page.goto() finishing only means the selected navigation condition was reached. Modern pages may still render fonts, images, charts, or client-side data afterward. Combine a navigation condition with a specific readiness check.
await page.goto('https://example.com/dashboard', { waitUntil: 'networkidle2' });
await page.waitForSelector('[data-ready="true"]');
await page.screenshot({ path: 'dashboard.png', fullPage: true });
For a known animation or delayed widget, use a deliberate delay:
await new Promise(resolve => setTimeout(resolve, 1000));
await page.screenshot({ path: 'after-delay.png' });
Prefer a selector that proves the content is ready over an arbitrary sleep. You can also disable motion with CSS when visual stability is more important than animation fidelity:
await page.addStyleTag({
content: '* { animation: none !important; transition: none !important; }'
});
Return image data instead of writing immediately
path is the simplest disk workflow, but the API can return image data. According to the API reference, the default return value is a Promise<Uint8Array>. With encoding: 'base64', it is a string.
const bytes = await page.screenshot({ type: 'png' });
await fs.writeFile('from-bytes.png', bytes);
const base64 = await page.screenshot({ encoding: 'base64', type: 'jpeg', quality: 80 });
await fs.writeFile('image.txt', base64, 'ascii');
Use returned bytes when you need to upload to object storage, attach an HTTP response, hash the image, or process it without a temporary file. Use path when a conventional artifact on disk is the desired result.
PDF output is a separate operation
A screenshot produces an image. For a paginated document, use Puppeteer’s PDF functionality rather than expecting Page.screenshot() to create a PDF. Keep the output extension aligned with the operation so downstream tools do not misidentify the file.
Rank #4
Reliable scripts in CI and production
- Wrap the browser lifetime in
try/finallyso a navigation or screenshot exception does not leave Chromium processes running. - Use absolute output paths or log
process.cwd()when an artifact cannot be found. - Set explicit viewport dimensions, color scheme, locale, and timezone when pixel comparisons must be repeatable.
- Wait for a content-specific selector and fonts or images that affect the result; do not assume network idle means every visual is final.
- Give each concurrent page its own output filename. Do not let workers overwrite the same path.
- Remember that screenshot operations can affect scheduling within a browser context. The Page API remarks note that creating or closing a page in the same context waits for an in-progress screenshot, while
bringToFront()does not wait for existing screenshot operations.
Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| No file appears | The relative path points to a different working directory, or the parent directory is missing. | Print process.cwd(), use an absolute path, and create the directory with fs.mkdir(..., { recursive: true }). |
ENOENT while saving |
The destination directory does not exist. | Create it before page.screenshot(); Puppeteer writes the file but does not create all parent folders. |
| Element screenshot says the handle is detached | The framework re-rendered and replaced the element. | Call waitForSelector again immediately before elementHandle.screenshot(). |
| Blank or partially rendered image | Capture happened before client-side content, fonts, or images were ready. | Wait for a readiness selector, required requests, or a short targeted delay; then capture. |
| Full-page image is unexpectedly short | The page’s content was still loading or a scroll-triggered section had not rendered. | Wait for the final content marker and, if needed, scroll through the page before the full-page capture. |
| Wrong format or quality setting | The filename extension and type disagree, or quality was applied to PNG. |
Use matching values such as path: 'shot.webp', type: 'webp'; quality affects JPEG/WebP, not PNG. |
| Chromium fails to launch in a restricted runner | The execution environment lacks required sandbox permissions or browser dependencies. | Install the dependencies recommended for your operating system and configure the runner’s Chromium sandbox policy according to its security requirements; do not disable protections blindly. |
Or skip the browser setup
If you need a URL turned into an image or PDF without maintaining Chromium code, ScreenshotNeo is the alternative to try first: it produces clean shots, bills only clean captures, and its paid entry plan is $5 for 3,000 shots.
One GET request is enough. The ScreenshotNeo API documentation has the complete parameter reference.
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}`);
ScreenshotNeo accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and every response reports its result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Beyond the basic URL call, the service supports full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for selectors/delays/network idle, blocking ads/trackers/requests/resource types, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, user-selected cache TTLs, signed links for public <img> tags, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.
| Plan | Allowance | Price |
|---|---|---|
| Free | 1,000 shots/month | Free, no card |
| Starter | 3,000 shots | $5 |
| Growth | 15,000 shots | $15 |
| Pro | 60,000 shots | $39 |
| Scale | 250,000 shots | $99 |
| Business | 1,000,000 shots | $249 |
Yearly billing provides two months free, and every feature is included on every plan. Start with 1,000 free screenshots a month—no card required.
Best Value
Frequently Asked Questions
Can Puppeteer save a screenshot without a filename extension?
It can write to a path, but using an extension such as .png, .jpeg, or .webp makes the inferred format and downstream file handling unambiguous.
Does taking a screenshot change the page?
The capture itself reads the rendered page. However, page creation and page closing in the same browser context can wait for an in-progress screenshot, so coordinate those operations when running concurrent jobs.
What should I use for a single component in a test?
Wait for the component’s selector, obtain its current element handle, and call elementHandle.screenshot({ path }); this captures the element rather than the whole viewport.
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.




