puppeteer-capture v1.10.0
puppeteer-capture
A Puppeteer plugin for capturing page as a video with ultimate quality.
This project is brought to you by Alexey Pelykh.
Under The Hood
HeadlessExperimental is used to
capture frames in a deterministic way. This approach allows to achieve better quality than using screencast.
Getting Started
const { capture, launch } = require('puppeteer-capture')
(async () => {
const browser = await launch()
const page = await browser.newPage()
const recorder = await capture(page)
await page.goto('https://google.com', {
waitUntil: 'networkidle0',
})
await recorder.start('capture.mp4')
await recorder.waitForTimeout(1000)
await recorder.stop()
await recorder.detach()
await browser.close()
})()Time flow
The browser is running in a deterministic mode, thus the time flow is not real time. To wait for a certain amount of
time within the page's timeline, PuppeteerCapture.waitForTimeout() must be used:
await recorder.waitForTimeout(1000)Known Issues
--headless=new is not supported
Sadly, it is so. For Puppeteer v23+, the plugin enforces use
of the chrome-headless-shell binary.
Bad Chrome versions
117.0.5938.88(default forpuppeteerversion(s)21.3.0): reacts withtargetCrashed117.0.5938.92(default forpuppeteerversion(s)21.3.2…21.3.6): reacts withtargetCrashed117.0.5938.149(default forpuppeteerversion(s)21.3.7…21.3.8): reacts withtargetCrashed118.0.5993.70(default forpuppeteerversion(s)21.4.0…21.4.1): reacts withtargetCrashed119.0.6045.105(default forpuppeteerversion(s)21.5.0…21.7.0): reacts withtargetCrashed120.0.6099.109(default forpuppeteerversion(s)21.8.0): reacts withtargetCrashed
MacOS is not supported
Unfortunately, it is so.
No capturing == Nothing happens
This relates to timers, animations, clicks, etc. To process interaction with the page, frame requests have to be submitted and thus capturing have to be active.
Setting defaultViewport causes rendering to freeze
The exact origin of the issue is not yet known, yet it's likely to be related to the deterministic mode.
Calling page.setViewport() before starting the capture behaves the same, yet calling it after starting the capture
works yet not always. Thus it's safe to assume that there's some sort of race condition, since adding
recorder.waitForTimeout(100) just before setting the viewport workarounds the issue.
Also it should be taken into account that since frame size is going to change over the time of the recording, frame size autodetection will fail. To workaround this issue, frame size have to be specified:
const recorder = await capture(page, {
size: `${viewportWidth}x${viewportHeight}`,
})
await recorder.start('capture.mp4', { waitForFirstFrame: false })
await recorder.waitForTimeout(100)
await page.setViewport({
width: viewportWidth,
height: viewportHeight,
deviceScaleFactor: 1.0,
})A friendlier workaround is enabled by default: recorder.start() automatically waits for the first frame to be
captured. This approach seems to allow bypassing the alleged race condition:
const recorder = await capture(page, {
size: `${viewportWidth}x${viewportHeight}`,
})
await recorder.start('capture.mp4')
await page.setViewport({
width: viewportWidth,
height: viewportHeight,
deviceScaleFactor: 1.0,
})Multiple start()/stop() fail
It's unclear why, yet after disabling and re-enabling the capture, callbacks from browser stop arriving.
Time-related functions are affected
The following functions have to be overriden with injected versions:
setTimeout&clearTimeoutsetInterval&clearIntervalrequestAnimationFrame&cancelAnimationFrameDate()&Date.now()performance.now()
The injection should happen before page content loads:
const recorder = await capture(page) // Injection happens here during attach()
await page.goto('https://google.com') // Possible capture would happen here, thus injected versions would be capturedEvents
PuppeteerCapture supports following events:
captureStarted: capture was successfully startedframeCaptured: frame was capturedframeCaptureFailed: frame capture failedframeRecorded: frame has been submitted toffmpegcaptureStopped: capture was stopped
Dependencies
ffmpeg
It is resolved in the following order:
FFMPEGenvironment variable, should point to the executable- The executable that's available via the
PATHenvironment variable - Via
@ffmpeg-installer/ffmpeg, if it's installed as dependency