zenstruck/browser
zenstruck/browser is an expressive, fluent wrapper around Symfony's functional testing features, letting you visit pages, assert content, fill fields, and click buttons in chainable code. Pair it with zenstruck/foundry for succinct, readable tests.
Architecture fit — low for a Laravel estate, high for Symfony services. zenstruck/browser is explicitly a Symfony package: its KernelBrowser drives the Symfony Kernel through symfony/browser-kit, and its PantherBrowser wraps symfony/panther. It is not a Laravel package and carries no Laravel service provider, facade, or testing trait for Illuminate\Foundation\Testing\TestCase. Laravel's own HTTP test layer (MakesHttpRequests + TestResponse) already occupies this niche, and Laravel teams that need real browsers use Dusk or Pest 3's Playwright-based browser testing. Where the package genuinely fits is a Symfony application's WebTestCase/KernelTestCase suites, replacing verbose $client->request() + $crawler->filter() chains with a fluent, auto-completable DSL. v1.13.0 does not change this positioning.
Integration feasibility — near-trivial on Symfony, not attempted on Laravel. Adoption is a dev-only Composer require, an optional PHPUnit extension registration, and a use HasBrowser trait on test classes. No production code, config, or container changes. v1.13.0 adds one small ergonomic capability — the HTTP session can now be injected into use() — which simplifies reusing an authenticated/pre-seeded session across a fluent chain instead of rebuilding it per test; useful, not a reason to adopt on its own. The realistic "Laravel" value remains conceptual: its API is a good benchmark for internal test-style guidelines (chained assertions, artifact capture on failure), not a dependency.
Technical risk — contained, and modestly reduced by this release, but with one behavioral change to check.
BROWSER_CATCH_EXCEPTIONS, auto-followed redirects, and kernel reboot between requests are unchanged and still require knowing the defaults.Key questions for the adopting team.
<extension class=...>) vs 10+ (<bootstrap class=...>) changes the install snippet?symfony/panther implies Chrome + a reachable web server in CI, a materially larger commitment — and note the release pins Playwright 1.62.1 in the package's own CI, which is upstream's harness, not ours.Stack fit. Adopt only where the stack is Symfony 5/6/7 + PHPUnit. Treat KernelBrowser as the default (fast, in-process, no browser) and PantherBrowser as an opt-in for JS-critical flows. Pull in zenstruck/foundry only if factories are already the team's fixture pattern; the two compose well but are independent decisions. For Laravel, record the outcome as "not applicable — use Laravel's HTTP test assertions / Dusk / Pest browser plugin," and mine the README for conventions rather than code.
Migration path.
composer require zenstruck/browser --dev, committed to the lockfile with a caret constraint — target ^1.13 for greenfield adoption, since it carries the exception-after-action guard and the HTTP-option mutation fix.BrowserExtension in phpunit.xml for failure-artifact capture (source, screenshots, JS console) — highest value per line of config, because it shortens debugging of intermittent CI failures.WebTestCase assertions rather than rewriting whole suites.Compatibility and sequencing.
mtdowling/jmespath.php out of initial scope; add it only if API/JSON assertions are a stated need. Sequence Panther last — it changes CI infrastructure, not just test code, and should be justified by a concrete flow (payment widget, multi-step JS form) that KernelBrowser cannot verify.Maintenance. Low absolute effort, but it is a small-team OSS dependency: pin to a minor line, review changelogs on bump, and expect to absorb occasional breakage from Symfony's BrowserKit/DomCrawler deprecations. v1.13.0 is a low-risk upgrade for anyone starting fresh and a review-then-upgrade for anyone already on the 1.x line, because of the exception-after-action behavior change. Budget roughly a person-day per Symfony major upgrade plus an hour for this bump's triage, and near-zero in between. The dev-only scope means no security patching obligation for production images.
Support. Upstream is issue-driven and responsive for a project of this size, and this release reflects two outside contributors landing merged fixes — better than the bus-factor profile suggests. There is still no vendor SLA. Mitigate with an internal champion, a two-paragraph "how we test" doc, and a deliberate decision that reverting to native assertions is always an acceptable fallback. Ramp-up is genuinely low: the API is autocompletable in any IDE, so a competent Symfony developer is productive in an afternoon and fluent within a sprint.
Scaling. KernelBrowser suites scale like any PHPUnit suite — parallelize with ParaTest, no new infrastructure, and as of v1.13.0 the artifact writer is race-safe under parallel execution, which removes the main friction that previously argued for keeping browser tests serialized. PantherBrowser still scales poorly by comparison (seconds per test, Chrome processes, memory), so cap it to a small tagged subset and run it in a separate, non-blocking job initially. The residual artifact concern is volume rather than correctness: failed-test sources and screenshots land in var/browser (or BROWSER_SOURCE_DIR), which on a noisy suite can accumulate into CI disk pressure. Set an explicit artifact directory, retention window, and upload-as-CI-artifact step before enabling the extension broadly — unique filenames make retention policy more important, not less, since nothing overwrites.
Failure modes to monitor.
BROWSER_CATCH_EXCEPTIONS can still let assertions pass against an error page; enforce ->throwExceptions() and ->interceptRedirects() where behavior matters.disableReboot() used casually still produces order-dependent tests; treat it as an exception requiring a comment.assertSeeIn('h1', ...) and CSS-path assertions break on markup refactors; prefer stable IDs/data attributes.LogicException with assertAuthenticated() will surface as unexplained CI failures unless withProfiling() or framework.profiler.collect: true is configured up front.Net recommendation. Worth a scoped pilot on Symfony services where functional tests are already verbose and maintained — cheap to adopt, cheap to abandon, with measurable gains in test readability and failure debuggability. v1.13.0 strengthens the case modestly: it closes a real false-green hole, removes a flakiness source, and makes parallel execution with artifacts safe. It does not move the Laravel verdict — for Laravel, capture the DSL conventions as internal guidance and move on.
How can I help you explore Laravel packages today?