Zenstruck Browser

repository·1.x·Indexed 42 days ago

https://github.com/zenstruck/browser

A 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.

Tokens
11.6K
Snippets
32
Records
40
Agent score
52%

What's inside Zenstruck Browser

  1. Overview of zenstruck/browser

    1.x
    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.
  2. How the two browser types work

    1.x

    The library provides two distinct browser implementations for testing:

    1. KernelBrowser: Makes requests directly using your Symfony Kernel. It is very fast but cannot execute JavaScript.
    2. 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');
  3. Use Components to wrap common tasks

    1.x

    Components are objects that extend Zenstruck\\Browser\\Component and 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():

    1. The preActions() method is called (useful for navigation).
    2. The preAssertions() method is called (useful for verifying page state).
    3. 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();
    });
  4. Configure the BrowserExtension for PHPUnit

    1.x

    To 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\BrowserExtension in your phpunit.xml file.

    For PHPUnit 8 or 9: Use the <extensions> block with the class attribute.

    For PHPUnit 10+: Use the <extensions> block with the bootstrap attribute.

    <!-- PHPUnit 8 or 9 -->
    <extensions>
        <extension class="Zenstruck\Browser\Test\BrowserExtension" />
    </extensions>
    
    <!-- PHPUnit 10+ -->
    <phpunit>
       ...
       <extensions>
          <bootstrap class="Zenstruck\Browser\Test\BrowserExtension" />
       </extensions>
    </phpunit>
  5. Create a Custom Browser

    1.x

    You can extend KernelBrowser or PlaywrightBrowser to add your own domain-specific actions and assertions.

    To use your custom browser class, set the corresponding environment variable:

    • For KernelBrowser extensions: KERNEL_BROWSER_CLASS
    • For PlaywrightBrowser extensions: PLAYWRIGHT_BROWSER_CLASS

    Tip: Use the @method annotation 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;
    }
  6. Set up the Browser in your tests

    1.x

    To use the library in your PHPUnit tests, include the Zenstruck\Browser\Test\HasBrowser trait in your test class.

    Note: For PlaywrightBrowser, your test class must extend Symfony\Bundle\FrameworkBundle\Test\KernelTestCase (or WebTestCase).

    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');
        }
    }
  7. Configure custom HttpOptions

    1.x

    To avoid duplicating HTTP request configuration (like headers), you can use HttpOptions in several ways:

    1. Per-browser defaults: Use ->setDefaultHttpOptions(array $options) on the browser instance. Per-request options will be merged with these defaults.
    2. In Test Case configuration: Set defaults in the browser() method of your test class.
    3. Custom HttpOptions objects: Extend Zenstruck\\Browser\\HttpOptions to create reusable, semantic request configurations (e.g., an api() method that automatically adds an X-Token header).
    // 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'));
  8. Configure default browser state in tests

    1.x

    To set a consistent starting state for your browser in every test, override the browser() method in your test class using the HasBrowser trait. This allows you to define default behaviors like intercepting redirects, throwing exceptions, or visiting a specific starting URL.

    Note that when using the HasBrowser trait, you should alias the trait's method (e.g., browser as baseKernelBrowser) to avoid naming collisions with your own browser() 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
            ;
        }
    }
  9. Extend a Browser with Traits

    1.x

    You 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 AuthenticationExtension trait:

    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;
    }
  10. Use PantherBrowser for real browser testing

    1.x

    The PantherBrowser class 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 PlaywrightBrowser instead.

    Because PantherBrowser relies on the WebDriver protocol, certain standard browser methods like assertStatus(), assertSuccessful(), and header assertions are not supported and will throw a BadMethodCallException.

  11. Use PlaywrightBrowser for real browser testing

    1.x

    The PlaywrightBrowser class 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,
    ]);