Skip to content

[Bug]: Browser download retries do not reach IPv4 after an IPv6 TLS timeout #42193

Description

Version

1.62.1

Steps to reproduce

Minimal reproduction: https://github.com/martinblech/playwright-ipv6-tls-fallback-repro

  1. Clone the repository.
  2. Run npm ci.
  3. Run npx playwright --version (it prints Version 1.62.1).
  4. Run npm test.

The repository uses only Node built-ins and playwright@1.62.1. It runs the real Playwright browser installer against local loopback listeners, so it does not contact the Playwright CDN or download a browser.

npm test runs two cases on the same installer code path:

  1. Control: the IPv4 listener at 127.0.0.1 completes HTTPS, while nothing accepts IPv6 TCP. Playwright reaches IPv4. The listener deliberately returns HTTP 404 because this case tests address-family reachability, not archive contents.
  2. Reproduction: the same IPv4 HTTPS listener remains available, while a listener at ::1 accepts TCP and deliberately never completes TLS. Playwright makes five IPv6 TCP connections, reports five download timeouts, and never reaches IPv4.

The script points PLAYWRIGHT_DOWNLOAD_HOST at https://localhost:<random-port>, clears proxy variables for the installer child process, uses a fresh PLAYWRIGHT_BROWSERS_PATH, and sets PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT=1000 only to shorten the deterministic wait.

Expected behavior

When IPv6 accepts TCP but cannot establish HTTPS, while a resolved IPv4 address can establish HTTPS, at least one of the browser installer's retries should reach IPv4 instead of every retry selecting the same stalled IPv6 route.

I do not have a preference about whether fallback happens within the same attempt or on a later retry.

Actual behavior

The control confirms that the local IPv4 HTTPS endpoint is reachable by the installer:

IPv6 TCP connections accepted: 0
IPv4 HTTPS requests received: 5
Playwright installer exit code: 1
Playwright timeout errors: 0
CONTROL PASSED: Playwright reached IPv4 when IPv6 TCP failed.

With IPv6 accepting TCP but stalling TLS, every installer attempt times out and IPv4 receives no requests:

Error: Request to https://localhost:<port>/builds/... timed out after 1000ms
...
IPv6 TCP connections accepted: 5
IPv4 HTTPS requests received: 0
Playwright installer exit code: 1
Playwright timeout errors: 5
BUG REPRODUCED: IPv6 accepted TCP but stalled TLS; Playwright did not reach working IPv4.

I reproduced the same behavior on macOS/arm64 with Node 26.7.0 and in a clean Debian 12/arm64 container with Node 22.23.2.

Additional context

This arose from a real browser installation failure, not only from source inspection. I reproduced the CDN failure in a throwaway npm project using playwright@1.62.1, Node 24.2.0, a fresh browser cache, the default 30-second timeout, no NODE_OPTIONS, and no HTTP(S) or SOCKS proxy. The installer made five download attempts to the same CDN URL; every attempt timed out after 30000ms, and installation exited with code 1. The same result occurs with Node 26.

On that network, the exact CDN URL produces these direct controls:

family=IPv4 status=307 tcp=0.016493s tls=0.048759s total=0.061296s
family=IPv6 status=000 tcp=0.083410s tls=0.000000s total=5.004128s
curl: (28) SSL connection timeout

The nonzero IPv6 TCP time and absent TLS completion match a verbose trace in which TCP connects, a TLS exchange begins, and the handshake does not complete. IPv6 HTTPS to unrelated hosts and to the eventual Google Storage redirect target succeeds from the same machine. I cannot determine from one network whether the underlying TLS stall is caused by the local router, ISP/peering path, or remote CDN path. I am not claiming that Playwright causes that network condition, or that the localhost reproduction duplicates the CDN's TLS packets; it reproduces the relevant failure class in which TCP connects but HTTPS never becomes usable.

The behavior I am reporting is that Playwright's five download retries do not use the available IPv4 path. This is not a request for a global IPv4-only mode.

One source observation that appears consistent with the reproduction: in happyEyeballs.ts for 1.62.1, the TLS path resolves an address attempt on the socket's connect event, after which the other sockets are destroyed. This happens before secureConnect. httpRequest uses this agent for HTTPS. I am not asserting what the preferred implementation fix should be.

Related reports:

  • #40041 requested an IPv4-only option; the maintainer response invited an exact bug reproduction.
  • #41068 reported a proxy-related IPv6 failure. This report keeps both IPv4 and IPv6 candidates available, uses no proxy, and distinguishes successful IPv6 TCP from a stalled TLS handshake.

I can test a proposed change on the affected network. I am not opening a pull request; if maintainers would like a community contribution, I will wait for explicit approval or assignment before starting one.

Environment

  System:
    OS: macOS 26.5.2
    CPU: (8) arm64 Apple M2
    Memory: 244.23 MB / 24.00 GB
  Binaries:
    Node: 26.7.0 - /opt/homebrew/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 11.19.0 - /opt/homebrew/bin/npm
    pnpm: 11.16.0 - /Users/martinblech/.cache/codex-runtimes/codex-primary-runtime/dependencies/bin/fallback/pnpm
    Deno: 2.9.5 - /opt/homebrew/bin/deno
  IDEs:
    VSCode: 1.132.0 - /opt/homebrew/bin/code
    Cursor: 2.5.17 - /usr/local/bin/cursor
    Claude Code: 2.1.220 - /opt/homebrew/bin/claude
    Codex: 0.147.0 - /opt/homebrew/bin/codex
  Languages:
    Bash: 3.2.57 - /bin/bash
  npmPackages:
    playwright: 1.62.1 => 1.62.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions