Files
gstack/test/setup-playwright-platform.test.ts
T
87801b6da7 v1.94.31.0 fix(browse): Playwright 1.64.1 (Chromium 156) for the macOS sandbox-escape CVE; handoff honors GSTACK_CHROMIUM_PATH (#3947)
* test(browse): failing tests for #3907 (handoff ignores GSTACK_CHROMIUM_PATH; bundled Chromium predates CVE-2026-106378 fix)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): handoff honors GSTACK_CHROMIUM_PATH (#3907)

handoff() always launched Playwright's bundled Chromium, so a GSTACK_CHROMIUM_PATH browser dropped back to the bundle at handoff. It now passes executablePath like launch() and launchHeaded(), skips the poisoned-bundle probe and XProtect heal for a custom path, and never loads the extension into a build that bakes it in.

Co-authored-by: merit-blake <265362871+merit-blake@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(deps): Playwright 1.64.1 (Chromium 156) for CVE-2026-106378 (#3907)

Playwright 1.62.1 bundled Chromium 151.0.7922.34, which lacks the macOS GPU-process TCC fix (Chromium 0c2f3e54b265, crbug.com/507596239). 1.64.1 bundles 156.0.8078.4, which has it. The playwright-core patch is regenerated from our 1.62.1 patch: the launch windowsHide and TcpPortRandomizationWin hunks carry over unchanged; the taskkill hunk is dropped because 1.64 passes windowsHide there upstream.

Co-authored-by: merit-blake <265362871+merit-blake@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(pregate): map patches/** to the playwright-core patch test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(browse): name the 1.64.1 patch file in the TcpPortRandomizationWin check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): fragment for Playwright 1.64.1 and handoff GSTACK_CHROMIUM_PATH (#3907)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(troubleshooting): GSTACK_CHROMIUM_PATH covers headed browse and handoff

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(browse): the retired 1.62.1 patch stays deleted

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: merit-blake <265362871+merit-blake@users.noreply.github.com>
2026-11-03 18:34:14 +00:00

89 lines
5.1 KiB
TypeScript

import { afterEach, describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { runBashScript } from './helpers/bash-script';
const ROOT = resolve(import.meta.dir, '..');
const source = readFileSync(join(ROOT, 'setup'), 'utf8');
const owned: string[] = [];
afterEach(() => { for (const dir of owned.splice(0)) rmSync(dir, { recursive: true, force: true }); });
function temp() { const dir = mkdtempSync(join(tmpdir(), 'gstack-pw-platform-')); owned.push(dir); return dir; }
function slice(start: string, end: string) {
const a = source.indexOf(start), b = source.indexOf(end, a);
if (a < 0 || b < 0) throw new Error('setup anchors missing');
return source.slice(a, b);
}
function fn(name: string) { return slice(`${name}() {`, '\n}\n') + '\n}'; }
function platform(id: string, version: string, cpu: string, wholeBlock = false) {
const dir = temp();
const osRelease = join(dir, 'os-release');
writeFileSync(osRelease, `ID=${id}\nVERSION_ID="${version}"\n`);
const converted = process.platform === 'win32'
? spawnSync('bash', ['-c', 'cygpath -u "$1"', '_', osRelease], { encoding: 'utf8', timeout: 10_000 })
: null;
if (converted && converted.status !== 0) throw new Error(`Cannot resolve Bash fixture path: ${converted.stderr}`);
const selector = slice("# 2. Ensure Playwright's Chromium is available", wholeBlock ? '# 2b. Ensure a color-emoji font' : '# Chromium is BEST-EFFORT')
.replaceAll('/etc/os-release', JSON.stringify(converted ? converted.stdout.trim() : osRelease));
return runBashScript([
'set -e', `uname() { echo ${JSON.stringify(cpu)}; }`,
`SOURCE_GSTACK_DIR=${JSON.stringify(dir)}`, `TMPDIR=${JSON.stringify(dir)}`, 'IS_WINDOWS=0',
'ensure_playwright_browser() { echo PROBED; return 0; }',
'bunx() { echo INSTALL_ATTEMPTED; return 0; }',
selector,
'printf "OVERRIDE=%s\\nUNSUPPORTED=%s\\nREASON=%s\\n" "$_PLAYWRIGHT_PLATFORM_OVERRIDE" "${_PLAYWRIGHT_UNSUPPORTED_ARCH:-}" "${_PW_FAIL_REASON:-}"',
].join('\n'), { env: { PATH: process.env.PATH, HOME: dir }, timeout: 10_000 });
}
describe('Ubuntu fallback retains CPU architecture', () => {
for (const [cpu, expected] of [['x86_64', 'x64'], ['aarch64', 'arm64'], ['arm64', 'arm64']]) {
test(`${cpu} selects the matching real Playwright artifact`, () => {
const result = platform('ubuntu', '26.04', cpu!);
expect(result.status, result.stderr).toBe(0);
const override = result.stdout.match(/^OVERRIDE=(.*)$/m)![1]!;
expect(override).toBe(`ubuntu24.04-${expected}`);
const dryRun = spawnSync(process.execPath, [join(ROOT, 'node_modules/playwright/cli.js'), 'install', '--dry-run', 'chromium'], {
cwd: ROOT, env: { PATH: process.env.PATH, HOME: temp(), PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: override }, encoding: 'utf8', timeout: 20_000,
});
expect(dryRun.status, dryRun.stderr).toBe(0);
expect(dryRun.stdout).toContain(expected === 'arm64' ? 'chrome-linux-arm64.zip' : 'chrome-linux64.zip');
expect(dryRun.stdout).not.toContain(expected === 'arm64' ? 'chrome-linux64.zip' : 'chrome-linux-arm64.zip');
}, 30_000);
}
for (const cpu of ['riscv64', 'i686', 'unknown']) test(`${cpu} reports unsupported instead of installing x64`, () => {
const result = platform('ubuntu', '26.04', cpu, true);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain('OVERRIDE=\n');
expect(result.stdout).toContain(`UNSUPPORTED=${cpu}\n`);
expect(result.stdout).toContain('REASON=unsupported-platform\n');
expect(result.stdout).not.toContain('INSTALL_ATTEMPTED');
expect(result.stderr).toContain(cpu);
});
for (const [id, version] of [['ubuntu', '24.04'], ['debian', '13']]) test(`${id} ${version} keeps native detection`, () => {
const result = platform(id!, version!, 'aarch64');
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toBe('OVERRIDE=\nUNSUPPORTED=\nREASON=\n');
});
});
describe('actual setup launch probe diagnostics', () => {
for (const error of ['', 'spawn chromium ENOEXEC: wrong executable architecture', 'Missing shared library: libexample.so']) {
test(error || 'successful launch is silent', () => {
const dir = temp();
mkdirSync(join(dir, 'node_modules/playwright'), { recursive: true });
writeFileSync(join(dir, 'node_modules/playwright/index.js'), `module.exports = { chromium: { launch: async () => { ${error ? `throw new Error(${JSON.stringify(error)})` : 'return { close: async () => {} }'} } } };\n`);
const result = runBashScript([
'set -e', `SOURCE_GSTACK_DIR=${JSON.stringify(dir)}`, 'IS_WINDOWS=0',
fn('_kill_tree'), fn('_wait_with_deadline'), fn('ensure_playwright_browser'),
'ensure_playwright_browser',
].join('\n'), { env: { PATH: process.env.PATH, HOME: dir, TMPDIR: dir }, timeout: 10_000 });
expect(result.status).toBe(error ? 1 : 0);
expect(result.stdout).toBe('');
if (error) expect(result.stderr).toContain(error);
else expect(result.stderr).toBe('');
});
}
});