Working Playwright code for the screenshots people actually need, the fixes for the usual broken output, and an honest look at when an HTTP API saves you running browsers.
Install with npm i playwright and npx playwright install chromium.
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto("https://example.com", { waitUntil: "networkidle" });
await page.screenshot({ path: "full.png", fullPage: true });
await browser.close();
})();
fullPage: true captures the whole scrollable height. Without it you get just the viewport.
await page.locator("h1").screenshot({ path: "heading.png" });
The locator waits until the element is visible, scrolls it into view and crops to its bounding box. For a region, use page.screenshot({ clip: { x: 0, y: 0, width: 600, height: 300 } }).
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 2, // 2560px wide output
colorScheme: "dark", // prefers-color-scheme: dark
});
const page = await context.newPage();
await page.goto("https://example.com");
await page.screenshot({ path: "shot.jpg", type: "jpeg", quality: 80 });
await page.goto("https://example.com", { waitUntil: "networkidle" });
await page.pdf({ path: "page.pdf", format: "A4", printBackground: true,
margin: { top: "1cm", bottom: "1cm", left: "1cm", right: "1cm" } });
page.pdf() only works in Chromium, and it renders with print CSS (@media print). Add await page.emulateMedia({ media: "screen" }) first if you want it to look like the screen.
fullPage doesn't scroll. Scroll first: await page.evaluate(async () => { for (let y = 0; y < document.body.scrollHeight; y += 600) { window.scrollTo(0, y); await new Promise(r => setTimeout(r, 100)); } window.scrollTo(0, 0); });animations: "disabled" to screenshot(). It fast-forwards finite CSS animations and transitions.style: ".cookie-banner, #onetrust-banner-sdk { display: none !important }" in the screenshot options (Playwright 1.41+), or click "accept" first.style option with position: static !important on the header.apt install fonts-noto-cjk fonts-noto-color-emoji.networkidle never resolves: pages with analytics beacons or websockets never go quiet. Use waitUntil: "load" plus page.waitForSelector() for the content you need.type: "jpeg" with a quality, or compress afterwards.If screenshots are a side feature, you may not want Chromium, fonts and a browser pool in your deployment. Shotmint runs Playwright for you:
// shot.mjs, run with: SHOTMINT_KEY=... node shot.mjs
import { writeFileSync } from "node:fs";
const params = new URLSearchParams({
url: "https://example.com",
full_page: "true",
device_scale_factor: "2",
format: "jpeg",
quality: "80",
block_ads: "true", // also hides common cookie banners
});
const r = await fetch("https://shotmint.creasework.com/v1/screenshot?" + params, {
headers: { "X-API-Key": process.env.SHOTMINT_KEY },
});
if (!r.ok) throw new Error(await r.text());
writeFileSync("full.jpg", Buffer.from(await r.arrayBuffer()));
| Playwright | Shotmint |
|---|---|
viewport: {width, height} | width, height |
fullPage: true | full_page=true (scrolls first, so lazy images load) |
locator(sel).screenshot() | selector=sel |
deviceScaleFactor, colorScheme: "dark" | device_scale_factor, dark_mode=true |
style: ".banner { display: none }" | hide_selectors=.banner, or block_ads=true for ads and cookie banners |
type: "jpeg", quality | format=jpeg (or webp), quality |
page.pdf({format, landscape, printBackground}) | /v1/pdf with format, landscape, print_background |
Keep it for anything interactive: logging in, clicking through flows, filling forms, testing. Also keep it if you render at very high volume, or pages sit on a private network. The API is for when you just need the picture or the PDF. Using Selenium instead? See Selenium screenshots in Python. Want to try it without code? The full-page screenshot tool uses the same renderer.
No signup, no card. Get a key in one click, then buy credits only if you need more (from €5 for 2,000, never expire).
Get a free API key Full API reference