A Puppeteer plugin for capturing page as a video with ultimate quality
puppeteer-capture uses Chrome's HeadlessExperimental CDP domain to capture web pages as video in a deterministic way. Unlike screencast approaches, this produces frame-perfect, reproducible output.
License: MIT Node.js: >=22.12
src/
├── index.ts → Public API exports (capture, launch)
├── capture.ts → capture() factory function
├── launch.ts → launch() wrapper (enforces chrome-headless-shell)
├── PuppeteerCapture.ts → Main interface (public API contract)
├── PuppeteerCaptureBase.ts → Base class (event emitter, lifecycle)
├── PuppeteerCaptureViaHeadlessExperimental.ts → CDP-based implementation
├── PuppeteerCaptureOptions.ts → Capture options interface
├── PuppeteerCaptureStartOptions.ts → Start options interface
├── PuppeteerCaptureFormat.ts → Output format factory functions
├── PuppeteerCaptureEvents.ts → Event type definitions
├── MissingHeadlessExperimentalRequiredArgs.ts → Error class
└── NotChromeHeadlessShell.ts → Error class
Key classes:
PuppeteerCapture— Main interface, created viacapture(page, options?)PuppeteerCaptureViaHeadlessExperimental— Implementation using CDP'sHeadlessExperimental.beginFrame
Dependencies:
fluent-ffmpeg— Video encodingasync-mutex— Concurrency control for frame capturewhich— ffmpeg binary resolutionffmpeg-static(optional) — Bundled ffmpeg fallback
# Install dependencies
npm ci
# Build (compile TypeScript)
npm run build
# Run tests
npm run test
# Lint (ts-standard)
npm run lintLinting: ts-standard (StandardJS + TypeScript)
Naming:
| Element | Convention | Example |
|---|---|---|
| Files (classes) | PascalCase | PuppeteerCapture.ts |
| Files (functions) | camelCase | capture.ts |
| Classes | PascalCase | PuppeteerCapture |
| Functions | camelCase | capture(), waitForTimeout() |
| Constants | UPPER_SNAKE | — |
Formatting (editorconfig):
- JS/TS/JSON/YAML: 2-space indent
- Max line length: 120
- LF line endings, UTF-8, trailing newline
Format: (type) description
Types: feat, fix, imp, chore, docs
Examples:
(imp) puppeteer v24.6.1(fix) detect closed connection(feat) GitHub Pages(chore) ci: codecov token
Do not add issue numbers to commit messages. Use Closes #N in PR body instead.
- Framework: Jest with ts-jest
- Execution:
npm run test(runs with--runInBand --coverage --detectOpenHandles --forceExit) - Test files: Co-located with source (
*.test.tsinsrc/) - Slow test threshold: 30s (tests involve browser launch)
CI matrix:
- OS: Ubuntu 24.04, Windows 2022
- Node: LTS iron, LTS jod
Integration tests (CI):
- Puppeteer versions: latest patch of each supported (major, minor), 24.3 → 25.1, on Ubuntu + Windows (one entry per minor)
- Browser provisioning: the
npm install --no-save puppeteer@<v>swap runs withPUPPETEER_SKIP_DOWNLOAD=true— its 24.x postinstall otherwise leaves a partial cache folder for<v>'s pinned build that blocks re-download (@puppeteer/browsershas no--force). Browsers are then provisioned vianpx @puppeteer/browsers@latest install chrome@<build>+chrome-headless-shell@<build>at<v>'s pinned build: puppeteer 24.x's own bundled@puppeteer/browsers(2.13.2) downloads but silently fails to extract Chrome on the runners (exit 0, no executable), whereas the current 3.x extracts correctly (it is what the 25.x base uses). A verification step fails the job if the executables are missing. No per-version browser cache. - Run after build job passes
- Ensure
mainis green - Create GitHub Release (tag = semver version, e.g.
1.13.0) - Publish workflow validates tag, runs lint + test, then publishes with provenance
Release notes format (use GitHub's "Generate release notes" style):
## What's Changed
* (type) description by @author in https://github.com/alexey-pelykh/puppeteer-capture/pull/N
**Full Changelog**: https://github.com/alexey-pelykh/puppeteer-capture/compare/PREV_TAG...NEW_TAGPublishing: Uses OIDC trusted publishing (no NPM_TOKEN). The npm-publish GitHub environment
provides deployment protection. Provenance attestation links the published package to its source commit.
Required secrets: CODECOV_TOKEN
Required environments: npm-publish (with deployment protection rules)
- macOS not supported — Chrome's HeadlessExperimental is not available on macOS
--headless=newnot supported — Plugin enforceschrome-headless-shellbinary- Tests require ffmpeg — Resolved via
FFMPEGenv var,PATH, orffmpeg-static
When a new puppeteer version is released, a single atomic commit updates 4 files.
One (major, minor) at a time: When multiple (major, minor) combos are pending (e.g. 24.43 → 25.1), bump one per commit+release. Never skip intermediates (24.43 → 25.0 → 25.1, not 24.43 → 25.1). Exhaust all pending minors of the current major before crossing into the next major. Each (major, minor) gets its own commit, push, and npm release before proceeding to the next.
Commit message: (imp) puppeteer v{VERSION}
devDependencies: Pin bothpuppeteerandpuppeteer-coreto exact new version (no caret)peerDependencies: Append|| ^{major}.{minor}.0on minor or major bumps- Patch bump (e.g. 24.6.0 → 24.6.1): no peerDeps change (existing
^24.6.0covers it) - Minor bump (e.g. 24.5.0 → 24.6.0): add
|| ^24.6.0 - Major bump (e.g. 24.43.1 → 25.0.2): add
|| ^25.0.0
- Patch bump (e.g. 24.6.0 → 24.6.1): no peerDeps change (existing
- Update
PUPPETEER_VERSIONenv var (build job) to new version - Add the new version to the
puppeteer-versionintegration matrix, keeping one entry per (major, minor): append for a new (major, minor); for a patch bump, replace the existing same-minor entry (never two patches of one minor, e.g.24.6.0and24.6.1)
- Update
PUPPETEER_VERSIONenv var in bothvalidateandpublishjobs to new version
- Regenerated by
npm install(npm cifails when lockfile doesn't match updated package.json)
- Create branch
imp/puppeteer-v{VERSION} - Make the 4-file changes + run
npm install - Commit:
(imp) puppeteer v{VERSION} - Open PR, merge to main
- Create GitHub Release (bumps package minor version)
- Issues: https://github.com/alexey-pelykh/puppeteer-capture/issues
- Closing issues: Use
Closes #Nin PR description