Zenstruck Browser
repository·1.x·Indexed 42 days ago
https://github.com/zenstruck/browserA fluent and expressive wrapper for Symfony's functional testing features. It provides a chainable API to reduce verbosity and improve developer experience through auto-completion. It offers two implementations: KernelBrowser for fast requests via the Symfony Kernel, and PlaywrightBrowser for testing JavaScript execution using playwright-php/playwright-symfony.
What's inside Zenstruck Browser
- zenstruck/browser is an expressive, auto-completable, and fluent wrapper around Symfony's native functional testing features. It is designed to make functional testing in Symfony less verbose and more readable by providing a chainable API for common browser actions and assertions.
How the two browser types work
1.xThe library provides two distinct browser implementations for testing:
- KernelBrowser: Makes requests directly using your Symfony Kernel. It is very fast but cannot execute JavaScript.
- PlaywrightBrowser: Drives a real browser using
playwright-php/playwright-symfony. It intercepts requests and passes them to your Symfony Kernel, meaning no webserver is required. This allows you to test JavaScript while still using your application's booted kernel (mocked services, in-memory databases, etc.). It is slower than the KernelBrowser.
You can switch between them or use both in the same test to simulate real-time interactions like WebSockets.
// Using KernelBrowser (fast) $this->browser()->visit('/page'); // Using PlaywrightBrowser (real browser/JS support) $this->playwrightBrowser()->visit('/page');Use Components to wrap common tasks
1.xComponents are objects that extend
Zenstruck\\Browser\\Componentand wrap common UI tasks, assertions, or actions. They are injected into a browser instance using the->use()method.When a component is loaded via
->use():- The
preActions()method is called (useful for navigation). - The
preAssertions()method is called (useful for verifying page state). - The callback function is executed, providing the component instance.
Multiple components can be injected into a single
->use()call./** @var \Zenstruck\Browser $browser **/ $browser ->use(function(MyComponent $component) { $component->method(); }) ; // Injecting multiple components $browser->use(function(Component1 $component1, Component2 $component2) { $component1->doSomething(); $component2->doSomethingElse(); });- The
Configure the BrowserExtension for PHPUnit
1.xTo enable automatic error interception (saving browser source, screenshots, and JS console logs on failure) and a summary of artifacts after the test suite finishes, register the
Zenstruck\Browser\Test\BrowserExtensionin yourphpunit.xmlfile.For PHPUnit 8 or 9: Use the
<extensions>block with theclassattribute.For PHPUnit 10+: Use the
<extensions>block with thebootstrapattribute.<!-- PHPUnit 8 or 9 --> <extensions> <extension class="Zenstruck\Browser\Test\BrowserExtension" /> </extensions> <!-- PHPUnit 10+ --> <phpunit> ... <extensions> <bootstrap class="Zenstruck\Browser\Test\BrowserExtension" /> </extensions> </phpunit>Create a Custom Browser
1.xYou can extend
KernelBrowserorPlaywrightBrowserto add your own domain-specific actions and assertions.To use your custom browser class, set the corresponding environment variable:
- For
KernelBrowserextensions:KERNEL_BROWSER_CLASS - For
PlaywrightBrowserextensions:PLAYWRIGHT_BROWSER_CLASS
Tip: Use the
@methodannotation in a base test case to enable IDE autocompletion for your custom browser methods.namespace App\\Tests; use Zenstruck\\Browser\\KernelBrowser; class AppBrowser extends KernelBrowser { public function assertHasToolbar(): self { return $this->assertSeeElement('#toolbar'); } } // In your environment config: // KERNEL_BROWSER_CLASS=App\\Tests\\AppBrowser /** * @method AppBrowser browser() */ abstract class MyTest extends WebTestCase { use HasBrowser; }- For
Set up the Browser in your tests
1.xTo use the library in your PHPUnit tests, include the
Zenstruck\Browser\Test\HasBrowsertrait in your test class.Note: For
PlaywrightBrowser, your test class must extendSymfony\Bundle\FrameworkBundle\Test\KernelTestCase(orWebTestCase).namespace App\Tests; use PHPUnit\Framework\TestCase; use Zenstruck\Browser\Test\HasBrowser; class MyTest extends TestCase { use HasBrowser; public function test_example(): void { $this->browser()->visit('/my/page'); } }Configure custom HttpOptions
1.xTo avoid duplicating HTTP request configuration (like headers), you can use
HttpOptionsin several ways:- Per-browser defaults: Use
->setDefaultHttpOptions(array $options)on the browser instance. Per-request options will be merged with these defaults. - In Test Case configuration: Set defaults in the
browser()method of your test class. - Custom HttpOptions objects: Extend
Zenstruck\\Browser\\HttpOptionsto create reusable, semantic request configurations (e.g., anapi()method that automatically adds anX-Tokenheader).
// 1. Per-browser defaults $browser ->setDefaultHttpOptions(['headers' => ['X-Token' => 'my-token']]) ->get('/endpoint') ->get('/endpoint', ['headers' => ['Another' => 'Header']]); // Merged // 3. Custom HttpOptions object class AppHttpOptions extends HttpOptions { public static function api(string $token, $json = null): self { return self::json($json) ->withHeader('X-Token', $token); } } $browser->post('/api/endpoint', AppHttpOptions::api('my-token'));- Per-browser defaults: Use
Configure default browser state in tests
1.xTo set a consistent starting state for your browser in every test, override the
browser()method in your test class using theHasBrowsertrait. This allows you to define default behaviors like intercepting redirects, throwing exceptions, or visiting a specific starting URL.Note that when using the
HasBrowsertrait, you should alias the trait's method (e.g.,browser as baseKernelBrowser) to avoid naming collisions with your ownbrowser()method.namespace App\\Tests; use Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase; use Zenstruck\\Browser\\KernelBrowser; use Zenstruck\\Browser\\Test\\HasBrowser; class MyTest extends KernelTestCase { use HasBrowser { browser as baseKernelBrowser; } public function testDemo(): void { $this->browser() ->assertOn('/') // browser always starts on the homepage (as defined below) ; } protected function browser(): KernelBrowser { return $this->baseKernelBrowser() ->interceptRedirects() // always intercept redirects ->throwExceptions() // always throw exceptions ->visit('/') // always start on the homepage ; } }Extend a Browser with Traits
1.xYou can create reusable sets of actions and assertions by defining them in a PHP Trait and using that trait within a Custom Browser class. This is useful for repetitive tasks like authentication flows.
Example of an
AuthenticationExtensiontrait:trait AuthenticationExtension { public function loginAs(string $username, string $password): self { return $this ->visit('/login') ->fillField('email', $username) ->fillField('password', $password) ->click('Login') ; } public function logout(): self { return $this->visit('/logout'); } public function assertLoggedIn(): self { return $this->assertSee('Logout'); } }Then, apply it to your custom browser:
class AppBrowser extends KernelBrowser { use AuthenticationExtension; }namespace App\\Tests\\Browser; trait AuthenticationExtension { public function loginAs(string $username, string $password): self { return $this ->visit('/login') ->fillField('email', $username) ->fillField('password', $password) ->click('Login') ; } public function logout(): self { return $this->visit('/logout'); } public function assertLoggedIn(): self { return $this->assertSee('Logout'); } public function assertLoggedInAs(string $user): self { return $this->assertSee($user); } public function assertNotLoggedIn(): self { return $this->assertSee('Login'); } } namespace App\\Tests; use App\\Tests\\Browser\\AuthenticationExtension; use Zenstruck\\Browser\\KernelBrowser; class AppBrowser extends KernelBrowser { use AuthenticationExtension; }Install zenstruck/browser
1.xInstall the library as a development dependency using Composer:
composer require zenstruck/browser --devUse PantherBrowser for real browser testing
1.xThe
PantherBrowserclass provides a browser implementation using Symfony Panther for testing applications in a real browser environment.Note: This class is deprecated since version 1.11. Use
PlaywrightBrowserinstead.Because
PantherBrowserrelies on the WebDriver protocol, certain standard browser methods likeassertStatus(),assertSuccessful(), and header assertions are not supported and will throw aBadMethodCallException.Use PlaywrightBrowser for real browser testing
1.xThe
PlaywrightBrowserclass provides a high-level API for interacting with a real browser via Playwright. It is designed for end-to-end testing where JavaScript execution and real browser rendering are required.Note: This class is marked as
@experimental.// Note: Constructor is @internal, typically instantiated via a factory or container // using a PlaywrightKernelClient. $browser = new PlaywrightBrowser($playwrightClient, [ 'screenshot_dir' => '/path/to/screenshots', 'console_log_dir' => '/path/to/logs', 'follow_redirects' => true, 'catch_exceptions' => true, ]);