skylerkatz/phpunit-plugin-browser

PHPUnit extension for browser testing with Playwright

Maintainers

Package info

github.com/skylerkatz/phpunit-plugin-browser

pkg:composer/skylerkatz/phpunit-plugin-browser

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.0.3 2026-03-25 19:19 UTC

This package is auto-updated.

Last update: 2026-03-25 19:21:59 UTC


README

A PHPUnit extension for browser testing powered by Playwright.

Attribution: This package is a port of pestphp/pest-plugin-browser by Nuno Maduro and the Pest team. Full credit goes to the original authors for the API design and Playwright integration. This package adapts that work for use with PHPUnit directly.

Requirements

  • PHP 8.3+
  • ext-sockets
  • PHPUnit 11+
  • Node.js (required for Playwright)

Installation

Install via Composer:

composer require --dev skylerkatz/phpunit-plugin-browser

Install Playwright browsers:

npx playwright install --with-deps

PHPUnit Configuration

Register the extension in your phpunit.xml:

<extensions>
    <bootstrap class="SkylerKatz\PHPUnitBrowser\Extension\BrowserExtension" />
</extensions>

You can pass optional parameters directly in the bootstrap:

<extensions>
    <bootstrap
        class="SkylerKatz\PHPUnitBrowser\Extension\BrowserExtension"
        headed="true"
        debug="true"
        diff="true"
        dark="true"
        browser="firefox"
        timeout="10000"
        host="127.0.0.1"
    />
</extensions>

Writing Tests

Extend BrowserTestCase and use $this->visit() to open a page:

Basic Example

<?php

namespace Tests\Browser;

use SkylerKatz\PHPUnitBrowser\BrowserTestCase;

class ExampleTest extends BrowserTestCase
{
    public function test_homepage(): void
    {
        $this->visit('http://localhost:8000')
            ->assertTitle('My App')
            ->assertSee('Welcome');
    }
}

Form Interactions

public function test_login(): void
{
    $this->visit('http://localhost:8000/login')
        ->type('email', 'user@example.com')
        ->type('password', 'secret')
        ->press('Sign In')
        ->assertUrlIs('http://localhost:8000/dashboard')
        ->assertSee('Welcome back');
}

Dropdowns, Checkboxes, and Radio Buttons

public function test_form_fields(): void
{
    $this->visit('http://localhost:8000/settings')
        ->select('country', 'US')
        ->check('terms')
        ->radio('plan', 'pro')
        ->press('Save');
}

Device Emulation

use SkylerKatz\PHPUnitBrowser\Enums\Device;

public function test_mobile_layout(): void
{
    $this->visit('http://localhost:8000')
        ->fromDevice(Device::IPHONE_15_PRO)
        ->assertVisible('.mobile-menu');
}

Available devices include DESKTOP, MOBILE, MACBOOK_16, IPHONE_15_PRO, IPAD_PRO, PIXEL_8, GALAXY_S24_ULTRA, SURFACE_PRO_9, and more.

Dark Mode

public function test_dark_mode(): void
{
    $this->visit('http://localhost:8000')
        ->inDarkMode()
        ->assertScreenshotMatches();
}

Screenshot Assertions

public function test_visual_regression(): void
{
    $this->visit('http://localhost:8000')
        ->assertScreenshotMatches();

    // Full-page screenshot
    $this->visit('http://localhost:8000')
        ->assertScreenshotMatches(fullPage: true);
}

JavaScript Execution

public function test_javascript(): void
{
    $this->visit('http://localhost:8000')
        ->script('return document.title')
        ->assertScript('window.appVersion', '2.0');
}

Multiple URLs

public function test_multiple_pages(): void
{
    $this->visit([
        'http://localhost:8000',
        'http://localhost:8000/about',
    ])->assertSee('Welcome');
}

iFrames

public function test_iframe_content(): void
{
    $this->visit('http://localhost:8000')
        ->withinFrame('.iframe', function ($frame) {
            $frame->assertSee('Frame content');
        });
}

Configuration

Environment Variables

Variable Description
BROWSER_HEADED Show the browser window (true/false)
BROWSER_DEBUG Enable debug mode on failures
BROWSER_DIFF Show diffs on screenshot failures
BROWSER_DARK Use dark color scheme
BROWSER_LIGHT Use light color scheme
BROWSER_TYPE Browser: chrome, firefox, or safari
BROWSER_TIMEOUT Assertion timeout in milliseconds
BROWSER_HOST Playwright server host

Runtime Configuration

Configure globally in your test's setUp method:

use SkylerKatz\PHPUnitBrowser\Playwright\Playwright;
use SkylerKatz\PHPUnitBrowser\Enums\BrowserType;
use SkylerKatz\PHPUnitBrowser\Enums\ColorScheme;

protected function setUp(): void
{
    parent::setUp();

    Playwright::headed();
    Playwright::setTimeout(10000);
    Playwright::setDefaultBrowserType(BrowserType::FIREFOX);
    Playwright::setColorScheme(ColorScheme::DARK);
    Playwright::setUserAgent('Custom User Agent');
}

Available Assertions

Element Assertions

Method Description
assertTitle($title) Assert page title matches exactly
assertTitleContains($text) Assert page title contains text
assertSee($text) Assert text is visible on page
assertDontSee($text) Assert text is not visible
assertSeeIn($selector, $text) Assert text is visible within element
assertDontSeeIn($selector, $text) Assert text is not visible within element
assertPresent($selector) Assert element exists in DOM
assertMissing($selector) Assert element does not exist in DOM
assertVisible($selector) Assert element is visible
assertEnabled($selector) Assert element is enabled
assertDisabled($selector) Assert element is disabled
assertChecked($selector) Assert checkbox is checked
assertSelected($selector, $value) Assert select has value
assertValue($selector, $value) Assert input has value
assertAttribute($selector, $attr, $value) Assert element attribute value
assertAriaAttribute($selector, $attr, $value) Assert ARIA attribute value
assertDataAttribute($selector, $attr, $value) Assert data attribute value
assertSourceHas($code) Assert page source contains string
assertSourceMissing($code) Assert page source does not contain string
assertSeeLink($url) Assert link is visible
assertDontSeeLink($url) Assert link is not visible

URL Assertions

Method Description
assertUrlIs($url) Assert full URL matches (supports * wildcards)
assertUrlContains($text) Assert URL contains text
assertQueryStringHas($key, $value) Assert query string parameter

Screenshot Assertions

Method Description
assertScreenshotMatches($fullPage) Visual regression test against baseline

Available Interactions

Method Description
click($selector) Click an element
press($text) Press a button by text
type($selector, $value) Type into an input
typeSlowly($selector, $value) Type character by character
fill($selector, $value) Fill an input (clears first)
clear($selector) Clear an input
append($selector, $value) Append text to an input
select($selector, $value) Select a dropdown option
check($selector) Check a checkbox
uncheck($selector) Uncheck a checkbox
radio($selector, $value) Select a radio button
hover($selector) Hover over an element
rightClick($selector) Right-click an element
drag($from, $to) Drag and drop
keys($selector, $keys) Send keyboard input
attach($selector, $path) Upload a file
script($js) Execute JavaScript

Migrating from Pest

This package includes a CLI tool to convert Pest test files to PHPUnit. It handles structural transformations (test()/it() to class methods, expect() chains to PHPUnit assertions, beforeEach/afterEach to setUp/tearDown, etc.) and converts visit() calls to $this->visit().

# Convert a file (overwrites in place)
vendor/bin/pest-to-phpunit tests/Browser/LoginTest.php

# Preview output without writing
vendor/bin/pest-to-phpunit tests/Browser/LoginTest.php --stdout

# Convert a directory
vendor/bin/pest-to-phpunit tests/Browser/

# Convert to a different directory
vendor/bin/pest-to-phpunit tests/Browser/ --output-dir=tests/PHPUnit/

# Dry run (shows what would be converted)
vendor/bin/pest-to-phpunit tests/Browser/ --dry-run

# Set a custom namespace
vendor/bin/pest-to-phpunit tests/Browser/ --namespace="Tests\\Feature"

What gets converted

Pest PHPUnit
test('name', function () { ... }) public function test_name(): void { ... }
it('does something', function () { ... }) public function test_it_does_something(): void { ... }
describe('group', ...) Method name prefix (e.g. test_group_name)
beforeEach(function () { ... }) setUp() with parent::setUp()
afterEach(function () { ... }) tearDown() with parent::tearDown()
uses(RefreshDatabase::class) use RefreshDatabase trait
visit('/') $this->visit('/')
expect($val)->toBe('foo') $this->assertSame('foo', $val)
expect($val)->not->toBeNull() $this->assertNotNull($val)
->skip() $this->markTestSkipped()
->todo() $this->markTestIncomplete()

License

MIT