pbmedia/laravel-browser-kit-macro
pbmedia/laravel-browser-kit-macro adds useful macros to Laravel's Browser Kit, extending its fluent HTTP testing API with convenient helpers and assertions so you can write cleaner, more expressive feature tests for Laravel applications.
This package is a thin compatibility shim: it restores the Laravel 5.3 "BrowserKit" testing style (visit, click, type, see, press, seePageIs, …) inside Laravel 5.4+ test suites, where the framework replaced that API with the current get() / assertSee() / assertStatus() style.
Install
composer require pbmedia/laravel-browser-kit-macro --dev
Requires a PHP version supported by the release line you pin — as of 5.9.0 the package explicitly supports PHP 8.5, so it is safe to run on the newest PHP minor alongside current Laravel.
Register the provider (only needed on Laravel < 5.5; 5.5+ uses package auto-discovery):
// config/app.php
'providers' => [
// ...
Pbmedia\BrowserKitMacro\BrowserKitMacroServiceProvider::class,
],
Where to look first
src/BrowserKitMacroServiceProvider.php — this is where the macro(s) are registered on Illuminate\Foundation\Testing\TestCase (Laravel's base test case is Macroable, which is what makes the whole approach possible). Read the closure to confirm the exact macro name and signature for the release you installed.src/ root — the fluent helper class that actually holds the old visit/see/click methods.tests/ in the package itself — the fastest way to see a real usage example.First use case — a "smoke test" that walks a page like a user instead of asserting against a raw HTTP response:
public function test_homepage_and_about_page()
{
$this->browserKit()
->visit('/')
->see('Welcome')
->seeLink('About')
->click('About')
->seePageIs('/about')
->seeStatusCode(200);
}
⚠️ The macro entry point name (
browserKit(),visit(), etc.) has changed across releases of this package — always confirm against the service provider in your installed version before writing a suite around it.
Because the macro hangs off Illuminate\Foundation\Testing\TestCase, you can centralise setup (auth, seed data, withoutMiddleware) in your project's tests/TestCase.php so day-to-day tests stay one-liners:
abstract class BrowserKitTest extends TestCase
{
use RefreshDatabase;
protected function asUser(User $user)
{
return $this->actingAs($user)->browserKit();
}
}
This is where the 5.3 API genuinely beats the modern one for readability. Anything with a form flow collapses into a chain:
$this->browserKit()
->visit('/register')
->type('Pascal', 'name')
->type('[email protected]', 'email')
->type('secret', 'password')
->type('secret', 'password_confirmation')
->press('Register')
->seePageIs('/home');
The macro is non-destructive — it does not replace $this->get(). A pragmatic split:
| Test type | Layer to use |
|---|---|
| JSON / API contract | $this->getJson() + assertJson* |
| DOM/user journey | browserKit() chain |
| Unit | plain PHPUnit |
You can even use both in one test if you need a page scrape and a status assertion:
$response = $this->get('/');
$response->assertOk();
// … then poke at the rendered HTML with the legacy API
$this->browserKit()->visit('/')->see('Welcome');
The old layer supports followRedirects() — use it on multi-step auth flows (e.g. login → intended → dashboard) instead of chasing assertRedirect() calls.
Prefer $this->actingAs($user) before entering the chain. The old BrowserKit session handling is not the same as the modern MakesHttpRequests session handling, so don't assume $this->withSession([...]) is honoured mid-chain — set session state up front.
It's not a browser. BrowserKit is purely in-process HTTP calls; no JavaScript, no CSS, no window. Any assertSee/see is a substring check against the response HTML. Alpine/React/Vue-rendered content will not appear.
Low-maintenance, not dead. The package has few stars and no dependents, but 5.9.0 (PHP 8.5 support) is a real, fresh release with an outside contributor — so it is maintained, just slowly. Keep it as a dev dependency rather than copying it, but pin a version and watch releases: the maintenance cadence is a release-per-new-PHP, not per bug report. If you cannot tolerate that, vendoring the ~few-hundred-line macro into tests/Support/ remains a valid escape hatch.
PHP/platform upgrades. 5.9.0 is a compatibility-only release — no API changes, no deprecations, nothing to migrate in your tests. The practical takeaway: after bumping PHP (e.g. to 8.5) or Laravel, bump this package at the same time and re-run your browser-kit suites; a stale pin is the most likely cause of a sudden BadMethodCallException or a syntax error in vendor/.
Macro name drift. As noted, verify src/BrowserKitMacroServiceProvider.php. If a method you remember from 5.3 (attach, select, dontSeeLink, seeRouteIs) is missing, it's usually missing from this wrapper too, not broken in your app. Release notes for compatibility bumps like 5.9.0 tell you nothing about this — always read the source, not the changelog.
Macroable dependency. The whole mechanism relies on the framework's TestCase using Macroable. If a future Laravel version drops that trait, the macro silently registers on nothing and you get a BadMethodCallException. Pin Laravel's major version in composer.json if this matters.
Debugging failures: when see('…') fails, the exception message is often unhelpful. Dump the body:
try { $this->browserKit()->visit('/')->see('x'); }
catch (\Throwable $e) { dump($this->browserKit()->getResponse()->getContent()); throw $e; }
Or write the response to storage/framework/testing/ for HTML diffing.
Exceptions are caught by default. The old layer swallowed exceptions and turned them into 500s. Use $this->withoutExceptionHandling() before entering the chain if you want the real stack trace instead of a mysterious seeStatusCode(500) failure.
Middleware. $this->withoutMiddleware() must also be set before the chain, not inside it.
Extension points. If the returned helper object is Macroable (check src/), register your own domain-specific shortcuts in the provider's boot():
BrowserKit::macro('loginAs', function (User $user) {
return $this->type($user->email, 'email')
->type('secret', 'password')
->press('Login');
});
Even if it isn't macro-able, subclassing the helper or wrapping it in your own trait gives you the same effect.
Config quirks. On Laravel 5.5+ auto-discovery, the provider must be listed in composer.json's extra.laravel.providers; older installs that copied the provider into config/app.php will break if that file is regenerated by a package upgrade. If browserKit() is undefined, this is the first place to look.
RefreshDatabase vs DatabaseTransactions: the legacy layer may touch more rows per test than an API-style test (form submissions create/update/delete), so prefer RefreshDatabase in the file to keep suites reproducible.
Don't mix followRedirects() with $this->get() in the same test — the two layers maintain separate response state and you'll confuse yourself. Pick one per test.
How can I help you explore Laravel packages today?