feat: strip macOS Gatekeeper quarantine after browser install
macOS sets com.apple.quarantine on network-downloaded files. On Tahoe (macOS 26) and later, Gatekeeper enforcement silently blocks launch of quarantined Chromium binaries, causing confusing "browser failed to start" errors after a successful install. After `playwright install` completes on darwin, run `xattr -dr com.apple.quarantine` against the browser cache directory (~/Library/Caches/ms-playwright by default, or PLAYWRIGHT_BROWSERS_PATH when set). Best-effort: errors are logged via testDebug and never thrown. Skipped on Linux/Windows and when PLAYWRIGHT_BROWSERS_PATH=0 (node_modules install path doesn't get quarantined).
This commit is contained in:
parent
6ae3991efb
commit
4bb1b26137
@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fork } from 'node:child_process';
|
||||
import { fork, spawn } from 'node:child_process';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@ -62,6 +63,12 @@ async function runInstall(target: string): Promise<void> {
|
||||
|
||||
await runPlaywrightCli(cliPath, ['install', target]);
|
||||
|
||||
// macOS: strip Gatekeeper quarantine attribute from freshly downloaded
|
||||
// browser binaries. Without this, the first launch is silently blocked
|
||||
// by macOS on a fresh install.
|
||||
if (process.platform === 'darwin')
|
||||
await stripDarwinQuarantine();
|
||||
|
||||
// Best-effort system-deps install. Only runs when we're already root,
|
||||
// otherwise skipped silently — users will see Playwright's own missing-lib
|
||||
// error on the next launch, which tells them exactly what to apt install.
|
||||
@ -74,6 +81,40 @@ async function runInstall(target: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the `com.apple.quarantine` extended attribute from Playwright's
|
||||
* browser cache directory. macOS sets this on anything downloaded from the
|
||||
* network; on Tahoe and later it causes Gatekeeper to silently block launch.
|
||||
*
|
||||
* Best-effort: errors are logged but never thrown. If `xattr` doesn't exist,
|
||||
* the cache dir is missing, or the attribute isn't present, we just move on.
|
||||
*/
|
||||
async function stripDarwinQuarantine(): Promise<void> {
|
||||
const cacheDir = process.env.PLAYWRIGHT_BROWSERS_PATH
|
||||
|| path.join(os.homedir(), 'Library', 'Caches', 'ms-playwright');
|
||||
|
||||
// PLAYWRIGHT_BROWSERS_PATH=0 means "install into node_modules" — skip,
|
||||
// since those binaries weren't downloaded with quarantine in that flow.
|
||||
if (cacheDir === '0')
|
||||
return;
|
||||
|
||||
testDebug(`stripping quarantine attribute from ${cacheDir}`);
|
||||
await new Promise<void>(resolve => {
|
||||
const child = spawn('/usr/bin/xattr', ['-dr', 'com.apple.quarantine', cacheDir], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
child.on('close', code => {
|
||||
if (code !== 0)
|
||||
testDebug(`xattr exited ${code} (non-fatal)`);
|
||||
resolve();
|
||||
});
|
||||
child.on('error', err => {
|
||||
testDebug(`xattr spawn failed (non-fatal): ${err.message}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePlaywrightCli(): string {
|
||||
try {
|
||||
const cliUrl = import.meta.resolve('playwright/package.json');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user