mirror of https://github.com/garrytan/gstack.git
fix(windows): patch playwright-core processLauncher to pass windowsHide
chrome-headless-shell is a console-subsystem exe; launched by playwright-core from the console-less browse daemon on Windows it allocates a visible console window per browser launch, and the launcher force-kill path shells out to taskkill (a cmd.exe window per kill — the node_modules twin of the vendored-copy gap in #1989). Upstream playwright sets no windowsHide and offers no knob, so this cannot be fixed from gstack call sites the way #2153 fixed the polyfill spawns. Carry a bun patchedDependencies patch adding windowsHide: true to the launcher spawnOptions and the taskkill spawnSync options. Applies automatically on every bun install. Tripwire test reads the INSTALLED node_modules file so a playwright version bump that orphans the exact-version patch key fails CI instead of silently re-flashing consoles. Fixes #2160. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
11de390be1
commit
eea2192758
|
|
@ -0,0 +1,52 @@
|
|||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Windows console-flash guard for the Playwright browser launcher (#2151
|
||||
// follow-up; siblings: #1835, #1989).
|
||||
//
|
||||
// On Windows the browse server runs console-less (node server-node.mjs,
|
||||
// detached from the CLI). chrome-headless-shell is a console-subsystem
|
||||
// exe, so playwright-core's processLauncher spawning it without
|
||||
// windowsHide allocates a visible console window per launch, and its
|
||||
// force-kill path shells out to `taskkill` (another window). Upstream
|
||||
// playwright doesn't set windowsHide, so we carry a bun
|
||||
// `patchedDependencies` patch. These tripwires pin:
|
||||
// 1. package.json declares the patch for the installed version — if a
|
||||
// playwright bump orphans the patch key, test 3 catches the silent
|
||||
// regression (bun applies patches per exact version).
|
||||
// 2. the patch file itself still contains both windowsHide sites.
|
||||
// 3. the INSTALLED node_modules file actually got patched.
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..', '..');
|
||||
const INSTALLED_LAUNCHER = path.join(
|
||||
ROOT, 'node_modules', 'playwright-core', 'lib', 'server', 'utils', 'processLauncher.js',
|
||||
);
|
||||
|
||||
describe('playwright-core processLauncher windowsHide patch', () => {
|
||||
test('1. package.json declares a patchedDependencies entry matching the installed playwright-core version', () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
||||
const installed = JSON.parse(fs.readFileSync(
|
||||
path.join(ROOT, 'node_modules', 'playwright-core', 'package.json'), 'utf-8',
|
||||
));
|
||||
const key = `playwright-core@${installed.version}`;
|
||||
expect(pkg.patchedDependencies?.[key]).toBeDefined();
|
||||
expect(fs.existsSync(path.join(ROOT, pkg.patchedDependencies[key]))).toBe(true);
|
||||
});
|
||||
|
||||
test('2. the patch covers both spawn sites (browser launch + taskkill)', () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
||||
const patchRel = Object.entries(pkg.patchedDependencies ?? {})
|
||||
.find(([k]) => k.startsWith('playwright-core@'))?.[1] as string;
|
||||
const patch = fs.readFileSync(path.join(ROOT, patchRel), 'utf-8');
|
||||
expect(patch).toContain('+ windowsHide: true,');
|
||||
expect(patch).toContain('{ shell: true, windowsHide: true }');
|
||||
});
|
||||
|
||||
test('3. the installed processLauncher.js is actually patched', () => {
|
||||
const src = fs.readFileSync(INSTALLED_LAUNCHER, 'utf-8');
|
||||
const launchBlock = src.slice(src.indexOf('async function launchProcess'), src.indexOf('const spawnedProcess'));
|
||||
expect(launchBlock).toContain('windowsHide: true');
|
||||
expect(src).toMatch(/taskkill[^\n]*\{ shell: true, windowsHide: true \}/);
|
||||
});
|
||||
});
|
||||
3
bun.lock
3
bun.lock
|
|
@ -22,6 +22,9 @@
|
|||
},
|
||||
},
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"playwright-core@1.58.2": "patches/playwright-core@1.58.2.patch",
|
||||
},
|
||||
"packages": {
|
||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.117", "", { "dependencies": { "@anthropic-ai/sdk": "^0.81.0", "@modelcontextprotocol/sdk": "^1.29.0" }, "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.117" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-pVBss1Vu0w87nKCBhWtjMggSgCh6GVUtdRmuE58ZvXv0E2q0JcnUCQHehmn92BAW0+VCwPY8q/k7uKWkgwz/gA=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -76,5 +76,8 @@
|
|||
"@anthropic-ai/sdk": "^0.78.0",
|
||||
"xterm": "5",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"playwright-core@1.58.2": "patches/playwright-core@1.58.2.patch"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
diff --git a/lib/server/utils/processLauncher.js b/lib/server/utils/processLauncher.js
|
||||
index 954d5e6fbc527e640ccec5709263374101c6340b..6913f7e9cafe6bcc57ce5a501ce05edc01d36e0c 100644
|
||||
--- a/lib/server/utils/processLauncher.js
|
||||
+++ b/lib/server/utils/processLauncher.js
|
||||
@@ -112,6 +112,10 @@ async function launchProcess(options) {
|
||||
env: options.env,
|
||||
cwd: options.cwd,
|
||||
shell: options.shell,
|
||||
+ // gstack patch (#2151 follow-up): chrome-headless-shell is a console-
|
||||
+ // subsystem exe; launched from the console-less browse daemon on
|
||||
+ // Windows it allocates a visible console window without CREATE_NO_WINDOW.
|
||||
+ windowsHide: true,
|
||||
stdio
|
||||
};
|
||||
const spawnedProcess = childProcess.spawn(options.command, options.args || [], spawnOptions);
|
||||
@@ -191,7 +195,7 @@ async function launchProcess(options) {
|
||||
options.log(`[pid=${spawnedProcess.pid}] <will force kill>`);
|
||||
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}`);
|
||||
Loading…
Reference in New Issue