/** Minimal Chrome DevTools Protocol driver for the editor e2e: headless profiles, OOPIF frames, screenshots. */
import { spawn } from "node:child_process";
import type { ChildProcess } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const CHROME = process.env.CHROME_PATH ?? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const POLL_MS = 250;
const DEFAULT_WAIT_MS = 45_000;
const START_ATTEMPTS = 80;
type Reply = { result?: unknown; error?: { message: string } };
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export async function waitFor<T>(
label: string,
probe: () => Promise<T | null | undefined | false>,
timeoutMs = DEFAULT_WAIT_MS,
): Promise<T> {
const deadline = Date.now() + timeoutMs;
let lastError: unknown = null;
while (Date.now() < deadline) {
try {
const value = await probe();
if (value) return value;
} catch (error) {
lastError = error;
}
await sleep(POLL_MS);
}
throw new Error(`timed out waiting for ${label}${lastError ? ` (${String(lastError)})` : ""}`);
}
/** One headless Chrome with its own profile, so two instances are two independent browsers (no shared cookies). */
export class Browser {
private socket!: WebSocket;
private nextId = 1;
private pending = new Map<number, (reply: Reply) => void>();