[Bug]: Browser element assertion hangs forever instead of timing out when the element never appears
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
What Happened
An element assertion whose selector never matches does not fail at the configured timeout. The test process waits indefinitely. In CI this turned a missing frontend asset into a 20 minute job timeout with no failure message.
Expected
The assertion fails after about 30 seconds (the configured timeout) with an expectation message.
Actual
Nothing is printed and the test never finishes. The outer timeout kills it with exit code 124. In CI the same situation ran until the step's 20 minute bound.
While it hangs, the PHP process, the Playwright server and the Chromium renderer all sit at 0% CPU. PHP is blocked in poll with two sockets open: the Playwright websocket and the database.
Results
Tested on the same project and Playwright version in the range (npm install --save-dev playwright@<version>):
| Playwright | MissingElementTest |
|---|---|
| 1.59.1 | fails after 61s: "Timeout 30000ms exceeded." |
| 1.60.0 | fails after 63s: "Timeout 30000ms exceeded." |
| 1.61.1 | fails after 62s: "Timeout 30000ms exceeded." |
| 1.62.0 | hangs, killed after 180s (exit 124) |
| 1.62.1 | hangs, killed after 180s (exit 124) |
The behaviour changed in Playwright 1.62.0. With 1.61.1 and earlier, the getAttribute timeout reaches the plugin as an error. (The 61s is 30s of waitForExpectation() retries plus one final 30s attempt.)
Where it blocks
Logging in Pest\Browser\Playwright\Client::execute() shows the stuck request. Two temporary lines were added:
$this->websocketConnection->sendText($requestJson);
error_log('SEND '.$requestJson); // added
// inside the while loop:
$response = json_decode($responseJson, true);
error_log('RECV '.substr($responseJson, 0, 300)); // added
Output (trimmed):
SEND {"id":"6aa7bf5ef169a","guid":"frame@...","method":"goto","params":{"timeout":30000,"url":"http://127.0.0.1:36249/","waitUntil":"load"}}
RECV {"guid":"frame@...","method":"loadstate","params":{"add":"load"}}
SEND {"id":"6aa7bf604db76","guid":"frame@...","method":"getAttribute","params":{"timeout":1000,"selector":"ui-never-rendered > [contenteditable]","strict":true,"name":"contenteditable"}}
RECV {"id":"6aa7bf5ef169a","result":{"response":{"guid":"response@..."}}}
The last RECV is the late reply to goto, which execute() returned from early on the load event. After that, no message of any kind arrives, not even an error for getAttribute. We watched for 78 seconds in one run and 4 minutes in another.
The call path is assertAttribute() → Execution::waitForExpectation() → Playwright::usingTimeout(1_000, ...) → Locator::getAttribute() → Client::execute(). The request carries timeout: 1000, so Playwright should answer with a TimeoutError after a second and the retry loop would give up at 30 seconds. With no reply, execute() blocks in WebsocketConnection::receive(), which has no timeout of its own, so the loop's deadline check in waitForExpectation() never runs again.
How to Reproduce
-
In a Laravel app with Pest 4 and the browser plugin installed (
npm install playwright@1.62.1,npx playwright install chromium), set a browser timeout intests/Pest.php:pest()->browser()->timeout(30_000); -
Add a test that asserts an attribute on an element that is never rendered:
// tests/Browser/MissingElementTest.php it('fails instead of hanging', function () { visit('/')->assertAttribute('ui-never-rendered > [contenteditable]', 'contenteditable', 'true'); }); -
Run it with an outer bound so it cannot run forever:
timeout 4m php artisan test tests/Browser/MissingElementTest.php; echo "exit $?"
Sample Repository
No response
Pest Version
Pest 4.7.5, pest-plugin-browser 4.3.1, pest-plugin-laravel 4.1.0
PHP Version
PHP 8.4.12
Operation System
Linux
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
Reproduce the issue with tests/Browser/MissingElementTest.php and the provided Playwright 1.62.1 command. Trace assertAttribute() through Execution::waitForExpectation(), Locator::getAttribute(), Client::execute(), and WebsocketConnection::receive(), focusing on the missing response timeout. Done means the missing-element assertion terminates near the configured 30-second timeout with an expectation message.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, playwright
- Domain
- testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100