How to Save Playwright Screenshots as PDFs of Web Pages
Use Playwright’s `page.pdf()` to save a web page as a paginated PDF, or `page.screenshot()` for an image. Learn the print, page-size, color, and stability options that affect the result.
To save a web page as a PDF with Playwright, use page.pdf(). A screenshot is an image: page.screenshot() can capture the full scrollable page, but it does not turn that image into a paginated PDF. By default, page.pdf() renders with print CSS media. If you want the page’s screen styles in the PDF, emulate screen media before exporting.
This guide uses Playwright’s JavaScript API. The examples save files locally and assume Playwright is installed in your project. See the official Page API reference for the documented page and PDF options, and the Screenshots guide for image capture.
1. Install Playwright and save a page as a PDF
In an existing Node.js project, install Playwright and its browser binaries:
npm install playwright
npx playwright install chromium
Save the following as save-page.js, then run node save-page.js. It opens a URL, waits for the page load event, and writes a PDF to page.pdf.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The path option saves the PDF to disk. Without it, page.pdf() returns a buffer that you can pass to another part of your application or write to storage yourself. The browser is closed in a finally block so it is cleaned up even when navigation or export fails.
2. Choose between a PDF and a screenshot
Use the output format that matches the job. PDFs paginate content for reading, printing, or archiving. Screenshots produce raster images that are useful for visual evidence, previews, and image-based comparisons.
| Goal | Playwright API | Useful options |
|---|---|---|
| Save a printable or shareable document | page.pdf() |
format, width, height, margin, pageRanges, printBackground, preferCSSPageSize |
| Capture the visible viewport as an image | page.screenshot() |
type, quality, scale |
| Capture the full scrollable page as one image | page.screenshot() |
fullPage: true |
| Capture a selected rectangular region | page.screenshot() |
clip |
| Check page appearance against a visual baseline | Playwright Test’s toHaveScreenshot() |
Use a stable browser and host environment |
A full-page screenshot is a tall image, not a multipage PDF. If the requirement is “one file per page” or a printable document with paper dimensions and margins, use page.pdf(). For a visual regression assertion, toHaveScreenshot() belongs to Playwright Test; it is not a PDF export API. See the official Visual comparisons guide.
3. Control print and screen styling
PDF export uses print CSS media by default. That means styles inside @media print can apply, and print styles may hide navigation, change layout, or omit decorative elements. If the document should look like the browser’s screen rendering, switch media before exporting:
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'screen-style.pdf', printBackground: true });
To use the default print media, omit emulateMedia() or explicitly set media: 'print'. Inspect the page’s print stylesheet if content is missing or arranged differently than expected.
Backgrounds and exact colors
printBackground: true asks Playwright to include background graphics. It does not disable print styles or guarantee that every color matches the screen. Print rendering modifies colors by default. If exact CSS colors matter, the page’s CSS can use -webkit-print-color-adjust: exact, for example:
@media print {
.report {
-webkit-print-color-adjust: exact;
}
}
Use this deliberately: print color adjustment can affect how a document looks and prints. Check both the PDF’s background graphics setting and its print CSS when colors differ.
4. Set paper size, margins, and page ranges
Choose a named paper format or provide explicit dimensions. You can also set margins, select pages, scale the output, include background graphics, and decide whether CSS @page size takes priority.
await page.pdf({
path: 'report.pdf',
format: 'A4',
margin: {
top: '12mm',
right: '12mm',
bottom: '16mm',
left: '12mm'
},
printBackground: true,
pageRanges: '1-3',
scale: 1,
preferCSSPageSize: false
});
format: a named paper format such asA4. Use either a format or explicit dimensions to define the page size.widthandheight: explicit dimensions. Unlabeled values are pixels; supported units includepx,in,cm, andmm. For example,width: '210mm'.margin: top, right, bottom, and left margins. Use unit-bearing values such as'12mm'or'0.5in'.pageRanges: the page or range to print, such as'1','1-3', or'2,4-6'.printBackground: include background graphics when set totrue.scale: adjust the rendering scale. Keep it within the documented range for your installed Playwright version.preferCSSPageSize: whentrue, honor the page size declared in CSS@pagerather than the PDFformat,width, orheight.landscape: use landscape orientation whentrue.
When your site owns the document layout, CSS can define its intended paper size and page breaks:
@page {
size: A4 landscape;
margin: 12mm;
}
@media print {
.chapter {
break-before: page;
}
}
Set preferCSSPageSize: true if the CSS @page declaration should take precedence over the size supplied in the PDF options. If the output ignores the CSS size, check this setting and confirm that the print stylesheet is active.
5. Save a full-page screenshot as an image
For a full-length image rather than a PDF, set fullPage: true. By default this writes a PNG when saved to a file:
await page.screenshot({ path: 'full-page.png', fullPage: true });
The screenshot API supports image type, lossy quality, clipping, and scale. quality applies to lossy formats such as JPEG; it is not a PDF quality control. scale: 'css' produces one image pixel per CSS pixel, while scale: 'device' uses device pixels and can produce a larger image on high-DPI displays.
// JPEG screenshot of the current viewport
await page.screenshot({ path: 'viewport.jpg', type: 'jpeg', quality: 85 });
// Full page as WebP
await page.screenshot({ path: 'full-page.webp', type: 'webp', fullPage: true });
// Capture a rectangle in page CSS pixels
await page.screenshot({
path: 'chart.png',
clip: { x: 40, y: 120, width: 900, height: 500 },
scale: 'css'
});
Check the API reference for the exact options supported by your installed Playwright version, especially when combining clipping, full-page capture, and image formats.
6. Make the capture reliable
A successful navigation does not always mean the page is ready for a useful export. A page may still be loading images or rendering client-side content. Choose a navigation wait condition that fits the site, and add a targeted readiness check when a known element indicates that the content is ready.
await page.goto('https://example.com/report', { waitUntil: 'load' });
await page.locator('[data-report-ready="true"]').waitFor({ state: 'visible' });
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
Replace the example selector with an element your page actually renders. Avoid waiting indefinitely: set a navigation timeout or a timeout on the readiness wait so a broken page fails with a useful error instead of leaving a worker occupied.
For visual regression work, keep the baseline and comparison environment consistent. Playwright documents that screenshots can vary with the host operating system, browser version, settings, hardware, power source, and headless mode. Its toHaveScreenshot() assertion waits for consecutive screenshots to match before comparing them with the expectation, but that does not make captures from different environments identical.
7. Troubleshoot common PDF and screenshot problems
| Symptom | Likely cause | Fix |
|---|---|---|
| The PDF has print styling instead of the visible screen layout | PDF export uses print media by default | Call await page.emulateMedia({ media: 'screen' }) before page.pdf(). |
| Background colors or graphics are missing | Background printing is disabled | Set printBackground: true; then check the page’s print CSS. |
| Colors look faded or differ from the browser | Print color adjustment or print rules changed them | Inspect print styles, use -webkit-print-color-adjust: exact where appropriate, and enable printBackground for backgrounds. |
| The PDF uses an unexpected page size | CSS @page may be taking precedence, or the requested dimensions may not be in the intended units |
Check preferCSSPageSize, use explicit units such as '210mm', and inspect the page’s @page rule. |
| Content is cut off or split awkwardly | Print layout, page breaks, margins, or scale do not fit the chosen paper size | Review print CSS and page breaks; adjust margins, paper size, or scale and inspect the resulting PDF. |
| The screenshot shows only the viewport | Screenshot capture defaults to the visible page area | Set fullPage: true for a full-page image. Use page.pdf() if you need paginated output. |
| Images or dynamic content are absent | The page had not rendered the content when capture began | Wait for a page-specific ready selector or other explicit condition before exporting. |
| Screenshot assertions differ between machines | Browser or host rendering conditions differ | Generate baselines and comparisons in a consistent operating system, browser version, settings, and execution mode. |
| PDF export throws or the output file is missing | Navigation, browser launch, output path, or permissions failed | Log the caught error, confirm the browser is installed, verify the destination directory exists and is writable, and check that navigation completed. |
8. Performance, reliability, and cost considerations
With Playwright, your application launches and manages a browser, loads each target page, and renders the requested output. For repeated captures, reuse a browser process and create a page or context for each job rather than launching a new browser for every URL. Close pages and contexts after each job, and close the browser when the worker shuts down.
Full-page images can be large because they include the whole scrollable page. Device-pixel screenshots can be larger still on high-DPI displays. Choose viewport capture when the full page is unnecessary, use CSS-pixel scale where suitable, and select a lossy image format and quality only when the output is an image and file size matters. For PDFs, use page ranges when you only need part of a document.
Reliability depends on page readiness and the consistency of the browser environment. A timeout or failed navigation should be handled as a failed capture, not treated as a valid blank document. For visual comparisons, pin the browser and host environment used for baselines and comparisons.
Playwright is an open-source browser automation library; this method has no per-screenshot ScreenshotNeo charge, but your own compute, browser hosting, storage, and operations have costs. If you need a managed screenshot API instead, ScreenshotNeo’s published plans are free for 1,000 shots a month with no card, then Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. For a PDF, use its capture_pdf MCP tool. For a screenshot, one GET request returns PNG, JPEG, or WebP. The API accepts screenshot parameters used by other screenshot APIs, which can make switching easier. See the ScreenshotNeo documentation for API and MCP details.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
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(`ScreenshotNeo returned HTTP ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
The one-call API example returns an image; use the MCP capture_pdf tool when the requested output is a PDF. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
10. Frequently asked questions
Can I convert a Playwright screenshot directly into a PDF?
The APIs serve different purposes: page.screenshot() creates an image, while page.pdf() renders the page as a paginated PDF. Use the PDF API when you want document layout and page controls.
Can I save the PDF to memory instead of a file?
Yes. Omit path from page.pdf() and use the returned PDF buffer in your application.
Does toHaveScreenshot() create a PDF?
No. It is a Playwright Test visual comparison assertion for screenshots. Use page.pdf() to export a PDF.
Which format should I use for a web archive?
Use a PDF when pagination, print dimensions, and document sharing matter. Use a full-page screenshot when you specifically need one raster image of the page’s full scrollable content.


