Capture a website screenshot in Node.js with Playwright
Capture a viewport, full page, or clipped region with Playwright in Node.js. Configure formats and scale, handle failures, and troubleshoot common capture issues.
Use Playwright’s page.screenshot() after navigating to a page. By default it captures the visible viewport; set fullPage: true to capture the full scrollable page. Playwright can save PNG, JPEG, or WebP files, or return the screenshot bytes as a Node.js Buffer. See the Playwright Page API.
1. Install Playwright and a browser
In an existing Node.js project, install the Playwright library and Chromium:
npm install playwright
npx playwright install chromium
The install command downloads the browser Playwright uses. If your project already has Playwright installed, install the browser required by your environment. This guide uses Chromium; Playwright also supports Firefox and WebKit.
2. Capture a website screenshot
Save this as screenshot.js and run node screenshot.js. The finally block closes the browser even when navigation or screenshot capture throws an error.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
})();
The output path is relative to the current working directory. Without a path, page.screenshot() still returns a Buffer; you can pass those bytes to another library or write them yourself.
3. Capture a full page, viewport, or region
Choose the capture area that matches the task:
| Goal | Option | What it captures |
|---|---|---|
| Visible screen | Default | The current viewport |
| Entire scrollable page | fullPage: true |
The full page, beyond the current viewport |
| Specific rectangle | clip: { x, y, width, height } |
The selected page region |
Full-page capture
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
Full-page capture can produce a very tall image. If a site loads content only as the visitor scrolls, you may need to scroll through it first so lazy-loaded content appears. For a particularly long page, consider whether a viewport capture or a series of clipped captures better suits the consumer of the image.
Capture a clipped region
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 800, height: 500 }
});
The coordinates and dimensions describe a rectangle in page pixels. The requested rectangle must fit within the page; an out-of-bounds clip can fail. Use a locator screenshot instead when the desired area is a specific element, since it follows that element’s bounds:
await page.locator('main article').screenshot({ path: 'article.png' });
4. Choose format, quality, and pixel scale
Playwright supports PNG, JPEG, and WebP. It infers the format from the filename extension when a path is provided; without a path, PNG is the default unless you specify a type.
// JPEG with quality from 0 to 100
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 80 });
// WebP
await page.screenshot({ path: 'page.webp', type: 'webp', quality: 80 });
// Transparent background (PNG; not applicable to JPEG)
await page.screenshot({ path: 'transparent.png', omitBackground: true });
Use PNG when you want lossless output, such as for text-heavy visual comparisons. JPEG and WebP support a quality setting and can reduce file size, with some loss of image fidelity. Use omitBackground: true when a transparent background is useful; it does not apply to JPEG.
The scale option controls output pixels:
// One output pixel per CSS pixel
await page.screenshot({ path: 'css-scale.png', scale: 'css' });
// Device pixels; can create a larger output
await page.screenshot({ path: 'device-scale.png', scale: 'device' });
css keeps the output at one pixel per CSS pixel. device uses device pixels and can substantially increase image dimensions and file size, especially on high-density screens.
5. Make captures more repeatable
For screenshots used in documentation or visual tests, animations, caret blinking, and dynamic regions can cause unnecessary differences. Playwright provides options to control some of this:
await page.screenshot({
path: 'stable.png',
animations: 'disabled',
caret: 'hide',
mask: [page.locator('.live-timestamp')],
style: 'html { scroll-behavior: auto !important; }'
});
animations: 'disabled'disables CSS animations, transitions, and Web Animations according to Playwright’s documented behavior.caret: 'hide'hides a visible text caret.maskcovers the selected locator regions.styleapplies a stylesheet while taking the screenshot.
These settings reduce some incidental variation, but do not guarantee identical pixels across different environments. Playwright notes that output may vary by host operating system, browser version and settings, hardware, power source, and headless mode. Keep the browser and machine environment consistent when comparing screenshots. See Playwright’s visual comparison guidance.
6. Wait for the page you actually want to capture
A screenshot taken immediately after navigation may miss content that appears later. Choose a readiness condition based on the page, rather than adding an arbitrary delay to every capture.
await page.goto('https://example.com');
// Wait for a key element to appear
await page.locator('main').waitFor({ state: 'visible' });
// Or wait briefly for a known client-side update
await page.waitForTimeout(500);
await page.screenshot({ path: 'ready.png', fullPage: true });
A fixed delay is simple but can be too short on a slow page and unnecessarily long on a fast one. Waiting for a selector is usually a clearer signal when the page has a known element that indicates readiness. For content loaded on scroll, scroll the relevant area into view before capturing.
7. Handle screenshot bytes in Node.js
Omit path to get the image bytes as a Buffer. For example, this writes the returned bytes using Node’s filesystem API:
const fs = require('node:fs/promises');
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
const image = await page.screenshot({ fullPage: true, type: 'png' });
await fs.writeFile('page.png', image);
} finally {
await browser.close();
}
})();
This approach is useful when the bytes need to be uploaded, stored, or processed before being written to a local file. If you just need a file, using path is shorter.
8. Use Playwright Test for visual regression
A one-off page.screenshot() saves an image. For screenshot comparison in a test suite, Playwright Test provides expect(page).toHaveScreenshot(). It waits for two consecutive screenshots to match before comparing with the expected snapshot. Use the visual comparison documentation to configure and maintain baselines.
import { test, expect } from '@playwright/test';
test('homepage matches its visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png');
});
This assertion belongs to Playwright Test; it is not a replacement for a standalone capture when the goal is to generate an image file.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API: make one GET request with a URL and receive a screenshot or PDF. Its API documentation lists the request options, and the familiar screenshot parameter names used by other APIs also work.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
10. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
browserType.launch reports a missing executable |
The Playwright package is installed, but its browser binary is not. | Run npx playwright install chromium for this guide’s browser, then retry. |
| Navigation times out | The site is slow, unreachable from the machine, or keeps background connections open. | Check that the URL is reachable. Wait for a page-specific selector or use an appropriate navigation readiness condition instead of waiting for every network connection to end. |
| The screenshot is blank or missing expected content | Capture ran before client-side content appeared, or lazy content has not loaded. | Wait for a relevant visible element. For lazy content, scroll through the page or bring the target into view before capturing. |
| The file is saved somewhere unexpected | A relative path resolves from the process’s current working directory. | Use an absolute path or log process.cwd() to confirm the working directory. |
| Screenshot dimensions are too large | Full-page mode or device-pixel scale can produce a tall or high-resolution image. | Use viewport or clipped capture, set scale: 'css', or choose a compressed format and suitable quality. |
| Clip capture fails | The rectangle is invalid or extends beyond the page. | Check that x, y, width, and height describe a valid region within the page. |
| Visual tests fail across machines | Rendering differs across operating systems, browser versions, settings, or hardware. | Run baseline creation and comparisons in the same environment, and mask genuinely dynamic regions. |
| The browser process stays open after an error | Cleanup did not run after a failed navigation or capture. | Close the browser in a finally block, as in the examples above. |
11. Performance, reliability, and cost
- Image size: Full-page and device-scale captures use more pixels and can take more memory to encode. Use viewport or clipped shots when they answer the need; choose JPEG or WebP with a suitable quality for smaller files.
- Repeatability: Pin the browser and operating environment for visual comparison work. Mask changing timestamps, rotating content, and other regions that do not matter to the comparison.
- Failure handling: Treat navigation and capture as fallible operations. Close the browser in
finally; for repeated captures, handle per-URL errors so one failed page does not prevent unrelated work from finishing. - Cost: A self-hosted Playwright script has no per-screenshot API charge, but you provide the machine, browser installation, execution time, storage, and maintenance. A hosted screenshot API trades browser operations for service pricing. ScreenshotNeo has a free allowance of 1,000 shots monthly and paid plans from $5 for 3,000; see its site for the listed plans.
12. FAQ
Does page.screenshot() return image data?
Yes. It returns a Buffer; adding path also saves the screenshot to that location.
Does full-page capture scroll the page?
fullPage: true captures the full scrollable page. Sites that load content only when scrolled may need a separate scroll step to reveal that content first.
Can I make screenshots pixel-identical on every machine?
No. Browser rendering can vary across environments. Keep the capture and baseline environments consistent to make comparisons more reliable.
Should I use Playwright Test or page.screenshot()?
Use page.screenshot() to create an image. Use Playwright Test’s screenshot assertion when the goal is to compare a page against a visual baseline.


