[browser plugin] Client::execute() waitUntil early-break strands responses and desynchronizes subsequent commands
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
Filed here because pestphp/pest-plugin-browser has issues disabled.
Description
Client::execute() can leave a command's own response stranded in the websocket buffer, after which every later command on the connection consumes shifted responses, producing confusing downstream failures far from the cause.
Version: pest-plugin-browser v4.3.1 (pest v4, PHP 8.5)
Root cause
src/Playwright/Client.php, execute() — the response loop breaks on either the command's own response id or a matching waitUntil lifecycle event:
if (
(isset($response['id']) && $response['id'] === $requestId)
|| (isset($params['waitUntil']) && isset($response['params']['add']) && $params['waitUntil'] === $response['params']['add'])
) {
break;
}
Page::goto() always sends waitUntil: 'load'. When the load lifecycle event arrives before the goto command's own response (ordering depends on server/browser timing), the loop breaks early and goto's {id: ...} response is never consumed.
The next command then reads it. processResultResponse() accepts the first message carrying result.value without correlating the response id:
foreach ($response as $message) {
if (isset($message['result']['value'])) {
return JavaScriptSerializer::parseValue($message['result']['value']);
}
}
From that point the stream is desynchronized: each command may receive the previous command's result.
Observed symptoms
Page::consoleLogs(): Return value must be of type array, true returned— the evaluate consumed a stale boolean from a previousassertScript.click()returns success but the page never receives the click (failure screenshots show the page fully rendered with pre-click state).- Text assertions fail on pages that never received the preceding action.
In our suite (~220 browser tests) this reproduced deterministically on GitHub Actions (sequential run, sqlite, both with --only-shell and full chromium) while passing locally — the event ordering is environment-timing dependent, so victims appear/disappear with machine speed.
Suggested fix
Drop responses whose id belongs to a previous command before processing:
// in execute(), after json_decode:
if (isset($response['id']) && $response['id'] !== $requestId) {
continue;
}
With this guard our previously-failing suite is green; the stranded response from a waitUntil early-break is simply discarded by the next command instead of being misinterpreted as its result. (Correlating ids in processResultResponse() would work as well.)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/Playwright/Client.php at execute() and processResultResponse(), then trace the response handling from Page::goto(), which sends waitUntil: 'load'. Reproduce the ordering where the lifecycle event precedes the command response and verify that later commands cannot consume a stale result; the reported browser suite should remain green without shifted 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
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100