pestphp / pestphp/pest

[pest-plugin-browser] Client::execute() loops forever once the Playwright websocket is closed (run hangs at 100% CPU or exhausts memory)

Open
#1,909 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
11.7k
Forks
538
Avg merge
4d 11h
Merged PRs (30d)
8

Description

This concerns pestphp/pest-plugin-browser (4.3.1; the same code is on its 5.x branch, 5.0.1). That repository has issues disabled, hence the report here.

What happens

When the browser or the playwright run-server process dies during a run, every later call into Playwright hangs the PHP process instead of failing.

Pest\Browser\Playwright\Client::execute() reads responses in a while (true) loop:

while (true) {
    $responseJson = $this->fetch($this->websocketConnection);   // (string) $client->receive()?->read()
    $response = json_decode($responseJson, true);
    ...
    yield $response;
    if ((isset($response['id']) && $response['id'] === $requestId) || ...) {
        break;
    }
}

Amp\Websocket\Client\WebsocketConnection::receive() returns null once the connection is closed, so fetch() returns '', json_decode('') returns null, nothing matches the request id, and the loop never ends:

  • consumers that iterate the generator with foreach (processResultResponse, querySelectorAll, locator counts, …) spin at 100% CPU forever — a pest --coverage run sat like that for 40 minutes with no output;
  • consumers that call iterator_to_array() (processVoidResponse: goto, close, …) accumulate null entries until memory_limit: Allowed memory size of 4294967296 bytes exhausted (tried to allocate 4294967304 bytes) at src/Playwright/Concerns/InteractsWithPlaywright.php:123.

The first symptom in our log was Amp\Websocket\WebsocketClosedException: Client unexpectedly closed; Code 1006 (ABNORMAL_CLOSE); Reason: "Writing to the client failed" thrown by sendText(); the next execute() call reached the read loop and never returned.

Reproduction

  1. Any browser test file with a handful of tests.
  2. While it runs, kill the Playwright server: pkill -9 -f "playwright run-server".
  3. The test running at that moment never finishes (CPU at 100%), or the process dies with the memory error above.

Seen with pestphp/pest-plugin-browser 4.3.1 (Pest 4.7.5, PHP 8.4, Chromium via playwright run-server --mode launchServer); the loop is unchanged on 5.x (5.0.1).

Suggested fix

Treat a closed connection as an error instead of an empty response, e.g. in execute():

$message = $this->websocketConnection->receive();

if ($message === null) {
    throw new BrowserAlreadyClosedException(); // or a dedicated exception naming the closed connection
}

$responseJson = $message->read();

With that, the test that lost the browser fails at once with a clear reason, and the following tests fail the same way instead of hanging the run.

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.

Research direction

Start in Pest\Browser\Playwright\Client::execute(), especially the websocket receive loop described in the issue, and reproduce the failure by killing the Playwright server during a browser test. Treat a closed receive as an error so the run fails promptly with a clear browser-closed reason instead of spinning or accumulating responses.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.