Puppeteer screenshots: full page, elements and reliable capture
Capture viewport, full-page and element screenshots using inline HTML, explicit dimensions and checks for images, fonts and hidden targets.
Use page.screenshot() for the viewport, {fullPage: true} for the document, and ElementHandle.screenshot() for one rendered element. Prepare the content and assets your image is supposed to represent before capturing.
Capture screenshots step by step
Run npm install puppeteer in your project. We will capture the viewport, the whole document and one element. These snippets use the page created by the complete script below, which also supplies the HTML and closes the browser.
1. Set the viewport and device scale
Explicit dimensions make the output easier to predict. Here, the viewport is 800 by 600 pixels at a device scale of one.
await page.setViewport({
width: 800,
height: 600,
deviceScaleFactor: 1,
});
2. Prepare the document
The inline CSS makes the document taller than the viewport and gives the card a fixed size. Wait for font loading before capturing.
await page.setContent(`
<style>
html, body {
margin: 0;
background: white;
}
body {
height: 1800px;
}
#card {
width: 240px;
height: 120px;
background: #dbeafe;
}
</style>
<div id="card">A captured element</div>
`);
await page.evaluate(() => document.fonts.ready);
3. Capture the viewport and full document
The first call captures the visible viewport. Adding fullPage: true captures the document’s full height; it does not load infinite-scroll content.
await page.screenshot({
path: "viewport.png",
});
await page.screenshot({
path: "full-page.png",
fullPage: true,
});
4. Capture one visible element
Wait for the card, capture through its handle, and dispose of that handle in finally. The complete script separately closes the browser.
const card = await page.waitForSelector("#card", {
visible: true,
timeout: 5000,
});
if (!card) {
throw new Error("Required card absent");
}
try {
await card.screenshot({
path: "card.png",
});
} finally {
await card.dispose();
}
Complete script
Here are all the pieces together, including setup and cleanup. Save this as screenshots.mjs and run node screenshots.mjs.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
});
try {
const page = await browser.newPage();
await page.setViewport({
width: 800,
height: 600,
deviceScaleFactor: 1,
});
await page.setContent(`
<style>
html, body {
margin: 0;
background: white;
}
body {
height: 1800px;
}
#card {
width: 240px;
height: 120px;
background: #dbeafe;
}
</style>
<div id="card">A captured element</div>
`);
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
path: "viewport.png",
});
await page.screenshot({
path: "full-page.png",
fullPage: true,
});
const card = await page.waitForSelector("#card", {
visible: true,
timeout: 5000,
});
if (!card) {
throw new Error("Required card absent");
}
try {
await card.screenshot({
path: "card.png",
});
} finally {
await card.dispose();
}
} finally {
await browser.close();
}
With this layout and device scale, viewport.png is 800×600, full-page.png is 800×1800 and card.png is 240×120. Dimensions can change with device scale or layout. For a known crop, use a positive clip rectangle; do not combine clip and fullPage.
For a URL, replace setContent() with a validated navigation and wait for the actual application result. See the tutorial and navigation response policy.
Fonts and images
Navigation completion does not prove visual assets succeeded. Inside a live page lifecycle, decode the current document’s images before capture:
await page.evaluate(async () => {
await document.fonts.ready;
for (const image of document.images) {
await image.decode();
if (!image.naturalWidth) {
throw new Error(`Broken image: ${image.src}`);
}
}
});
This checks current image elements, not later inserted assets or CSS backgrounds. document.fonts.ready does not guarantee the preferred font is installed. Add your application’s meaningful visual-ready condition and bound preparation time when running a service.
Lazy content and animation
fullPage is not an infinite-scroll loader. If scrolling triggers more content, use a finite step/count/time/height limit and an explicit end condition. Stop rather than build an unbounded screenshot. Return to the intended scroll position before a viewport capture.
Prefer a deterministic application state or wait for real animation completion. Disabling animation can be appropriate in a test-owned page, but may change behavior on a real application. Do not prescribe one universal sleep. See waiting.
Formats and transparency
PNG is lossless and does not support quality. JPEG/WebP accept quality from 0 to 100. Set the intended type deliberately.
omitBackground: true can produce transparency where supported. It is not supported with WebDriver BiDi.
Diagnose missing or incorrect output
Check the response/final URL, required content, viewport/device scale, image loading and positive element geometry. Hidden nodes and stale handles need targeted diagnosis, not a browser-side DOM click workaround. See clickable node geometry and detached frames.
Screenshots can differ across fonts, browsers and operating systems. For printed multi-page output, use PDF generation.