zenstruck/browser
A Laravel-friendly browser testing toolkit built on Symfony BrowserKit and Panther. Easily crawl pages, click links, submit forms, assert on HTML, and drive real headless browsers—great for end-to-end tests and fluent, expressive UI assertions.
Installation:
composer require zenstruck/browser --dev
Add the PHPUnit extension to phpunit.xml:
<extensions>
<extension class="Zenstruck\Browser\Test\BrowserExtension" />
</extensions>
Basic Test Class:
use PHPUnit\Framework\TestCase;
use Zenstruck\Browser\Test\HasBrowser;
class MyTest extends TestCase
{
use HasBrowser;
public function testBasicPageVisit()
{
$this->browser()->visit('/')->assertSee('Welcome');
}
}
Test a simple form submission:
public function testFormSubmission()
{
$this->browser()
->visit('/contact')
->fillField('Name', 'John Doe')
->fillField('Email', 'john@example.com')
->click('Submit')
->assertSee('Thank you, John Doe');
}
public function testApiEndpoint()
{
$this->browser()
->post('/api/users', HttpOptions::json(['name' => 'John']))
->assertJson()
->assertJsonMatches('id', 1)
->assertStatus(201);
}
public function testDynamicContent()
{
$this->pantherBrowser()
->visit('/dashboard')
->waitForElementVisible('#user-menu')
->click('#user-menu')
->assertSee('Logout');
}
public function testProtectedRoute()
{
$user = $this->createTestUser(); // Your user factory
$this->browser()
->actingAs($user)
->visit('/profile')
->assertSee('Welcome, ' . $user->getUsername());
}
public function testWithFoundry()
{
$post = PostFactory::new()->create(['title' => 'Test Post']);
$this->browser()
->visit("/posts/{$post->id}")
->assertSeeIn('h1', 'Test Post');
}
Environment Configuration:
Set BROWSER_SOURCE_DIR to customize where screenshots/sources are saved:
export BROWSER_SOURCE_DIR=./var/browser
Profiling:
Enable globally in phpunit.xml:
<php>
<server name="KERNEL_DEBUG" value="1"/>
</php>
Exception Handling:
Use throwExceptions() when testing error cases:
$this->browser()
->throwExceptions()
->visit('/invalid-route')
->expectException(NotFoundHttpException::class);
Authentication Quirks:
LogicException: Cannot create the remember-me cookie, call withProfiling() before the request or enable the profiler globally.PantherBrowser Slowness:
Redirect Handling:
interceptRedirects() to test redirect responses directly.JMESPath Dependencies:
mtdowling/jmespath.php for JSON assertions. Install it manually if missing:
composer require --dev mtdowling/jmespath.php
Save Sources:
Use saveSource('filename.html') to debug failed tests. Artifacts are saved to var/browser/source by default.
Dump Data:
$this->browser()->visit('/page')->dump('h1'); // Dumps the h1 element
$this->browser()->visit('/api')->dd('data.*.id'); // Dumps and dies on JSON data
Cookie Management: Access the cookie jar directly:
$this->browser()->use(function($cookieJar) {
$cookieJar->expire('MOCKSESSID');
});
Custom Assertions:
Extend the Browser class to add domain-specific assertions:
class CustomBrowser extends Browser
{
public function assertCustomCondition()
{
return $this->assertSee('Expected Text');
}
}
Override Defaults: Set environment variables to change defaults:
export BROWSER_CATCH_EXCEPTIONS=false # Disable exception catching
export BROWSER_FOLLOW_REDIRECTS=false # Disable redirect following
Custom Data Collectors:
Use the use() method to interact with Symfony's data collectors:
$this->browser()->use(function($collector) {
$queries = $collector->getQueries();
});
Test Data Setup:
Combine with zenstruck/foundry for seamless test data management:
$user = UserFactory::new()->create();
$this->browser()->actingAs($user)->visit('/dashboard');
API Testing:
Use HttpOptions for complex requests:
$this->browser()
->post('/api', HttpOptions::json(['data' => 'value'])
->withHeader('Authorization', 'Bearer token'));
Performance: Disable kernel reboot for faster tests (if stateful tests aren't needed):
$this->browser()->disableReboot();
How can I help you explore Laravel packages today?