skylerkatz / phpunit-plugin-browser
PHPUnit extension for browser testing with Playwright
Package info
github.com/skylerkatz/phpunit-plugin-browser
pkg:composer/skylerkatz/phpunit-plugin-browser
Requires
- php: ^8.3
- ext-sockets: *
- amphp/amp: ^3.1.1
- amphp/http-server: ^3.4.4
- amphp/websocket-client: ^2.0.2
- nikic/php-parser: ^5.0
- nunomaduro/collision: ^8.9
- phpunit/phpunit: ^11.0
- symfony/process: ^7.4.5|^8.0.5
Requires (Dev)
- brianium/paratest: ^7.8
- orchestra/testbench: ^11.0
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