// Ensure Playwright Chromium tests cover ensure playwright chromium script behavior. import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { ensurePlaywrightChromium, installLinuxSystemChromiumPackage, resolvePlaywrightInstallRunner, shouldEnsureFfmpegFromArgv, shouldInstallPlaywrightSystemDependencies, shouldRequirePlaywrightChromiumFromArgv, } from "../../scripts/ensure-playwright-chromium.mts"; describe("ensurePlaywrightChromium", () => { it("does nothing when the browser binary exists and runs", () => { const spawnSync = vi.fn(() => ({ status: 0 })); expect( ensurePlaywrightChromium({ executablePath: "/cache/chromium/chrome", existsSync: () => true, spawnSync, }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith("/cache/chromium/chrome", ["--version"], { stdio: "ignore", }); }); it("uses an explicit Chromium executable override", () => { const spawnSync = vi.fn(() => ({ status: 0 })); expect( ensurePlaywrightChromium({ env: { PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: " /snap/bin/chromium " }, executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => candidatePath === "/snap/bin/chromium", spawnSync, }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith("/snap/bin/chromium", ["--version"], { stdio: "ignore", }); }); it("fails when the explicit Chromium executable override is missing", () => { const logs: string[] = []; const spawnSync = vi.fn(); expect( ensurePlaywrightChromium({ env: { PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: "/snap/bin/chromium" }, executablePath: "/cache/chromium/chrome", existsSync: () => false, log: (line: string) => logs.push(line), spawnSync, }), ).toBe(1); expect(spawnSync).not.toHaveBeenCalled(); expect(logs.join("\n")).toContain( "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH points to /snap/bin/chromium", ); }); it("uses a system Chromium binary when Playwright Chromium is missing", () => { const logs: string[] = []; const spawnSync = vi.fn(() => ({ status: 0 })); expect( ensurePlaywrightChromium({ executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => candidatePath === "/usr/bin/chromium-browser", log: (line: string) => logs.push(line), spawnSync, }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith("/usr/bin/chromium-browser", ["--version"], { stdio: "ignore", }); expect(logs.join("\n")).toContain("Using system Chromium at /usr/bin/chromium-browser"); }); it("installs Playwright Chromium when the lane requires its pinned browser", () => { let managedChromiumInstalled = false; const spawnSync = vi.fn((command: string, args: string[]) => { if (command === "pnpm" && args.includes("chromium")) { managedChromiumInstalled = true; return { status: 0 }; } if (command === "/cache/chromium/chrome") { return { status: managedChromiumInstalled ? 0 : 127 }; } if (command === "/usr/bin/chromium-browser") { return { status: 0 }; } return { status: 1 }; }); expect( ensurePlaywrightChromium({ cwd: "/repo", env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => candidatePath === "/usr/bin/chromium-browser" || (managedChromiumInstalled && candidatePath === "/cache/chromium/chrome"), requirePlaywrightChromium: true, spawnSync, stdio: "pipe", systemExecutablePath: "/usr/bin/chromium-browser", }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith( "pnpm", ["--dir", "ui", "exec", "playwright", "install", "chromium"], expect.objectContaining({ cwd: "/repo", stdio: "pipe" }), ); expect(spawnSync).not.toHaveBeenCalledWith("/usr/bin/chromium-browser", ["--version"], { stdio: "ignore", }); }); it("installs a relative pinned browser cache in the caller's directory, not the UI package", () => { const callerDirectory = "/repo"; const browserCache = path.join(callerDirectory, ".artifacts", "playwright-browsers"); const executablePath = path.join(browserCache, "chromium-1234", "chrome-linux64", "chrome"); const installedCaches: string[] = []; let installedExecutable: string | undefined; const spawnSync = vi.fn( (command: string, args: string[], options?: Record) => { if (command === "pnpm" && args.includes("chromium")) { const installerEnv = options?.env as NodeJS.ProcessEnv; const configuredCache = installerEnv.PLAYWRIGHT_BROWSERS_PATH ?? ""; const installerDirectory = path.join(String(options?.cwd), "ui"); // pnpm --dir ui resolves a relative cache in the UI package, not the caller. const installedCache = path.isAbsolute(configuredCache) ? configuredCache : path.resolve(installerDirectory, configuredCache); installedCaches.push(installedCache); installedExecutable = path.join( installedCache, "chromium-1234", "chrome-linux64", "chrome", ); return { status: 0 }; } return { status: command === installedExecutable ? 0 : 127 }; }, ); const status = ensurePlaywrightChromium({ cwd: callerDirectory, env: { INIT_CWD: callerDirectory, OPENCLAW_TESTBOX: "1", PATH: "/bin", PLAYWRIGHT_BROWSERS_PATH: ".artifacts/playwright-browsers", }, executablePath, existsSync: (candidate: string) => candidate === installedExecutable, getuid: () => 501, log: vi.fn(), platform: "linux", requirePlaywrightChromium: true, spawnSync, stdio: "pipe", }); expect({ browserCache: installedCaches[0], status }).toEqual({ browserCache, status: 0, }); }); it.each([ { configuredPath: undefined, label: "an unset cache path" }, { configuredPath: "", label: "an empty cache path" }, { configuredPath: "/shared/playwright", label: "an absolute cache path" }, { configuredPath: "0", label: "Playwright's package-local cache sentinel" }, ])("preserves $label for sibling browser dependency installs", ({ configuredPath }) => { const env: NodeJS.ProcessEnv = { INIT_CWD: "/repo", PATH: "/bin" }; if (configuredPath !== undefined) { env.PLAYWRIGHT_BROWSERS_PATH = configuredPath; } const spawnSync = vi.fn(() => ({ status: 0 })); expect( ensurePlaywrightChromium({ cwd: "/repo", ensureFfmpeg: true, env, executablePath: "/cache/chromium/chrome", existsSync: (candidate: string) => candidate === "/cache/chromium/chrome", spawnSync, stdio: "pipe", }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith( "pnpm", ["--dir", "ui", "exec", "playwright", "install", "ffmpeg"], expect.objectContaining({ env }), ); expect(env.PLAYWRIGHT_BROWSERS_PATH).toBe(configuredPath); }); it("installs Playwright ffmpeg when recorded UI tests request it", () => { const logs: string[] = []; const spawnSync = vi.fn(() => ({ status: 0 })); expect( ensurePlaywrightChromium({ cwd: "/repo", ensureFfmpeg: true, env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => candidatePath === "/usr/bin/chromium-browser", log: (line: string) => logs.push(line), spawnSync, stdio: "pipe", }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith("/usr/bin/chromium-browser", ["--version"], { stdio: "ignore", }); expect(spawnSync).toHaveBeenCalledWith( "pnpm", ["--dir", "ui", "exec", "playwright", "install", "ffmpeg"], { cwd: "/repo", env: { PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); expect(logs.join("\n")).toContain("Using system Chromium at /usr/bin/chromium-browser"); }); it("skips a broken system Chromium binary and uses the first runnable candidate", () => { const logs: string[] = []; const spawnSync = vi.fn((candidatePath: string) => ({ status: candidatePath === "/usr/bin/google-chrome" ? 0 : 127, })); expect( ensurePlaywrightChromium({ executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => candidatePath === "/snap/bin/chromium" || candidatePath === "/usr/bin/google-chrome", log: (line: string) => logs.push(line), spawnSync, }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith("/snap/bin/chromium", ["--version"], { stdio: "ignore", }); expect(spawnSync).toHaveBeenCalledWith("/usr/bin/google-chrome", ["--version"], { stdio: "ignore", }); expect(logs.join("\n")).toContain("Using system Chromium at /usr/bin/google-chrome"); }); it("preserves the intentional missing-browser skip mode", () => { const logs: string[] = []; const spawnSync = vi.fn(); expect( ensurePlaywrightChromium({ env: { OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM: "1" }, executablePath: "/cache/chromium/chrome", existsSync: () => false, log: (line: string) => logs.push(line), spawnSync, }), ).toBe(0); expect(spawnSync).not.toHaveBeenCalled(); expect(logs.join("\n")).toContain("leaves the lane skipped"); }); it("installs Chromium through the UI Playwright package when missing", () => { const spawnSync = vi.fn(() => ({ status: 0 })); let existsCalls = 0; expect( ensurePlaywrightChromium({ cwd: "/repo", env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: () => ++existsCalls > 1, platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith( "pnpm", ["--dir", "ui", "exec", "playwright", "install", "chromium"], { cwd: "/repo", env: { PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); }); it("installs Linux system dependencies when Chromium still cannot start in a root lane", () => { const spawnSync = vi .fn() .mockReturnValueOnce({ status: 127 }) .mockReturnValueOnce({ status: 0 }) .mockReturnValueOnce({ status: 127 }) .mockReturnValueOnce({ status: 0 }) .mockReturnValueOnce({ status: 0 }); expect( ensurePlaywrightChromium({ cwd: "/repo", env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: () => true, getuid: () => 0, platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(0); expect(spawnSync).toHaveBeenNthCalledWith( 2, "pnpm", ["--dir", "ui", "exec", "playwright", "install", "chromium"], { cwd: "/repo", env: { PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); expect(spawnSync).toHaveBeenNthCalledWith( 4, "pnpm", ["--dir", "ui", "exec", "playwright", "install", "--with-deps", "chromium"], { cwd: "/repo", env: { PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); }); it("retries with Linux system dependencies when the Chromium install reports missing host deps", () => { const logs: string[] = []; const spawnSync = vi .fn() .mockReturnValueOnce({ status: 23 }) .mockReturnValueOnce({ status: 0 }) .mockReturnValueOnce({ status: 0 }); let existsCalls = 0; expect( ensurePlaywrightChromium({ cwd: "/repo", env: { CI: "1", PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: () => ++existsCalls > 1, getuid: () => 501, log: (line: string) => logs.push(line), platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(0); expect(spawnSync).toHaveBeenNthCalledWith( 1, "pnpm", ["--dir", "ui", "exec", "playwright", "install", "chromium"], { cwd: "/repo", env: { CI: "1", PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); expect(spawnSync).toHaveBeenNthCalledWith( 2, "pnpm", ["--dir", "ui", "exec", "playwright", "install", "--with-deps", "chromium"], { cwd: "/repo", env: { CI: "1", PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); expect(logs.join("\n")).toContain("installing Linux system dependencies"); }); it("falls back to distro Chromium when Playwright does not support the Linux runner image", () => { const logs: string[] = []; let installedSystemChromium = false; const spawnSync = vi.fn((command: string, args: string[]) => { if (command === "pnpm" && args.includes("chromium")) { return { status: 1 }; } if (command === "apt-get" && args.includes("update")) { return { status: 0 }; } if (command === "apt-get" && args.includes("chromium-browser")) { installedSystemChromium = true; return { status: 0 }; } if (command === "/usr/bin/chromium-browser") { return { status: installedSystemChromium ? 0 : 127 }; } return { status: 1 }; }); expect( ensurePlaywrightChromium({ cwd: "/repo", env: { CI: "1", PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: (candidatePath: string) => installedSystemChromium && candidatePath === "/usr/bin/chromium-browser", getuid: () => 0, log: (line: string) => logs.push(line), platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(0); expect(spawnSync).toHaveBeenCalledWith( "apt-get", ["update", "-qq"], expect.objectContaining({ cwd: "/repo", stdio: "pipe" }), ); expect(spawnSync).toHaveBeenCalledWith( "apt-get", ["install", "-y", "chromium-browser"], expect.objectContaining({ cwd: "/repo", stdio: "pipe" }), ); expect(logs.join("\n")).toContain("installing a system Chromium package"); expect(logs.join("\n")).toContain("Using system Chromium at /usr/bin/chromium-browser"); }); it("does not install Linux system dependencies for an unprivileged local lane", () => { const spawnSync = vi .fn() .mockReturnValueOnce({ status: 127 }) .mockReturnValueOnce({ status: 0 }) .mockReturnValueOnce({ status: 127 }); expect( ensurePlaywrightChromium({ cwd: "/repo", env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: () => true, getuid: () => 501, platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(1); expect(spawnSync).toHaveBeenCalledTimes(3); }); it("reinstalls Chromium when the cached executable exists but cannot start", () => { const spawnSync = vi .fn() .mockReturnValueOnce({ status: 127 }) .mockReturnValueOnce({ status: 0 }) .mockReturnValueOnce({ status: 0 }); expect( ensurePlaywrightChromium({ cwd: "/repo", env: { PATH: "/bin" }, executablePath: "/cache/chromium/chrome", existsSync: () => true, platform: "linux", spawnSync, stdio: "pipe", systemExecutablePath: "", }), ).toBe(0); expect(spawnSync).toHaveBeenNthCalledWith(1, "/cache/chromium/chrome", ["--version"], { stdio: "ignore", }); expect(spawnSync).toHaveBeenNthCalledWith( 2, "pnpm", ["--dir", "ui", "exec", "playwright", "install", "chromium"], { cwd: "/repo", env: { PATH: "/bin" }, shell: false, stdio: "pipe", windowsVerbatimArguments: undefined, }, ); expect(spawnSync).toHaveBeenNthCalledWith(3, "/cache/chromium/chrome", ["--version"], { stdio: "ignore", }); }); it("returns the installer status when Playwright install fails", () => { expect( ensurePlaywrightChromium({ executablePath: "/cache/chromium/chrome", existsSync: () => false, platform: "darwin", spawnSync: vi.fn(() => ({ status: 23 })), stdio: "pipe", systemExecutablePath: "", }), ).toBe(23); }); it("wraps the pnpm command shim on Windows", () => { expect( resolvePlaywrightInstallRunner({ comSpec: "C:\\Windows\\System32\\cmd.exe", env: {}, platform: "win32", }), ).toEqual({ args: ["/d", "/s", "/c", "pnpm.cmd --dir ui exec playwright install chromium"], command: "C:\\Windows\\System32\\cmd.exe", shell: false, windowsVerbatimArguments: true, }); }); it("wraps the dependency install command shim on Windows", () => { expect( resolvePlaywrightInstallRunner({ comSpec: "C:\\Windows\\System32\\cmd.exe", env: {}, platform: "win32", withDeps: true, }), ).toEqual({ args: ["/d", "/s", "/c", "pnpm.cmd --dir ui exec playwright install --with-deps chromium"], command: "C:\\Windows\\System32\\cmd.exe", shell: false, windowsVerbatimArguments: true, }); }); it("allows dependency installation for Linux CI lanes", () => { expect( shouldInstallPlaywrightSystemDependencies({ env: { CI: "true" }, getuid: () => 501, platform: "linux", }), ).toBe(true); expect( shouldInstallPlaywrightSystemDependencies({ env: { CI: "1" }, getuid: () => 501, platform: "linux", }), ).toBe(true); expect( shouldInstallPlaywrightSystemDependencies({ env: { OPENCLAW_TESTBOX: "1" }, getuid: () => 501, platform: "linux", }), ).toBe(true); }); it("installs Linux system Chromium packages with sudo for non-root lanes", () => { const spawnSync = vi.fn(() => ({ status: 0 })); expect( installLinuxSystemChromiumPackage({ cwd: "/repo", env: { PATH: "/bin" }, getuid: () => 501, platform: "linux", spawnSync, stdio: "pipe", }), ).toBe(0); expect(spawnSync).toHaveBeenNthCalledWith(1, "sudo", ["-n", "true"], { stdio: "ignore" }); expect(spawnSync).toHaveBeenNthCalledWith( 2, "sudo", ["-n", "apt-get", "update", "-qq"], expect.objectContaining({ cwd: "/repo", stdio: "pipe" }), ); expect(spawnSync).toHaveBeenNthCalledWith( 3, "sudo", ["-n", "apt-get", "install", "-y", "chromium-browser"], expect.objectContaining({ cwd: "/repo", stdio: "pipe" }), ); }); it("allows QA scenario runners to skip optional Playwright ffmpeg", () => { expect(shouldEnsureFfmpegFromArgv(["node", "scripts/ensure-playwright-chromium.mts"])).toBe( true, ); expect( shouldEnsureFfmpegFromArgv([ "node", "scripts/ensure-playwright-chromium.mts", "--skip-ffmpeg", ]), ).toBe(false); }); it("parses the pinned Playwright Chromium requirement", () => { expect( shouldRequirePlaywrightChromiumFromArgv([ "node", "scripts/ensure-playwright-chromium.mts", "--require-playwright-chromium", ]), ).toBe(true); expect( shouldRequirePlaywrightChromiumFromArgv(["node", "scripts/ensure-playwright-chromium.mts"]), ).toBe(false); }); });