diff --git a/apps/server/src/preview/ServerBrowserContexts.test.ts b/apps/server/src/preview/ServerBrowserContexts.test.ts index 99504e86cb1b2..00c1b4f31a180 100644 --- a/apps/server/src/preview/ServerBrowserContexts.test.ts +++ b/apps/server/src/preview/ServerBrowserContexts.test.ts @@ -1,4 +1,8 @@ +// @effect-diagnostics nodeBuiltinImport:off - Hands a real file on disk to a real desktop browser. import * as NodeEvents from "node:events"; +import * as NodeFS from "node:fs"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; import { INCOGNITO_BROWSER_PROFILE_ID } from "@t3tools/contracts"; import { chromium, type Browser, type BrowserContext, type BrowserType } from "playwright-core"; @@ -12,6 +16,7 @@ const launches = vi.hoisted(() => ({ mkdir: vi.fn().mockResolvedValue(undefined), rm: vi.fn().mockResolvedValue(undefined), })); +const launchPersistentChromium = chromium.launchPersistentContext.bind(chromium); 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 })); @@ -327,4 +332,45 @@ describe("ServerBrowserContexts", () => { expect(browser.close).toHaveBeenCalledTimes(1); expect(browser.created[0]?.close).toHaveBeenCalled(); }); + + it("uploads a file over 50 MB to a desktop tab, which shares the server's filesystem", async () => { + const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-desktop-upload-")); + const userDataDir = NodePath.join(directory, "profile"); + const pool = new ServerBrowserContexts(options()); + try { + const desktop = await launchPersistentChromium(userDataDir, { + headless: true, + args: ["--remote-debugging-port=0"], + }); + try { + const [port] = NodeFS.readFileSync( + NodePath.join(userDataDir, "DevToolsActivePort"), + "utf8", + ).split("\n"); + const { browser, page } = await pool.connectDesktopPage(`http://127.0.0.1:${port}`); + try { + const file = NodePath.join(directory, "large.bin"); + NodeFS.writeFileSync(file, ""); + NodeFS.truncateSync(file, 51 * 1024 * 1024); + await page.setContent(``); + await page.locator("input").setInputFiles(file); + expect( + await page.$eval("input", (input) => + Array.from( + (input as unknown as { files: ArrayLike<{ name: string; size: number }> }).files, + ({ name, size }) => ({ name, size }), + ), + ), + ).toEqual([{ name: "large.bin", size: 51 * 1024 * 1024 }]); + } finally { + await browser.close(); + } + } finally { + await pool.close(); + await desktop.close(); + } + } finally { + NodeFS.rmSync(directory, { recursive: true, force: true }); + } + }); }); diff --git a/apps/server/src/preview/ServerBrowserContexts.ts b/apps/server/src/preview/ServerBrowserContexts.ts index 365add7565c69..f82f5e33c97a0 100644 --- a/apps/server/src/preview/ServerBrowserContexts.ts +++ b/apps/server/src/preview/ServerBrowserContexts.ts @@ -234,7 +234,8 @@ export class ServerBrowserContexts { async connectDesktopPage(endpoint: string) { const { chromium } = loadPlaywright(); - const browser = await chromium.connectOverCDP(endpoint, { timeout: 15_000 }); + // Only a same-machine desktop backend gets the browser channel, so uploads can pass file paths. + const browser = await chromium.connectOverCDP(endpoint, { isLocal: true, timeout: 15_000 }); const page = browser.contexts().flatMap((context) => context.pages())[0]; if (!page) { await browser.close().catch(constVoid);