diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24935437792fa35765ad40e7c0cb92c9b9f3239d..fb4927a86bdb097d484f30256894270c6dba3da8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -330,6 +330,9 @@ jobs: # No Electron setup here: `t3` (apps/server) has no Electron dependency # and none of its tests touch the runtime. Only the non-server `test` # job, which covers @t3tools/desktop, needs the download. + - name: Install Chromium for server browser integration tests + run: node apps/server/node_modules/playwright-core/cli.js install --with-deps --only-shell chromium + - name: Test run: vp run --filter t3 test --shard ${{ matrix.shard }}/${{ strategy.job-total }} diff --git a/apps/server/src/preview/ServerBrowserContexts.test.ts b/apps/server/src/preview/ServerBrowserContexts.test.ts index 8bb8413a1949ca9fb5d03eaaa8e3b1135b78dd66..4353377ffc25b9a155a1e1884a3ff31899d5b265 100644 --- a/apps/server/src/preview/ServerBrowserContexts.test.ts +++ b/apps/server/src/preview/ServerBrowserContexts.test.ts @@ -1,7 +1,7 @@ import * as NodeEvents from "node:events"; import { INCOGNITO_BROWSER_PROFILE_ID } from "@t3tools/contracts"; -import type { Browser, BrowserContext, BrowserType } from "playwright-core"; +import { chromium, type Browser, type BrowserContext, type BrowserType } from "playwright-core"; import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { ServerBrowserContexts } from "./ServerBrowserContexts.ts"; @@ -12,9 +12,8 @@ const launches = vi.hoisted(() => ({ mkdir: vi.fn().mockResolvedValue(undefined), rm: vi.fn().mockResolvedValue(undefined), })); -vi.mock("playwright-core", () => ({ - chromium: { launch: launches.launch, launchPersistentContext: launches.persistent }, -})); +vi.spyOn(chromium, "launch").mockImplementation(launches.launch); +vi.spyOn(chromium, "launchPersistentContext").mockImplementation(launches.persistent); vi.mock("node:fs/promises", () => ({ mkdir: launches.mkdir, rm: launches.rm })); const makeContext = () => { diff --git a/apps/server/src/preview/ServerBrowserContexts.ts b/apps/server/src/preview/ServerBrowserContexts.ts index 20534757e8fbbe26a0920fbb8db875020ec92977..3e48bfb6b88c44d016fdb1c334c257a22b175e85 100644 --- a/apps/server/src/preview/ServerBrowserContexts.ts +++ b/apps/server/src/preview/ServerBrowserContexts.ts @@ -2,11 +2,17 @@ import { INCOGNITO_BROWSER_PROFILE_ID } from "@t3tools/contracts"; import { constVoid } from "effect/Function"; import * as NodeFSP from "node:fs/promises"; +import * as NodeModule from "node:module"; import * as NodePath from "node:path"; import type { Browser, BrowserContext } from "playwright-core"; import { sandboxDisabled } from "./PreviewBrowserHost.ts"; +// Playwright needs its files on disk. createRequire also resolves it from a Node SEA executable. +const requirePlaywright = NodeModule.createRequire(import.meta.url); +const loadPlaywright = () => + requirePlaywright("playwright-core") as typeof import("playwright-core"); + interface Options { readonly profilesDir: string; /** The shared headless browser's executable, installing it on first use. */ @@ -54,7 +60,7 @@ export class ServerBrowserContexts { private sharedBrowser() { if (!this.browser) { const launched = this.launchOptions().then(async (options) => { - const { chromium } = await import("playwright-core"); + const { chromium } = loadPlaywright(); const browser = await this.launch(options, () => chromium.launch(options)); browser.on("disconnected", () => { if (this.browser === launched) this.browser = undefined; @@ -104,7 +110,7 @@ export class ServerBrowserContexts { } const directory = this.profileDirectory(profileId); const options = await this.launchOptions(); - const { chromium } = await import("playwright-core"); + const { chromium } = loadPlaywright(); await NodeFSP.mkdir(directory, { recursive: true }); return this.launch(options, () => chromium.launchPersistentContext(directory, { ...options, ...contextOptions }), @@ -133,7 +139,7 @@ export class ServerBrowserContexts { * serves. The page keeps the desktop's storage, size, and window. */ async connectDesktopPage(endpoint: string) { - const { chromium } = await import("playwright-core"); + const { chromium } = loadPlaywright(); const browser = await chromium.connectOverCDP(endpoint, { timeout: 15_000 }); const page = browser.contexts().flatMap((context) => context.pages())[0]; if (!page) {