feat: Add PlaywrightBrowser - #195
Draft
kbond wants to merge 1 commit into
Draft
Conversation
kbond
force-pushed
the
playwright
branch
11 times, most recently
from
August 13, 2026 00:43
86ea2f9 to
d6e1bed
Compare
Member
Author
|
Now that Measured with 100 authenticated tests of each style in a single process, three runs:
~71% less time, ~12.6s saved per 100 authenticated tests (~63s per 500). For contrast, the same comparison with the |
kbond
force-pushed
the
playwright
branch
3 times, most recently
from
August 13, 2026 16:05
3c7831f to
c1a24b5
Compare
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.
Adds a
PlaywrightBrowser: it drives a real browser (Chromium, Firefox or WebKit) but intercepts its requests and passes them to your booted kernel, so no webserver is required. The browser talks to the same application instance and container as the test, so mocked services and an in-memory database behave as they do with theKernelBrowser.Also deprecates
PantherBrowserin favour of it (@deprecated since 1.11), and moves the response status/header assertions, container access and the security helpers up toZenstruck\Browserso both browsers share them.Out of scope by design: JSON/XML assertions, HTTP-method actions and
HttpOptions. Playwright is for browser testing, not API testing.Required
redirects_are_followed_by_defaultandcan_use_current_browserwere erroring; the whole suite is green againstmainnow.enableProfiler()work for every request playwright-php/playwright-symfony#53 -enableProfiler()only takes effect for the first intercepted request of a test, because booting an already booted kernel resets the profiler.can_profile_multiple_requestsis skipped for thePlaywrightBrowseruntil this lands.playwright-symfonyrelease, then bump it off^0.9here.Nice to have
withProfiling()/profile()), with the same one-shot semantics as theKernelBrowser.$kernel->terminate()after handling intercepted requests playwright-php/playwright-symfony#37 - without terminating the kernel the profile is never written to storage, soprofile()always fails.enableProfiler()to the kernel client playwright-php/playwright-symfony#38 (enableProfiler()) - makeswithProfiling()apply to a single request, matchingKernelBrowser.getContainer()to the kernel client playwright-php/playwright-symfony#36 (getContainer()) - drops the test-container lookup currently duplicated inHasBrowser.PlaywrightBrowserTestfrom 30s to 12s with concurrent browsers capped at 2.>=8.3and Symfony^7.0, which would have limited the Playwright tests to a fraction of the CI matrix; they now run in every job.actingAs(),assertAuthenticated()andassertNotAuthenticated()with theKernelBrowser- unblocked by Add loginUser and extend WebTestCase playwright-php/playwright-symfony#28, which addsloginUser()to the client with a matching signature. They moved toZenstruck\Browserrather than being duplicated, andKernelBrowserAuthenticationTestis gone: its coverage is now shared, including a real form login that survives losing the session cookie via remember-me (previouslymarkTestIncompletesince 2022).throwExceptions(),catchExceptions()andexpectException()with theKernelBrowser. They moved toZenstruck\Browser, the expectation machinery moved to the sharedSession\Driverbase, and they are wired to thecatch_exceptionsoption andBROWSER_CATCH_EXCEPTIONS. Five tests moved intoBrowserTestsso both browsers run them. Two things a real browser does differently: a request it has not finished is handled after the current call returns, sowrapRequest()restores the previous catching setting rather than leaving it off (which also settles atodotheKernelBrowsercarried), and a link click that throws leaves the browser where it was, since no response reaches it, so the shared test does not assert the url afterwards.fails_if_trying_to_manipulate_exception_pagestill sits inKernelBrowserTests:Session::ensureNoException()reads the rendered page rather than the raw response, so moving it needs a driver-level raw-content accessor.$kernel->handle()with$catch = falsehardcoded, so an app exception always propagated. It now catches by default and exposescatchExceptions(), spelled asHttpKernelBrowserdoes.->use(CookieJar $jar)now handsPlaywrightBrowsera jar that proxies to the browser, in the same spirit asSymfony\Component\Panther\Cookie\CookieJar.Future scope
Redirect interception
interceptRedirects(),followRedirect(),assertRedirected(),assertRedirectedTo()andclickAndIntercept()look achievable.PlaywrightKernelClienttakes a$hookReceiverwhoseafterResponse()is handed the response object that is later fulfilled to the browser, so rewriting a 3xx into a 200 parks the browser on the redirect URL with the status andLocationrecorded. The body has to be neutralized too:RedirectResponseembeds a<meta http-equiv="refresh">that navigates regardless of the status. Stepping/redirect1→/redirect2→/redirect3→/page1one hop at a time works with that in place.It belongs upstream rather than here though: a
followRedirects(bool)on the client (theAbstractBrowserspelling) can fulfill a mutated clone and leavegetLastSymfonyResponse()truthful, which a hook-only version downstream cannot - it mutates the same objectgetStatusCode()reads. Telling document navigations from subresources wants playwright-php/playwright#124; theAcceptheader discriminates well enough in the meantime.Auto-wait behaviour
Playwright auto-waits on actions but not on queries:
click()retries until the element is actionable, whileassertVisible()is a one-shot check. That is why thewaitUntil*()methods exist alongside the assertions - making assertions retry would remove the split. The default timeout is also 30s, which feels long for an assertion and should be configurable. playwright-php/playwright-symfony#35 does the same thing forPlaywrightTestCase's own assertion trait, which we don't use, but it is the same idea.Uncaught javascript errors
Playwright reports uncaught javascript errors as a
pageerrorevent, separate from console messages, andplaywright-phpdoes not expose it yet. They are therefore missing fromsaveConsoleLog(),dumpConsoleLog()andsaveCurrentState()- precisely the situations where you'd want them.can_dump_console_log_with_throw_erroris skipped until the event is surfaced upstream.