← Back to microsoft/playwright
microsoft / playwright · Issue No. 42768
Playwright's bundled Firefox resolves its app-data directory to ~/Library/Application Support/Firefox — the same directory the real Firefox uses. On macOS 27 that directory is TCC-protected, so on any machine with Firefox installed, every firefox.launch() hangs until it times out unless the host process (terminal, editor, CI agent) has Full Disk Access.
Chromium is immune because Playwright gives it --user-data-dir=<temp>, which is its complete state root. Firefox's -profile only sets the profile; Gecko still separately resolves the shared app-data root for profiles.ini / installs.ini bookkeeping — and that lookup is what gets denied.
This is not the same as #42082 (closed as upstream). That report's key unexplained detail — "stock Nightly double-clicked from Finder does launch — it's the command-line launch with a fresh profile that fails" — is precisely what TCC predicts: launched from Finder, Firefox is its own responsible process and may access its own protected directory; launched from a terminal, the terminal is the responsible process and is denied.
Playwright: 1.58.2 (firefox-1509 / Firefox 146.0.1)
and: 1.62.1 (firefox-1538 / Firefox 153.0) <- both reproduce identically
OS: macOS 27.0 (26A428), Apple Silicon arm64
launch: Timeout 180000ms exceeded.
Call log:
- <launching> .../firefox-1509/firefox/Nightly.app/Contents/MacOS/firefox -no-remote -headless -profile /var/folders/.../playwright_firefoxdev_profile-XXXX -juggler-pipe -silent
- <launched> pid=55386
- [err] *** You are running in headless mode.
- [err] sandbox_extension_issue_file_to_process failed for .../plugin-container.app: 1 (Operation not permitted)
- [out] Crash Annotation GraphicsCriticalError: RenderCompositorSWGL failed mapping default framebuffer, no dt
The temp profile directory is created but stays completely empty. Headed, Firefox puts up a modal "Profile Missing — Your Firefox profile cannot be loaded. It may be missing or inaccessible." Headless the modal cannot render, so the process spins on the main thread (sample shows SLEventSourceCounterForEventType → CGSEventSourceForID → CGSScoreboard in SkyLight) burning CPU until Playwright's timeout.
sandbox_extension_issue_file_to_process ... Operation not permitted is emitted by working Gecko browsers on the same machine. Zen (a Firefox fork, Developer ID signed) prints the identical line and starts fine, creating a full 25-file profile. The RenderCompositorSWGL line likewise appears on successful startups. Diagnosing from these two lines is what led #42082 to the wrong conclusion.
The app-data directory is TCC-protected — permissive mode, correct owner, still denied:
$ ls -ld ~/Library/Application\ Support/Firefox
drwxr-xr-x@ 8 user staff 256 ... /Users/user/Library/Application Support/Firefox
$ ls ~/Library/Application\ Support/Firefox/
ls: ...: Operation not permitted
$ touch ~/Library/Application\ Support/Firefox/.probe
touch: ...: Operation not permitted
No filesystem flags, no ACL, no com.apple.quarantine. Scanning ~/Library/Application Support, the denied entries are Apple's privacy directories plus exactly two browsers — both installed, both protected:
DENIED Firefox, Microsoft Edge
OK Google, Chromium, BraveSoftware, Vivaldi, Arc, com.operasoftware.Opera
Proof it is the app-data directory and nothing else. Playwright's Contents/Resources/application.ini is branded:
[App]
Vendor=Mozilla
Name=Firefox
Pointing the same binary at an identical application.ini with only Name/Vendor changed makes it start immediately:
$ firefox -app .../Contents/Resources/browser/application.ini \
-no-remote -headless -profile /tmp/p -juggler-pipe -silent
Juggler listening to the pipe # <- starts
$ ls /tmp/p | wc -l
20 # <- profile written
(-app must be the first argument, so this is not expressible through launchOptions.args.)
Proof it is not Playwright's build or its code signature. Stock /Applications/Firefox.app (Developer ID, org.mozilla.firefox) fails the same way from the command line, with Playwright not involved:
$ /Applications/Firefox.app/Contents/MacOS/firefox -no-remote -headless -profile /tmp/realfx -silent
*** You are running in headless mode.
Could not find profile folder.
Also ruled out: re-signing Playwright's bundle so codesign --verify --deep --strict passes (still fails); relocating the browser out of ~/Library/Caches (still fails); XRE_PROFILE_PATH, MOZ_LEGACY_PROFILES, MOZ_DISABLE_*_SANDBOX (no effect).
Even though the denial originates in macOS, Playwright is uniquely positioned to be unaffected. The bundled browser is already branded CodeName=Nightly and is not a user-facing Firefox install — it has no reason to share the real Firefox's app-data directory. Giving the bundled build its own Name/Vendor (e.g. PlaywrightFirefox) would make it self-contained in the same way --user-data-dir makes Chromium self-contained, and it would sidestep this class of OS-level protection permanently.
Verified above: with a distinct Name/Vendor, the exact same binary launches and writes its profile with no permissions granted at all.
On macOS 27, any developer or CI machine with Firefox installed loses all Playwright Firefox runs. The only workarounds today are granting Full Disk Access to every host application individually (terminal, editor, CI agent — I confirmed Terminal.app and an editor fail independently, each needing its own grant), or uninstalling Firefox.
const { firefox } = require('playwright');
// On macOS 27 with /Applications/Firefox.app installed,
// run from a terminal that does NOT have Full Disk Access:
(async () => { await firefox.launch(); })(); // hangs until timeout
Happy to test a patched build on macOS 27 — I have a reliable reproduction and the diagnosis above was done on that machine.
Relay reads this issue against the repository's contribution signals: the files it is likely to touch, how the maintainers triage work this size, and what the first contribution would exercise.
The full analysis for this issue is still being assembled. Until then, the description above and the thread on GitHub are the most reliable context.