@axelboberg/puppeteer-screen-recorder
Version:
A puppeteer Plugin that uses the native chrome devtool protocol for capturing video frame by frame. Also supports an option to follow pages that are opened by the current page object
297 lines • 27 kB
JavaScript
import fs from 'fs';
import { dirname } from 'path';
import test from 'ava';
import puppeteer from 'puppeteer';
import { PuppeteerScreenRecorder } from '../';
test('case 1a --> Happy Path: Should be able to create a new screen-recording session', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase1.mp4';
const recorder = new PuppeteerScreenRecorder(page);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 1a --> Happy Path: Should be able to create a new screen-recording session using mp4 format', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase1.mp4';
const recorder = new PuppeteerScreenRecorder(page);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 1b --> Happy Path: Should be able to create a new screen-recording session using mov', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase1.mov';
const recorder = new PuppeteerScreenRecorder(page);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 1c --> Happy Path: Should be able to create a new screen-recording session using webm', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase1.webm';
const recorder = new PuppeteerScreenRecorder(page);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 1d --> Happy Path: should be to get the total duration of recording using avi', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase2.avi';
const recorder = new PuppeteerScreenRecorder(page);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
const duration = recorder.getRecordDuration();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
console.log('duration', duration);
assert.is(duration !== '00:00:00:00', true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 2 --> Happy Path: testing video recording with video frame width, height and aspect ratio', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const options = {
followNewTab: false,
videoFrame: {
width: 1024,
height: 1024,
},
aspectRatio: '4:3',
};
const outputVideoPath = './test-output/test/video-recorder/testCase4.mp4';
const recorder = new PuppeteerScreenRecorder(page, options);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('test 3 -> Error Path: should throw error if an invalid savePath argument is passed for start method', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
try {
const outputVideoPath = './test-output/test/video-recorder/';
const recorder = new PuppeteerScreenRecorder(page);
await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
await recorder.stop();
}
catch (error) {
assert.true(error.message === 'File format is not supported');
}
});
test('case 4 --> Happy Path: Should be able to create a new screen-recording session using streams', async (assert) => {
/** setup */
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env['PUPPETEER_EXECUTABLE_PATH'],
});
const page = await browser.newPage();
const outputVideoPath = './test-output/test/video-recorder/testCase4.mp4';
try {
fs.mkdirSync(dirname(outputVideoPath), { recursive: true });
}
catch (e) {
console.error(e);
}
const fileWriteStream = fs.createWriteStream(outputVideoPath);
const recorder = new PuppeteerScreenRecorder(page);
await recorder.startStream(fileWriteStream);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
await page.goto('https://google.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
fileWriteStream.on('end', () => {
assert.is(fileWriteStream.writableFinished, true);
});
});
test('case 5a --> Happy Path: testing video recording with video frame width, height and autopad color', async (assert) => {
/** setup */
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const options = {
followNewTab: false,
videoFrame: {
width: 1024,
height: 1024,
},
autopad: {
color: 'gray',
},
};
const outputVideoPath = './test-output/test/video-recorder/testCase5a.mp4';
const recorder = new PuppeteerScreenRecorder(page, options);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 5b --> Happy Path: testing video recording with video frame width, height and autopad color as hex code', async (assert) => {
/** setup */
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const options = {
followNewTab: false,
videoFrame: {
width: 1024,
height: 1024,
},
autopad: {
color: '#008000',
},
};
const outputVideoPath = './test-output/test/video-recorder/testCase5b.mp4';
const recorder = new PuppeteerScreenRecorder(page, options);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 5c --> Happy Path: testing video recording with video frame width, height and default autopad color', async (assert) => {
/** setup */
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const options = {
followNewTab: false,
videoFrame: {
width: 1024,
height: 1024,
},
autopad: {},
};
const outputVideoPath = './test-output/test/video-recorder/testCase5c.mp4';
const recorder = new PuppeteerScreenRecorder(page, options);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
test('case 6 --> Happy Path: should create a video with a custom crf', async (assert) => {
/** setup */
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const options = {
followNewTab: false,
videoFrame: {
width: 1024,
height: 1024,
},
videoCrf: 0,
};
const outputVideoPath = './test-output/test/video-recorder/testCase6.mp4';
const recorder = new PuppeteerScreenRecorder(page, options);
const recorderValue = await recorder.start(outputVideoPath);
/** execute */
await page.goto('https://github.com', { waitUntil: 'load' });
/** clear */
const status = await recorder.stop();
await browser.close();
/** assert */
assert.is(recorderValue instanceof PuppeteerScreenRecorder, true);
assert.is(status, true);
assert.is(fs.existsSync(outputVideoPath), true);
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHVwcGV0ZWVyU2NyZWVuUmVjb3JkZXIuc3BlYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zcGVjL1B1cHBldGVlclNjcmVlblJlY29yZGVyLnNwZWMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3BCLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFL0IsT0FBTyxJQUFJLE1BQU0sS0FBSyxDQUFDO0FBQ3ZCLE9BQU8sU0FBUyxNQUFNLFdBQVcsQ0FBQztBQUVsQyxPQUFPLEVBQUUsdUJBQXVCLEVBQWtDLE1BQU0sS0FBSyxDQUFDO0FBRTlFLElBQUksQ0FBQyxpRkFBaUYsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7SUFDdkcsWUFBWTtJQUNaLE1BQU0sT0FBTyxHQUFHLE1BQU0sU0FBUyxDQUFDLE1BQU0sQ0FBQztRQUNyQyxRQUFRLEVBQUUsSUFBSTtRQUNkLGNBQWMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLDJCQUEyQixDQUFDO0tBQ3pELENBQUMsQ0FBQztJQUNILE1BQU0sSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRXJDLE1BQU0sZUFBZSxHQUFHLGlEQUFpRCxDQUFDO0lBQzFFLE1BQU0sUUFBUSxHQUFHLElBQUksdUJBQXVCLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkQsTUFBTSxhQUFhLEdBQUcsTUFBTSxRQUFRLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0lBRTVELGNBQWM7SUFDZCxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUM3RCxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUU3RCxZQUFZ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