[Bug]: Browser downloads time out (30000ms) on dual-stack/VPN environments due to broken Happy Eyeballs in dualStackLookup

Open
#42,799 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, typescript
Domain
networking

Research direction

Start in packages/utils/src/network.ts, reading dualStackLookup and the httpRequest happyEyeballsOptions described in the report. Reproduce with npx playwright install firefox on a dual-stack or VPN environment where IPv6 is unrouted, then verify that the downloader falls back to IPv4 promptly and respects the relevant family or DNS options instead of waiting 30 seconds.

Written by the indexing model from the issue text.

Description

Version

1.63.0

Steps to Reproduce
  1. Connect to any network or VPN interface that has a local IPv6 address configured but does not route public IPv6 internet traffic (or where public IPv6 is black-holed).
  2. Run npx playwright install firefox (or any non-cached browser binary).
  3. Observe repeated 30-second timeouts:
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
Expected behavior

Playwright's downloader should either:

  • Supply 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.
  • Respect options.family / Node's --dns-result-order=ipv4first.
  • Or fall back to IPv4 upon socket connection timeout rather than failing the entire download attempt.
Actual behavior

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.

Additional context
Root Cause Analysis

In packages/utils/src/network.ts:

  1. 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);
    };
    
  2. 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).

  3. 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.

  4. 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.

  5. Furthermore, NODE_OPTIONS="--dns-result-order=ipv4first" has no effect because families = [6, 4] is hardcoded regardless of the runtime's DNS configuration.

Environment
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
Dominant language
TypeScript
Stars
96.4k
Forks
6.5k
Avg merge
1d 7h
Merged PRs (30d)
170

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/playwright

All issues in microsoft/playwright

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.