import * as fs from 'fs'; import * as path from 'path'; import type { eventWithTime, recordOptions } from '../../src/types'; import { launchPuppeteer, ISuite } from '../utils'; const suites: Array<{ title: string; eval: string; eventsString?: string; times?: number; // defaults to 5 }> = [ { title: 'append 70 x 70 x 70 elements', eval: ` () => { return new Promise((resolve) => { const branches = 70; const depth = 3; // append children for the current node function expand(node, depth) { if (depth == 0) return; for (let b = 0; b < branches; b++) { const child = document.createElement('div'); node.appendChild(child); expand(child, depth - 1); } } const frag = document.createDocumentFragment(); const node = document.createElement('div'); expand(node, depth); frag.appendChild(node); document.body.appendChild(frag); resolve(); }); }; `, times: 3, }, { title: 'append 1000 elements and reverse their order', eval: ` () => { return new Promise(async (resolve) => { const branches = 1000; function waitForTimeout(timeout) { return new Promise((resolve) => setTimeout(() => resolve(), timeout)); } const frag = document.createDocumentFragment(); const node = document.createElement('div'); for (let b = 0; b < branches; b++) { const child = document.createElement('div'); node.appendChild(child); child.textContent = b + 1; } frag.appendChild(node); document.body.appendChild(frag); const children = node.children; await waitForTimeout(0); // reverse the order of children for (let i = children.length - 1; i >= 0; i--) node.appendChild(node.children[i]); resolve(); }); }; `, times: 3, }, ]; function avg(v: number[]): number { return v.reduce((prev, cur) => prev + cur, 0) / v.length; } describe('benchmark: replayer fast-forward performance', () => { jest.setTimeout(240000); let code: ISuite['code']; let page: ISuite['page']; let browser: ISuite['browser']; beforeAll(async () => { browser = await launchPuppeteer({ headless: true, args: ['--disable-dev-shm-usage'], }); const bundlePath = path.resolve(__dirname, '../../dist/rrweb.min.js'); code = fs.readFileSync(bundlePath, 'utf8'); for (const suite of suites) suite.eventsString = await generateEvents(suite.eval); }, 600_000); afterAll(async () => { await browser.close(); }); for (const suite of suites) { it( suite.title, async () => { suite.times = suite.times ?? 5; const durations: number[] = []; for (let i = 0; i < suite.times; i++) { page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(` `); const duration = (await page.evaluate(() => { const replayer = new (window as any).rrweb.Replayer( (window as any).events, ); const start = Date.now(); replayer.play(replayer.getMetaData().totalTime + 100); return Date.now() - start; })) as number; durations.push(duration); await page.close(); } console.table([ { title: suite.title, times: suite.times, duration: avg(durations), durations: durations.join(', '), }, ]); }, 60_000, ); } /** * Get the recorded events after the mutation function is executed. */ async function generateEvents(mutateNodesFn: string): Promise { const page = await browser.newPage(); await page.goto('about:blank'); await page.setContent(` `); const eventsString = (await page.evaluate((mutateNodesFn) => { return new Promise((resolve) => { const events: eventWithTime[] = []; const options: recordOptions = { emit: (event) => { events.push(event); }, }; const record = (window as any).rrweb.record; record(options); eval(mutateNodesFn)().then(() => { resolve(JSON.stringify(events)); }); }); }, mutateNodesFn)) as string; await page.close(); return eventsString; } });