import { execFileSync } from "node:child_process"; import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const scriptPath = path.join(repoRoot, "scripts/free-e2e-ports.sh"); const script = readFileSync(scriptPath, "utf8"); const playwrightWorkflow = readFileSync( path.join(repoRoot, ".github/workflows/playwright.yml"), "utf8", ); function portsFromShellArray(source: string, name: string): number[] { const match = source.match(new RegExp(`readonly ${name}=\\(([^)]*)\\)`)); expect(match, `${name} array`).toBeTruthy(); return [...match![1].matchAll(/\b(\d+)\b/g)].map(([, port]) => Number(port)); } /** Runs the script on a fake Linux host whose fuser never finds a listener, so nothing is killed. */ function runScript(env: Record): { status: number; output: string } { const fakeBin = mkdtempSync(path.join(tmpdir(), "evoo-e2e-ports-")); for (const [name, body] of [ ["uname", "echo Linux"], ["fuser", "exit 1"], ] as const) { const file = path.join(fakeBin, name); writeFileSync(file, `#!/usr/bin/env bash\n${body}\n`); chmodSync(file, 0o755); } try { const output = execFileSync("bash", [scriptPath], { env: { PATH: `${fakeBin}:${process.env.PATH}`, ...env }, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); return { status: 0, output }; } catch (error) { const failure = error as { status: number; stdout: string; stderr: string }; return { status: failure.status, output: `${failure.stdout}${failure.stderr}` }; } } function announcedPorts(output: string): number[] { const match = output.match(/E2E ports \(([^)]*)\)/); expect(match, "announced E2E ports").toBeTruthy(); return match![1].split(" ").map(Number); } describe("free-e2e-ports.sh", () => { it("keeps the live dev ports apart from the default E2E ports", () => { const livePorts = portsFromShellArray(script, "LIVE_PORTS"); expect(livePorts).toEqual([3100, 5173]); const run = runScript({}); expect(run.status).toBe(0); const e2ePorts = announcedPorts(run.output); expect(e2ePorts).toEqual([3110, 3111, 5180, 4321]); for (const livePort of livePorts) { expect(e2ePorts).not.toContain(livePort); } }); it("follows the harness port settings so runners on one host never touch each other", () => { const run = runScript({ API_PORT: "3310", WEB_PORT: "5380", WWW_PORT: "4521" }); expect(run.status).toBe(0); expect(announcedPorts(run.output)).toEqual([3310, 3311, 5380, 4521]); }); it("refuses a port setting that is not a number", () => { const run = runScript({ API_PORT: "abc" }); expect(run.status).toBe(1); expect(run.output).toContain("API_PORT must be a port number"); }); it("uses fuser and skips non-Linux hosts", () => { expect(script).toContain('fuser -k "$spec"'); expect(script).toContain('uname -s)" != "Linux"'); }); it("is wired into the Playwright workflow before product and marketing E2E", () => { const occurrences = playwrightWorkflow.match(/free-e2e-ports\.sh/g) ?? []; expect(occurrences.length).toBeGreaterThanOrEqual(2); expect(playwrightWorkflow.indexOf("free-e2e-ports.sh")).toBeLessThan( playwrightWorkflow.indexOf("Run Playwright tests"), ); expect(playwrightWorkflow.lastIndexOf("free-e2e-ports.sh")).toBeLessThan( playwrightWorkflow.indexOf("Run marketing homepage E2E"), ); }); it("gets its ports from the workflow step that picks them per runner", () => { const pickStep = playwrightWorkflow.indexOf("Pick E2E ports for this runner"); expect(pickStep).toBeGreaterThan(-1); expect(pickStep).toBeLessThan(playwrightWorkflow.indexOf("free-e2e-ports.sh")); for (const setting of ["API_PORT=", "WEB_PORT=", "WWW_PORT="]) { expect(playwrightWorkflow).toContain(setting); } }); });