From 436adb72d37a6987442839fcd118aec379991c9f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 16 Aug 2026 09:48:13 -0700 Subject: [PATCH] =?UTF-8?q?fix(deps):=20bun-patch=20playwright-core=201.62?= =?UTF-8?q?.1=20=E2=80=94=20windowsHide=20at=20launch=20+=20taskkill=20(#2?= =?UTF-8?q?160,=20#1989)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo's first patchedDependencies entry. playwright-core's bundled process launcher (lib/coreBundle.js in the 1.62.x layout) spawns browser children without windowsHide — Node defaults it to FALSE for child_process.spawn — so Chromium children could flash a console window on Windows, and its force-kill path shells `taskkill /pid /T /F` through cmd.exe with the same omission. Both sites now pass windowsHide: true via patches/playwright-core@1.62.1.patch (generated with `bun patch` / `bun patch --commit`). Coherence verified end-to-end: rm -rf node_modules && bun install applies the patch cleanly (both sites present in the reinstalled tree), and a real chromium.launch() through the patched bundle works. browse/test/playwright-core-patch.test.ts pins the three-legged invariant statically — package.json's patchedDependencies key is VERSION-KEYED against the installed playwright-core, the patch file exists and carries both sites, bun.lock records the patch, and the installed bundle actually has it applied — so a future playwright bump that forgets to re-target the patch fails CI with the exact key to regenerate (revert pairing: dropping the c25 bump requires dropping this patch too). Tests: playwright-core-patch 4 pass, 0 fail. Fixes #2160, #1989. Co-Authored-By: Claude Fable 5 --- browse/test/playwright-core-patch.test.ts | 62 +++++++++++++++++++++++ bun.lock | 3 ++ package.json | 3 ++ patches/playwright-core@1.62.1.patch | 24 +++++++++ 4 files changed, 92 insertions(+) create mode 100644 browse/test/playwright-core-patch.test.ts create mode 100644 patches/playwright-core@1.62.1.patch diff --git a/browse/test/playwright-core-patch.test.ts b/browse/test/playwright-core-patch.test.ts new file mode 100644 index 000000000..d09e136c9 --- /dev/null +++ b/browse/test/playwright-core-patch.test.ts @@ -0,0 +1,62 @@ +/** + * #2160/#1989: playwright-core is bun-patched to pass windowsHide at its two + * Windows-visible child_process sites — the browser launch spawn (Node + * defaults windowsHide to FALSE for spawn, so browser children could flash a + * console window) and the force-kill taskkill spawnSync. This is the repo's + * first patchedDependencies entry; these static checks pin the three-legged + * coherence (patch file ↔ package.json ↔ installed tree) so a playwright + * bump that forgets to re-target the patch fails CI instead of silently + * dropping it. NOTE: bumping playwright (c25-style) REQUIRES regenerating + * this patch against the new version — see the revert pairing in the wave + * plan (reverting the bump means dropping the patch too). + */ + +import { describe, expect, test } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..', '..'); + +function installedPlaywrightCoreVersion(): string { + const pkg = JSON.parse(fs.readFileSync( + path.join(ROOT, 'node_modules', 'playwright-core', 'package.json'), 'utf-8', + )); + return pkg.version as string; +} + +describe('playwright-core windowsHide patch (#2160, #1989)', () => { + test('package.json declares the patch for the installed version', () => { + const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8')); + const version = installedPlaywrightCoreVersion(); + const key = `playwright-core@${version}`; + expect(pkg.patchedDependencies).toBeDefined(); + // Version-keyed on purpose: if playwright is bumped without re-targeting + // the patch, this fails with the exact key that needs regenerating. + expect(pkg.patchedDependencies[key]).toBe(`patches/playwright-core@${version}.patch`); + }); + + test('the patch file exists and carries both windowsHide sites', () => { + const version = installedPlaywrightCoreVersion(); + const patchPath = path.join(ROOT, 'patches', `playwright-core@${version}.patch`); + expect(fs.existsSync(patchPath)).toBe(true); + const patch = fs.readFileSync(patchPath, 'utf-8'); + // Launch spawnOptions site. + expect(patch).toContain('+ windowsHide: true,'); + // taskkill force-kill site. + expect(patch).toContain('shell: true, windowsHide: true'); + }); + + test('bun.lock is coherent: the lockfile records the patched dependency', () => { + const lock = fs.readFileSync(path.join(ROOT, 'bun.lock'), 'utf-8'); + const version = installedPlaywrightCoreVersion(); + expect(lock).toContain(`patches/playwright-core@${version}.patch`); + }); + + test('the INSTALLED tree actually has the patch applied (bun install ran it)', () => { + const bundle = fs.readFileSync( + path.join(ROOT, 'node_modules', 'playwright-core', 'lib', 'coreBundle.js'), 'utf-8', + ); + expect(bundle).toContain('gstack patch (#2160/#1989)'); + expect(bundle).toContain('shell: true, windowsHide: true'); + }); +}); diff --git a/bun.lock b/bun.lock index 724d0cd42..e12f18e33 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,9 @@ }, }, }, + "patchedDependencies": { + "playwright-core@1.62.1": "patches/playwright-core@1.62.1.patch", + }, "overrides": { "basic-ftp": "5.3.1", }, diff --git a/package.json b/package.json index e6667c14c..55a97927b 100644 --- a/package.json +++ b/package.json @@ -82,5 +82,8 @@ }, "overrides": { "basic-ftp": "5.3.1" + }, + "patchedDependencies": { + "playwright-core@1.62.1": "patches/playwright-core@1.62.1.patch" } } diff --git a/patches/playwright-core@1.62.1.patch b/patches/playwright-core@1.62.1.patch new file mode 100644 index 000000000..b270adbdb --- /dev/null +++ b/patches/playwright-core@1.62.1.patch @@ -0,0 +1,24 @@ +diff --git a/lib/coreBundle.js b/lib/coreBundle.js +index d9041dd0f009072511a7d808dec83121ff29e127..e5c72c452c98f1c64038c5f24ac5805fbc99151f 100644 +--- a/lib/coreBundle.js ++++ b/lib/coreBundle.js +@@ -8906,6 +8906,10 @@ async function launchProcess(options) { + env: options.env, + cwd: options.cwd, + shell: options.shell, ++ // gstack patch (#2160/#1989): Node defaults windowsHide to false for ++ // child_process.spawn, so browser children could flash a console window ++ // on Windows. Playwright never needs the child's window shown. ++ windowsHide: true, + stdio + }; + const spawnedProcess = childProcess.spawn(options.command, options.args || [], spawnOptions); +@@ -8985,7 +8989,7 @@ async function launchProcess(options) { + options.log(`[pid=${spawnedProcess.pid}] `); + try { + if (process.platform === "win32") { +- const taskkillProcess = childProcess.spawnSync(`taskkill /pid ${spawnedProcess.pid} /T /F`, { shell: true }); ++ const taskkillProcess = childProcess.spawnSync(`taskkill /pid ${spawnedProcess.pid} /T /F`, { shell: true, windowsHide: true }); + const [stdout2, stderr2] = [taskkillProcess.stdout.toString(), taskkillProcess.stderr.toString()]; + if (stdout2) + options.log(`[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout2}`);