SnapDOMGitHub8K
Puppeteer integration

SnapDOM vs Puppeteer

Puppeteer supplies the browser process, page lifecycle and network control for a Node capture worker. Running SnapDOM inside that page adds DOM-derived SVG, plugins and reusable exports.

TL;DR

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 responsibilityPuppeteerSnapDOM in the page
Launch or connect to a browserYesNo
Navigate, authenticate and intercept requestsYesNo
Viewport or full-page pixelsPage.screenshot()Capture the document element
Element pixelsElementHandle.screenshot()DOM subtree export
Raw SVGPixel screenshotresult.url
Several formats from one DOM passSeparate screenshot callsOne CaptureResult
Capture-pipeline pluginsNo capture hooksLifecycle 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.

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