Repository navigation
fix: keep the original failure as the previous exception - #268
Merged
MrPunyapal merged 2 commits intoSep 24, 2026
Merged
Conversation
`BrowserExpectationFailedException::from()` builds a richer message and returns a new `ExpectationFailedException`, passing `$e->getPrevious()` as its third argument rather than `$e`. The original exception is the root of its own chain, so its previous is null: the error that came out of Playwright is dropped, along with its file, line and trace. Protocol errors surface as `ExpectationFailedException` too, so a closed-target error or a timeout reaches the test with its origin stripped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a browser assertion fails, the exception the test receives has no
previous. The error that actually came out of Playwright — and the stack that goes with it — is gone by the time anything can report it.Measured on
5.xwith a probe that catches the failure and prints its internals:Root cause
BrowserExpectationFailedException::from()builds a richer message (screenshot path, console logs, JavaScript errors, an exception thrown by the HTTP server) and returns a newExpectationFailedException:The third argument is
$e->getPrevious()— the previous of the original — rather than$eitself. Since the original is typically the root of its own chain, itsgetPrevious()isnull, so the chain ends there and$eis discarded along with its file, line and trace.The reported location is fine: the new exception's trace still contains the test frame, and PHPUnit filters the plugin's frames out, so the line shown is the call that threw. What is lost is the error underneath it.
This matters most when the failure did not originate in an assertion at all. Protocol errors surface as
ExpectationFailedExceptiontoo (Client::execute()turns any error frame into one), so aTarget page, context or browser has been closedor a Playwright timeout reaches the test with its origin stripped — which is part of why transport-level bugs such as #247 and #266 read as application flakiness.The fix
Pass the original exception:
After the change the same probe reports:
The
@phpstan-ignore-lineis no longer needed and is removed —$eis aThrowable, which the constructor accepts directly, whereas the previous expression needed the suppression.The visible message is unchanged:
from()already copies the original text into$message, so nothing new is printed unless a tool walks the chain.Tests
tests/Browser/Visit/FailureOriginTest.phpfails a real browser assertion and asserts that the exception carries the original one as itsprevious, with the original message intact. It fails on5.xas it stands (null is not an instance of ExpectationFailedException) and passes with this change.composer test:lint,composer test:typesandcomposer test:type-coverage(100%) pass on this branch (PHP 8.4.24, Playwright 1.62.1).The full suite leaves three failures here — the two
IframeTestcases andSingleUrl > it may visit external URLs— all of themTimeout 2000ms exceededonexample.com. They are an artefact of my environment: outbound traffic goes through an authenticated proxy that Chromium does not inherit from the environment. I verified they are unrelated by running those two files with and without this change: 3 failed / 4 passed either way, identical.