Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 48 additions & 20 deletions benchmarks/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
*/

import { type ChildProcess, execSync, spawn } from "node:child_process";
import puppeteer from "puppeteer-core";
import puppeteer, { type Browser } from "puppeteer-core";

const LIGHTPANDA_PORT = 9222;
const ITERATIONS = 5;
const TEST_URL = "https://example.com";
const ITERATIONS = 100;
const TEST_URL = "https://example.org";

interface BenchmarkResult {
browser: string;
Expand Down Expand Up @@ -47,16 +47,47 @@ function getChromePath(): string {
throw new Error("Chrome not found");
}

async function startLightpanda(): Promise<ChildProcess> {
return new Promise((resolve, reject) => {
const proc = spawn(
"lightpanda",
["serve", "--host", "127.0.0.1", "--port", String(LIGHTPANDA_PORT)],
{ stdio: "ignore" },
);
proc.on("error", reject);
setTimeout(() => resolve(proc), 1500);
});
/**
* Spawn Lightpanda and wait until it actually accepts a CDP connection.
*
* The returned duration is therefore comparable to puppeteer.launch() for
* Chrome: both measure "time until the browser is usable", not "time until
* spawn() returned".
*/
async function startLightpanda(): Promise<[ChildProcess, Browser]> {
const proc = spawn(
"lightpanda",
["serve", "--host", "127.0.0.1", "--port", String(LIGHTPANDA_PORT)],
{ stdio: "ignore" },
);

const deadline = Date.now() + 10_000;
for (;;) {
if (proc.exitCode !== null || proc.signalCode !== null) {
throw new Error(
`Lightpanda exited during startup (code=${String(proc.exitCode)}). Is port ${LIGHTPANDA_PORT} already in use?`,
);
}
try {
const browser = await puppeteer.connect({
browserWSEndpoint: `ws://127.0.0.1:${LIGHTPANDA_PORT}`,
});
return [proc, browser];
} catch (e) {
if (Date.now() > deadline) {
throw new Error(`Lightpanda never became ready: ${String(e)}`);
}
await new Promise((r) => setTimeout(r, 1));
}
}
}

/** Kill Lightpanda and wait for the port to be released. */
async function stopLightpanda(proc: ChildProcess): Promise<void> {
if (proc.exitCode !== null || proc.signalCode !== null) return;
const exited = new Promise<void>((r) => proc.once("exit", () => r()));
proc.kill();
await exited;
}

async function benchmarkChrome(): Promise<BenchmarkResult[]> {
Expand Down Expand Up @@ -107,22 +138,19 @@ async function benchmarkLightpanda(): Promise<BenchmarkResult[]> {
const navigationTimes: number[] = [];

for (let i = 0; i < ITERATIONS; i++) {
const [proc, startupTime] = await measure(() => startLightpanda());
const [[proc, browser], startupTime] = await measure(() =>
startLightpanda(),
);
startupTimes.push(startupTime);

const browser = await puppeteer.connect({
browserWSEndpoint: `ws://127.0.0.1:${LIGHTPANDA_PORT}`,
});

const page = await browser.newPage();
const [, navTime] = await measure(() =>
page.goto(TEST_URL, { waitUntil: "domcontentloaded" }),
);
navigationTimes.push(navTime);

await browser.disconnect();
proc.kill();
await new Promise((r) => setTimeout(r, 500));
await stopLightpanda(proc);
}

results.push({
Expand Down