Skip to content

fix: fail a request the Playwright server never answers, instead of waiting forever - #301

Open
3darkman wants to merge 1 commit into
pestphp:5.xfrom
3darkman:fix/bounded-reply-wait
Open

3darkman wants to merge 1 commit into
pestphp:5.xfrom
3darkman:fix/bounded-reply-wait

Conversation

@3darkman

@3darkman 3darkman commented Nov 3, 2026

Copy link
Copy Markdown

Problem

Client::fetch() waits for the next websocket message with receive() and no cancellation. When the Playwright server accepts a request and never answers it, the test run hangs forever, with no output and no timeout: the only bound in the stack is the action timeout the server itself enforces, and a server that does not answer does not enforce it either.

That is the alive-socket half of pestphp/pest#1855. #277 covered the other half (a closed socket). We hit the alive case on Windows 11 with 5.0.1, twice over nine days, on full suite runs: the websocket to playwright run-server stayed ESTABLISHED, the server process had no browser child left, the PHP thread sat in a synchronous wait with its CPU time frozen, and the run had to be killed by hand. One of the two started during a memory-exhaustion warning of Windows against the node.exe of the server (RADAR_PRE_LEAK_64), so the browser launch likely died under pressure while the socket stayed open.

Fix

fetch() now receives with a TimeoutCancellation of the action timeout plus a grace (60 s by default, setReplyGrace() to change it), and turns the cancellation into a RuntimeException that names the deadline. Playwright answers every request within its action timeout, with an error if need be, so a reply that has not come by then plus the grace is not coming. The grace covers the requests the action timeout does not bound, such as a browser launch on a loaded machine.

Events keep flowing as before: each message gets its own deadline, so a navigation that waits on its load event is bounded per message, not as a whole.

Tests

tests/Unit/Playwright/ClientTest.php: a stubbed connection whose receive() honours the cancellation and never produces a message. With a 100 ms action timeout and no grace, execute() throws the new exception within the deadline instead of blocking the test.

vendor/bin/pint --test, vendor/bin/phpstan analyse and the unit suite pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant