Use Page.screenshot() or ElementHandle.screenshot() for a direct pixel capture. Inject SnapDOM when the worker needs raw SVG, in-page redaction or watermarking, several formats from one capture, or output that follows the same code path as a client-side export.
What each layer owns
Puppeteer launches or connects to a browser, opens URLs, authenticates, controls requests and chooses when to capture. Its screenshot APIs read the browser’s rendered surface and return a pixel buffer.
SnapDOM executes in the page. It clones a selected element, carries over its resolved styles and resources, and creates an SVG data URL. The same CaptureResult can then produce raster output without rebuilding the DOM capture.
| Worker responsibility | Puppeteer | SnapDOM in the page |
|---|---|---|
| Launch or connect to a browser | Yes | No |
| Navigate, authenticate and intercept requests | Yes | No |
| Viewport or full-page pixels | Page.screenshot() | Capture the document element |
| Element pixels | ElementHandle.screenshot() | DOM subtree export |
| Raw SVG | Pixel screenshot | result.url |
| Several formats from one DOM pass | Separate screenshot calls | One CaptureResult |
| Capture-pipeline plugins | No capture hooks | Lifecycle hooks and exporters |
A direct Puppeteer screenshot
This is the shortest path to an element PNG. Replace the sample data-ready attribute with the signal your application exposes after its data and layout settle.
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 })
await page.goto('https://example.com/report', { waitUntil: 'networkidle2' })
const report = await page.waitForSelector('#report[data-ready="true"]')
await report.screenshot({ path: 'report.png' })
await browser.close()
For the entire scrollable document, call page.screenshot({ path: 'page.png', fullPage: true }). Puppeteer lists both page and element forms in its screenshot guide.
Create SVG and PNG in one Puppeteer page
Set CSP bypass before navigation when the target policy would block the injected bundle. The evaluated function waits for fonts, captures the element once, and returns strings that Puppeteer can serialize back to Node.
import puppeteer from 'puppeteer'
import { writeFile } from 'node:fs/promises'
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setBypassCSP(true)
await page.goto('https://example.com/report', { waitUntil: 'networkidle2' })
await page.waitForSelector('#report[data-ready="true"]')
await page.addScriptTag({
url: 'https://cdn.jsdelivr.net/npm/@zumer/snapdom/dist/snapdom.js'
})
const artifacts = await page.$eval('#report', async element => {
await document.fonts.ready
const capture = await window.snapdom(element, { embedFonts: true })
const canvas = await capture.toCanvas({ dpr: 2 })
return {
svgURL: capture.url,
pngURL: canvas.toDataURL('image/png')
}
})
const svgData = artifacts.svgURL.slice(artifacts.svgURL.indexOf(',') + 1)
const pngData = artifacts.pngURL.slice(artifacts.pngURL.indexOf(',') + 1)
await Promise.all([
writeFile('report.svg', decodeURIComponent(svgData)),
writeFile('report.png', Buffer.from(pngData, 'base64'))
])
await browser.close()
The browser bundle exposes window.snapdom. A local or air-gapped worker can pass the installed IIFE bundle through addScriptTag({ content }) instead of fetching the CDN URL. Puppeteer documents Page.addScriptTag() and notes that setBypassCSP() must run before navigation because CSP is initialized while the page loads.
Readiness and worker operations
networkidle2 is a network checkpoint. A WebSocket app can be ready while the network remains active, and a quiet page can still be waiting for a chart animation or client-side state transition. Prefer an application-specific selector or JavaScript condition, then wait for fonts before either capture path.
- Pin the Puppeteer and browser versions used by the worker so output does not drift between deployments.
- Use the full
puppeteerpackage when its managed browser download fits the deployment; usepuppeteer-corewhen the service supplies or connects to the browser. - Keep authentication, request policy, timeouts and browser-process recycling in the Puppeteer layer.
- Keep DOM capture options and plugins inside the page, next to
window.snapdom.
Current Puppeteer releases support Chrome and Firefox; the protocols and supported versions differ. Check the project’s supported-browser table when selecting a worker image.
Frequently asked questions
Can SnapDOM run in a Node service?
SnapDOM needs browser DOM APIs. A Node service can launch a Puppeteer page, inject the browser bundle, call SnapDOM inside page evaluation and return a data URL to Node.
When is Page.screenshot() the better Puppeteer API?
Use Page.screenshot() or ElementHandle.screenshot() for a direct pixel capture of a page, viewport or element. Use SnapDOM in the page for SVG, capture plugins, several exports from one capture or parity with a client-side export.
Does networkidle2 mean the page is ready to capture?
It only describes recent network activity. Wait for an application-specific ready signal and for document.fonts.ready before taking either kind of capture.
Run DOM capture in your page
Install SnapDOM in the application, or inject the browser bundle from a Puppeteer worker.
Read the API docsInstall from npm