Files
openclaw/test/scripts/ensure-playwright-chromium.test.ts
2026-06-24 13:24:43 +08:00

501 lines
15 KiB
TypeScript

// Ensure Playwright Chromium tests cover ensure playwright chromium script behavior.
import { describe, expect, it, vi } from "vitest";
import {
ensurePlaywrightChromium,
installLinuxSystemChromiumPackage,
resolvePlaywrightInstallRunner,
shouldEnsureFfmpegFromArgv,
shouldInstallPlaywrightSystemDependencies,
} from "../../scripts/ensure-playwright-chromium.mjs";
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: (path: string) => path === "/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: (path: string) => path === "/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 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: (path: string) => path === "/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((path: string) => ({
status: path === "/usr/bin/google-chrome" ? 0 : 127,
}));
expect(
ensurePlaywrightChromium({
executablePath: "/cache/chromium/chrome",
existsSync: (path: string) =>
path === "/snap/bin/chromium" || path === "/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: (path: string) =>
installedSystemChromium && path === "/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.mjs"])).toBe(
true,
);
expect(
shouldEnsureFfmpegFromArgv([
"node",
"scripts/ensure-playwright-chromium.mjs",
"--skip-ffmpeg",
]),
).toBe(false);
});
});