diff --git a/apps/server/src/browser-preview/browser-preview-manager.test.ts b/apps/server/src/browser-preview/browser-preview-manager.test.ts index b849529f0028..3a90134cb663 100644 --- a/apps/server/src/browser-preview/browser-preview-manager.test.ts +++ b/apps/server/src/browser-preview/browser-preview-manager.test.ts @@ -1,7 +1,7 @@ import { ThreadId } from "@t3tools/contracts"; import { describe, expect, it } from "vitest"; -import { BrowserPreviewManager } from "./browser-preview-manager.ts"; +import { BrowserPreviewManager, playwright_chrome_candidates } from "./browser-preview-manager.ts"; const threadId = ThreadId.make("thread-browser-preview"); @@ -75,3 +75,31 @@ describe("BrowserPreviewManager", () => { expect(tickets.map((ticket) => manager.resolve_ticket(ticket))).toEqual([null, null]); }); }); + +describe("playwright_chrome_candidates", () => { + it("prefers the newest revision and ignores unrelated cache entries", async () => { + const fs = await import("node:fs/promises"); + const os = await import("node:os"); + const path = await import("node:path"); + const cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), "gits-playwright-cache-")); + try { + for (const entry of ["chromium-1208", "chromium-1228", "firefox-1489", ".links"]) { + await fs.mkdir(path.join(cacheDir, entry), { recursive: true }); + } + + const candidates = playwright_chrome_candidates(cacheDir); + + expect(candidates[0]).toBe(path.join(cacheDir, "chromium-1228", "chrome-linux64", "chrome")); + expect(candidates).toContain( + path.join(cacheDir, "chromium-1208", "chrome-linux64", "chrome"), + ); + expect(candidates.join(" ")).not.toContain("firefox"); + } finally { + await fs.rm(cacheDir, { recursive: true, force: true }); + } + }); + + it("returns nothing when the cache directory is absent", () => { + expect(playwright_chrome_candidates("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/nonexistent/ms-playwright")).toEqual([]); + }); +}); diff --git a/apps/server/src/browser-preview/browser-preview-manager.ts b/apps/server/src/browser-preview/browser-preview-manager.ts index 1464dcb8585d..1875486c280b 100644 --- a/apps/server/src/browser-preview/browser-preview-manager.ts +++ b/apps/server/src/browser-preview/browser-preview-manager.ts @@ -1,6 +1,7 @@ // @effect-diagnostics nodeBuiltinImport:off globalDate:off - external CLI boundary and expiring viewer tickets. import { execFile } from "node:child_process"; -import { existsSync } from "node:fs"; +import { existsSync, readdirSync } from "node:fs"; +import { join } from "node:path"; import { randomUUID } from "node:crypto"; import { promisify } from "node:util"; @@ -55,6 +56,36 @@ export interface BrowserPreviewManagerOptions { readonly run?: BrowserPreviewRun; } +// Playwright installs each Chromium build under its own `chromium-` directory and +// prunes old ones, so pinning revisions goes stale on the next `playwright install`. +const PLAYWRIGHT_CHROME_RELATIVE_PATHS = [ + "chrome-linux64/chrome", + "chrome-linux/chrome", + "chrome-mac/Chromium.app/Contents/MacOS/Chromium", +] as const; + +export function playwright_chrome_candidates(cache_dir: string): ReadonlyArray { + let entries: ReadonlyArray; + try { + entries = readdirSync(cache_dir); + } catch { + return []; + } + + return ( + entries + .flatMap((entry) => { + const revision = /^chromium(?:_headless_shell)?-(\d+)$/u.exec(entry)?.[1]; + return revision === undefined ? [] : [{ entry, revision: Number(revision) }]; + }) + // Newest revision first: Playwright keeps older builds around until they are pruned. + .sort((left, right) => right.revision - left.revision) + .flatMap(({ entry }) => + PLAYWRIGHT_CHROME_RELATIVE_PATHS.map((relative) => join(cache_dir, entry, relative)), + ) + ); +} + function resolve_browser_path(): string | undefined { if (process.env.GSD_BROWSER_BROWSER_PATH) { return process.env.GSD_BROWSER_BROWSER_PATH; @@ -64,8 +95,9 @@ function resolve_browser_path(): string | undefined { "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", - `${process.env.HOME ?? ""}/.cache/ms-playwright/chromium-1223/chrome-linux64/chrome`, - `${process.env.HOME ?? ""}/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome`, + ...(process.env.HOME + ? playwright_chrome_candidates(join(process.env.HOME, ".cache", "ms-playwright")) + : []), ]; return candidates.find((candidate) => existsSync(candidate)); }