Taking Screenshots with Headless Browsers

Updated June 2026
Headless browsers can capture three types of screenshots: viewport captures (the visible area), full-page captures (the entire scrollable content), and element captures (a specific DOM element). They can also generate PDF documents from web pages. Playwright, Puppeteer, and Selenium all provide built-in screenshot APIs that work in headless mode without any additional dependencies.

Screenshots from headless browsers power a range of production use cases: visual regression testing that detects layout changes between deployments, social media preview image generation, content thumbnailing for CMS platforms, automated report generation, and website monitoring dashboards that display visual snapshots of tracked pages. The quality and reliability of these screenshots depends on proper viewport configuration, timing (waiting for all content to render completely), and understanding the output options each tool provides.

Step 1: Configure the Viewport

The viewport determines the visible area of the browser window. Screenshot dimensions are directly tied to viewport size, so setting this correctly is the first and most important step in any screenshot workflow. If you do not set a viewport, most headless browsers default to 800x600 pixels, which produces small screenshots that trigger mobile or tablet layouts on responsive sites.

In Playwright, set the viewport when creating a browser context: context = browser.new_context(viewport={'width': 1920, 'height': 1080}). You can also set it per-page with page.set_viewport_size({'width': 1920, 'height': 1080}). In Puppeteer, use page.setViewport({ width: 1920, height: 1080 }). In Selenium, set the window size with driver.set_window_size(1920, 1080) or pass --window-size=1920,1080 as a Chrome argument.

Device pixel ratio (DPR) affects screenshot resolution. A DPR of 2 produces screenshots at twice the viewport dimensions in pixels, matching Retina and HiDPI displays. Playwright supports this through browser.new_context(viewport={'width': 1920, 'height': 1080}, device_scale_factor=2). Puppeteer uses page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 }). Higher DPR values produce sharper screenshots but larger file sizes. A 1920x1080 viewport at DPR 2 produces a 3840x2160 pixel image.

For mobile device simulation, both Playwright and Puppeteer provide device descriptor presets that set viewport size, DPR, user agent, and touch capability to match specific devices. Playwright's playwright.devices['iPhone 13'] returns a configuration object you can pass into browser.new_context() to simulate that exact device, producing screenshots that match what users on that device would see, including any responsive layout changes the site applies.

Step 2: Capture a Viewport Screenshot

A viewport screenshot captures exactly what is visible in the browser window at the current scroll position. This is the default screenshot mode in all major automation tools.

In Playwright: await page.screenshot({ path: 'viewport.png' }). In Puppeteer: await page.screenshot({ path: 'viewport.png' }). In Selenium (Python): driver.save_screenshot('viewport.png'). All three produce a PNG image matching the viewport dimensions (multiplied by the device pixel ratio if set above 1).

Playwright and Puppeteer also support JPEG output for smaller file sizes: page.screenshot({ path: 'viewport.jpg', type: 'jpeg', quality: 80 }). JPEG is useful when file size matters more than pixel-perfect accuracy, such as generating thumbnails for a content management system or monitoring dashboard. PNG is preferred for visual regression testing where lossless comparison is important.

Both Playwright and Puppeteer can return the screenshot as a Buffer or bytes object instead of writing to disk: const buffer = await page.screenshot(). This is useful for streaming screenshots directly to a database, object storage service, or HTTP response without creating temporary files on the local filesystem.

Step 3: Capture a Full-Page Screenshot

Full-page screenshots capture the entire scrollable content of the page, not just the visible viewport. This produces tall images for pages with significant content below the fold, sometimes thousands of pixels in height for long-form content pages.

In Playwright: await page.screenshot({ path: 'fullpage.png', fullPage: true }). In Puppeteer: await page.screenshot({ path: 'fullpage.png', fullPage: true }). These commands automatically scroll through the page and stitch the captures together into a single seamless image.

Full-page screenshots have practical limits. Pages with infinite scroll or lazy-loaded content may not render all content in the initial capture because the browser needs to scroll to trigger loading of additional content. For these pages, you may need to script the scrolling behavior first, gradually scrolling down the page and waiting for new content to load at each increment, before capturing the screenshot. A common pattern is to scroll by the viewport height, wait for network idle, and repeat until no new content appears.

Selenium does not have a built-in full-page screenshot method for Chrome. You can work around this by setting the window height to match the full page height: get the scroll height with driver.execute_script('return document.body.scrollHeight'), resize the window with driver.set_window_size(1920, scroll_height), and then take a standard screenshot. Firefox's Selenium driver does support full-page screenshots natively through driver.get_full_page_screenshot_as_file('fullpage.png').

Step 4: Capture an Element Screenshot

Element screenshots capture only a specific DOM element and its contents, cropping the image to that element's bounding box. This is useful for capturing individual components like charts, data tables, product cards, hero sections, or form controls.

In Playwright: element = page.locator('.chart-container') then await element.screenshot({ path: 'chart.png' }). In Puppeteer: const element = await page.$('.chart-container') then await element.screenshot({ path: 'chart.png' }). Both tools handle scrolling to the element if it is not currently visible in the viewport.

In Selenium, element screenshots are supported directly: element = driver.find_element(By.CSS_SELECTOR, '.chart-container') then element.screenshot('chart.png'). The output is cropped to the element's visible area within the viewport.

Element screenshots are particularly valuable for visual regression testing of individual UI components. Rather than comparing full-page screenshots where a change in the header shifts the layout and affects the diff for the entire page, you can capture and compare specific components independently. This produces more focused, actionable test results and reduces false positives from unrelated layout shifts elsewhere on the page.

Step 5: Generate PDF Documents

Headless browsers can render web pages as PDF documents, respecting CSS print styles, page breaks, and print-specific media queries. This capability is used for generating invoices, reports, certificates, resumes, and any document that starts as HTML and needs to be distributed as a printable PDF.

In Playwright: await page.pdf({ path: 'document.pdf', format: 'A4' }). In Puppeteer: await page.pdf({ path: 'document.pdf', format: 'A4' }). Both support options for margins (top, right, bottom, left), custom headers and footers with page numbers, page ranges for partial exports, and landscape orientation. The printBackground: true option includes background colors and images that CSS print styles normally omit by default.

PDF generation is currently a Chromium-only feature in automation tools. Neither Playwright's Firefox or WebKit builds nor Selenium's Firefox driver supports the PDF generation API. If PDF generation is a core requirement for your project, you must use Chromium as your browser engine.

For high-quality PDF output, design your HTML with CSS print styles in mind. Use @media print rules to control page breaks, hide navigation elements, adjust font sizes for print readability, and set appropriate margins. The page-break-before, page-break-after, and page-break-inside CSS properties control where page breaks occur in the document. Chrome's PDF renderer handles these properties well, producing clean, professional documents suitable for business and legal use.

Key Takeaway

Headless browsers support viewport, full-page, and element screenshots in PNG or JPEG format, plus PDF generation from HTML content. Configure the viewport size and device pixel ratio before capturing to ensure consistent, high-quality output across all your screenshots and documents.