← Back to microsoft/playwright
microsoft / playwright · Issue No. 42799
1.63.0
npx playwright install firefox (or any non-cached browser binary).Downloading Firefox 155.0 (playwright firefox v1543) from https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/1543/firefox-mac-arm64.zip
Error: Request to https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/1543/firefox-mac-arm64.zip timed out after 30000ms
at ClientRequest.<anonymous> (.../node_modules/playwright-core/lib/coreBundle.js:8793:15)
at TLSSocket.emitRequestTimeout (node:_http_client:1122:9)
...
Failed to install browsers
Error: Failed to download Firefox 155.0 (playwright firefox v1543), caused by
Error: Download failure, code=1
Playwright's downloader should either:
all: true to dualStackLookup so Node's native autoSelectFamily can race IPv6 and IPv4 and fall back to IPv4 within milliseconds when IPv6 connection attempts stall.options.family / Node's --dns-result-order=ipv4first.Running playwright install fails with repeated Request to https://cdn.playwright.dev/... timed out after 30000ms when connected to a dual-stack or corporate VPN network where an IPv6 address is bound to an interface (e.g., ULA fc00::), but public IPv6 internet traffic is unrouted or dropped.
While tools implementing standard Happy Eyeballs (RFC 8305) like curl and web browsers fall back to IPv4 within 250–300ms, Playwright's downloader hangs for the full 30-second socket timeout across all retry attempts.
In packages/utils/src/network.ts:
dualStackLookup hardcodes IPv6 ahead of IPv4:
dualStackLookup = (hostname, options, callback) => {
const families = options.family === 4 || options.family === 6 ? [options.family] : [6, 4];
// ...
if (options.all)
callback(null, addresses);
else
callback(null, addresses[0].address, addresses[0].family);
};
When httpRequest() uses happyEyeballsOptions:
happyEyeballsOptions = {
lookup: dualStackLookup,
autoSelectFamily: true,
autoSelectFamilyAttemptTimeout: Math.max(5e3, net.getDefaultAutoSelectFamilyAttemptTimeout())
};
Because httpRequest does not pass all: true to the lookup options, dualStackLookup only returns addresses[0] (the IPv6 address).
Node's autoSelectFamily: true algorithm requires multiple addresses (or options.all: true) to race or fall back between IPv6 and IPv4. Because only a single IPv6 address is supplied to callback(), Node has no alternative address to fall back to.
When Node attempts to connect to 2603:1061:14:72::1, the SYN packets are silently dropped by the unrouted network, hanging until NET_DEFAULT_TIMEOUT (30000ms) fires.
Furthermore, NODE_OPTIONS="--dns-result-order=ipv4first" has no effect because families = [6, 4] is hardcoded regardless of the runtime's DNS configuration.
System:
OS: macOS 26.6.2
CPU: (12) arm64 Apple M2 Max
Memory: 81.27 MB / 32.00 GB
Binaries:
Node: 24.11.0 - /Users/{username}/.local/share/mise/installs/node/24.11.0/bin/node
Yarn: 4.18.0 - /Users/{username}/.local/share/mise/installs/node/24.11.0/bin/yarn
npm: 11.6.1 - /Users/{username}/.local/share/mise/installs/node/24.11.0/bin/npm
IDEs:
VSCode: 1.138.0 - /Applications/Visual Studio Code.app/Contents/Resources/app/bin/code
Languages:
Bash: 3.2.57 - /bin/bash
npmPackages:
@playwright/test: ^1.63.0 => 1.63.0
playwright: ^1.63.0 => 1.63.0
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.