Skip to content

Browser mode tester can hang forever after reconnect if waitForConnection() is awaiting the first socket attempt #10166

Description

@jkieboom

Describe the bug

In browser mode, the tester can hang forever if the first tester websocket stays in CONNECTING long enough for the client to start awaiting waitForConnection(), then drops before open, and Vitest reconnects. This problem was triggered when running vitest through browserstack. I'm not sure what exactly triggers it, but something around the way local tunnel networking is setup causes it to consistently end up in a wrong state.

What I expect is for waitForConnection() to follow the current websocket attempt across reconnect, or at least not leave existing waiters stuck on the first failed attempt.

What actually happens is that the tester iframe loads, the browser reconnects, but the original waiter can stay attached to the first socket's unresolved openPromise, so the tester never processes prepare or execute and the run stalls indefinitely.

From inspecting Vitest 4.1.4, this appears to come from @vitest/browser storing a per-attempt openPromise inside registerWS(). When reconnect replaces client.ws, it also replaces openPromise, but callers that were already awaiting the old promise are still stuck on the first socket attempt. In the built tester bundle, message handling starts by awaiting client.waitForConnection(), which is why the iframe can reconnect and still never start running tests.

Reproduction

Reproduction repository: https://github.com/jkieboom/vitest-waitforconnection-repro

The reproduction is a minimal standalone Vitest browser-mode project. It uses a local reverse proxy in front of the Vitest server. In the broken case, that proxy forwards everything normally except the first tester __vitest_browser_api__ websocket upgrade: it holds that socket in CONNECTING for 1500 ms and then drops it before the handshake completes. The browser then reconnects naturally, but the original waitForConnection() waiter remains stuck.

Steps:

npm install
npx playwright install chromium
node ./run-repro.mjs

Expected output:

  • the control case exits successfully
  • the broken case should also eventually proceed after reconnect

Actual output:

  • the control case exits successfully
  • the broken case logs that the first tester websocket was dropped and then hangs until the harness timeout kills it

I reproduced this consistently across repeated local runs.

System Info

System:
  OS: macOS 26.4.1
  CPU: (12) arm64 Apple M2 Pro
  Memory: 130.44 MB / 32.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 24.12.0 - /Users/jess7953/.nvm/versions/node/v24.12.0/bin/node
  npm: 11.6.2 - /Users/jess7953/.nvm/versions/node/v24.12.0/bin/npm
  pnpm: 10.33.0 - /Users/jess7953/.nvm/versions/node/v24.12.0/bin/pnpm
Browsers:
  Chrome: 147.0.7727.56
  Chrome Canary: 149.0.7793.0
  Edge: 147.0.3912.60
  Firefox: 149.0.2
  Safari: 26.4
  Safari Technology Preview: 26.0
npmPackages:
  @vitest/browser: 4.1.4 => 4.1.4
  @vitest/browser-playwright: 4.1.4 => 4.1.4
  vitest: 4.1.4 => 4.1.4

Used Package Manager

npm

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions