From edf4462e2b286771d74a5f3f0c1c5cce4ea814cf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 3 Nov 2026 18:24:00 +0000 Subject: [PATCH 1/3] test(server): reproduce desktop preview uploads over 50 MB failing (#30287) These tests reproduce #30287: connectDesktopPage connects over CDP without isLocal, so Playwright refuses to hand a file over 50 MB to the desktop browser even though it shares the server's filesystem. Co-Authored-By: Claude Opus 5.5 --- .../src/preview/ServerBrowserContexts.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/apps/server/src/preview/ServerBrowserContexts.test.ts b/apps/server/src/preview/ServerBrowserContexts.test.ts index 522df30fb66c8..d5910ebfbfb1e 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 })); @@ -293,4 +298,41 @@ describe("ServerBrowserContexts", () => { expect(browser.close).toHaveBeenCalledTimes(1); expect(browser.contexts[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 desktop = await launchPersistentChromium(userDataDir, { + headless: true, + args: ["--remote-debugging-port=0"], + }); + const pool = new ServerBrowserContexts(options()); + 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(); + NodeFS.rmSync(directory, { recursive: true, force: true }); + } + }); }); From cfb39e13c998ab3eb940bfa130bef7275dc905b3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 3 Nov 2026 18:27:22 +0000 Subject: [PATCH 2/3] fix(server): desktop preview uploads accept files over 50 MB connectDesktopPage connected over CDP without isLocal, so Playwright treated the desktop browser as remote and refused files over 50 MB. Only the desktop's same-machine backend gets the browser channel, so pass isLocal and let Playwright hand file paths to DOM.setFileInputFiles. Fixes pingdotgg/t3code#30287 Co-Authored-By: Claude Opus 5.5 --- apps/server/src/preview/ServerBrowserContexts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/preview/ServerBrowserContexts.ts b/apps/server/src/preview/ServerBrowserContexts.ts index 2a7c342d30d81..36d1609b42ca7 100644 --- a/apps/server/src/preview/ServerBrowserContexts.ts +++ b/apps/server/src/preview/ServerBrowserContexts.ts @@ -200,7 +200,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); From 234c653f5fee9e229deb71269564bfe87482e966 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 3 Nov 2026 18:34:38 +0000 Subject: [PATCH 3/3] test(server): clean up the desktop upload test's directory when Chromium fails to launch Co-Authored-By: Claude Opus 5.5 --- .../src/preview/ServerBrowserContexts.test.ts | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/apps/server/src/preview/ServerBrowserContexts.test.ts b/apps/server/src/preview/ServerBrowserContexts.test.ts index d5910ebfbfb1e..4518af55e722e 100644 --- a/apps/server/src/preview/ServerBrowserContexts.test.ts +++ b/apps/server/src/preview/ServerBrowserContexts.test.ts @@ -298,40 +298,44 @@ describe("ServerBrowserContexts", () => { expect(browser.close).toHaveBeenCalledTimes(1); expect(browser.contexts[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 desktop = await launchPersistentChromium(userDataDir, { - headless: true, - args: ["--remote-debugging-port=0"], - }); const pool = new ServerBrowserContexts(options()); try { - const [port] = NodeFS.readFileSync( - NodePath.join(userDataDir, "DevToolsActivePort"), - "utf8", - ).split("\n"); - const { browser, page } = await pool.connectDesktopPage(`http://127.0.0.1:${port}`); + const desktop = await launchPersistentChromium(userDataDir, { + headless: true, + args: ["--remote-debugging-port=0"], + }); 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 }), + 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 }]); + ).toEqual([{ name: "large.bin", size: 51 * 1024 * 1024 }]); + } finally { + await browser.close(); + } } finally { - await browser.close(); + await pool.close(); + await desktop.close(); } } finally { - await pool.close(); - await desktop.close(); NodeFS.rmSync(directory, { recursive: true, force: true }); } });