///
import { expect, test, type Browser, type Page } from '@playwright/test';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PRIMARY_FIXTURE } from './browser-helpers';
interface BenchmarkResult {
readonly mode: 'single-thread' | 'multithread';
readonly source: 'small' | 'medium';
readonly sourceDescription: string;
readonly cacheCondition: string;
readonly initMs: number;
readonly probeMs: number;
readonly conversionMs: number;
readonly outputReadMs: number;
readonly cleanupMs: number;
readonly exportEndToEndMs: number;
readonly outputBytes: number;
readonly hardwareConcurrency: number;
readonly crossOriginIsolated: boolean;
readonly inputBytes: number;
readonly usedJsHeapBytes?: number;
}
test.describe('opt-in pinned-core performance record', () => {
test.skip(
process.env.AV_BENCHMARK !== '1',
'Run with AV_BENCHMARK=1 through npm run benchmark:browser.'
);
test('records comparable fresh-context ST and MT end-to-end timings', async ({
browser,
}, testInfo) => {
test.setTimeout(600_000);
const medium = createMediumFixture();
const results: BenchmarkResult[] = [];
try {
results.push(
await runMode(browser, 'single-thread', PRIMARY_FIXTURE, 'small')
);
results.push(
await runMode(browser, 'multithread', PRIMARY_FIXTURE, 'small')
);
results.push(
await runMode(browser, 'single-thread', medium.filePath, 'medium')
);
results.push(
await runMode(browser, 'multithread', medium.filePath, 'medium')
);
await testInfo.attach('av-tools-browser-benchmark.json', {
body: Buffer.from(`${JSON.stringify(results, null, 2)}\n`),
contentType: 'application/json',
});
process.stdout.write(`AV_BENCHMARK_RESULT ${JSON.stringify(results)}\n`);
} finally {
medium.dispose();
}
});
});
async function runMode(
browser: Browser,
mode: BenchmarkResult['mode'],
fixturePath: string,
source: BenchmarkResult['source']
): Promise {
const context = await browser.newContext();
const page = await context.newPage();
try {
page.on('dialog', (dialog) => void dialog.accept());
await page.addInitScript(() => {
Reflect.deleteProperty(globalThis, 'showSaveFilePicker');
});
await page.goto('http://127.0.0.1:4173/');
const environment = await page.evaluate(() => ({
hardwareConcurrency: navigator.hardwareConcurrency,
crossOriginIsolated: globalThis.crossOriginIsolated,
}));
if (mode === 'multithread') {
expect(environment.crossOriginIsolated).toBe(true);
}
await page.getByText('Engine & browser').click();
await page
.getByLabel('Engine mode')
.selectOption(
mode === 'single-thread' ? 'force-single-thread' : 'prefer-multithread'
);
const initStarted = await monotonicNow(page);
await page.getByRole('button', { name: 'Initialize engine' }).click();
await expect(
page.locator('.engine-pill').filter({
hasText:
mode === 'single-thread'
? 'Single-threaded compatibility mode'
: 'Multithreaded',
})
).toBeVisible({ timeout: 120_000 });
const initMs = (await monotonicNow(page)) - initStarted;
const probeStarted = await monotonicNow(page);
await mediaInput(page).setInputFiles(fixturePath);
await expect(page.locator('.source-summary .phase--ready')).toHaveText(
'Ready',
{ timeout: 120_000 }
);
const probeMs = (await monotonicNow(page)) - probeStarted;
const exportStarted = await monotonicNow(page);
await page.getByRole('button', { name: 'Convert', exact: true }).click();
const result = page.locator('.result-card').last();
await expect(result).toContainText(
/created · verified|completed locally/u,
{
timeout: 180_000,
}
);
const exportEndToEndMs = (await monotonicNow(page)) - exportStarted;
const reportDownload = page.waitForEvent('download');
await page
.getByRole('button', { name: 'Export validation report' })
.click();
const reportPath = await (await reportDownload).path();
expect(reportPath).not.toBeNull();
const report = JSON.parse(
readFileSync(reportPath as string, 'utf8')
) as PerformanceReport;
const outputBytes = await page
.locator('.quick-preview video')
.evaluate(async (element) => {
const response = await fetch((element as HTMLVideoElement).src);
return (await response.arrayBuffer()).byteLength;
});
const usedJsHeapBytes = await page.evaluate(() => {
const memory = (
performance as Performance & {
memory?: { usedJSHeapSize?: number };
}
).memory;
return memory?.usedJSHeapSize;
});
const benchmarkSample: BenchmarkResult = {
mode,
source,
sourceDescription:
source === 'small'
? '2 s, 160×90 H.264/AAC synthetic pattern'
: '6 s, 640×360 H.264/AAC synthetic loop',
cacheCondition:
'fresh browser context; browser HTTP cache may be warm after the first run',
initMs: rounded(initMs),
probeMs: rounded(probeMs),
conversionMs: rounded(report.execution.conversionMilliseconds),
outputReadMs: rounded(report.execution.outputReadMilliseconds),
cleanupMs: rounded(report.execution.cleanupMilliseconds),
exportEndToEndMs: rounded(exportEndToEndMs),
outputBytes,
hardwareConcurrency: environment.hardwareConcurrency,
crossOriginIsolated: environment.crossOriginIsolated,
inputBytes: statSync(fixturePath).size,
...(usedJsHeapBytes === undefined ? {} : { usedJsHeapBytes }),
};
process.stdout.write(
`AV_BENCHMARK_SAMPLE ${JSON.stringify(benchmarkSample)}\n`
);
return benchmarkSample;
} finally {
await context.close();
}
}
interface PerformanceReport {
readonly execution: {
readonly conversionMilliseconds: number;
readonly outputReadMilliseconds: number;
readonly cleanupMilliseconds: number;
};
}
function createMediumFixture(): {
readonly filePath: string;
readonly dispose: () => void;
} {
const directory = mkdtempSync(join(tmpdir(), 'av-tools-benchmark-'));
const filePath = join(directory, 'medium-pattern.mp4');
try {
execFileSync(
'ffmpeg',
[
'-hide_banner',
'-loglevel',
'error',
'-y',
'-stream_loop',
'2',
'-i',
PRIMARY_FIXTURE,
'-t',
'6',
'-vf',
'scale=640:360',
'-r',
'24',
'-c:v',
'libx264',
'-preset',
'ultrafast',
'-crf',
'30',
'-c:a',
'aac',
'-b:a',
'64k',
filePath,
],
{ stdio: 'pipe' }
);
} catch (error) {
rmSync(directory, { recursive: true, force: true });
throw error;
}
return {
filePath,
dispose: () => rmSync(directory, { recursive: true, force: true }),
};
}
function mediaInput(page: Page) {
return page.locator('input[type="file"][accept*="audio"]').first();
}
async function monotonicNow(page: Page): Promise {
return page.evaluate(() => performance.now());
}
function rounded(value: number): number {
return Math.round(value * 10) / 10;
}