Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Playwright Laravel Package

playwright-php/playwright

Playwright PHP brings browser automation to PHP with a Playwright-powered API for Chromium, Firefox, and WebKit. Automate pages, scrape content, take screenshots, generate PDFs, and write end-to-end tests using familiar PHP code.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this as a PHP client for the Playwright Node driver, not a pure-PHP browser library. You need PHP 8.1+, Composer, and a Node.js runtime. Install the package and then install the browser binaries through the package’s documented install command (often a vendor/bin/playwright wrapper around playwright install):

composer require --dev playwright-php/playwright
vendor/bin/playwright install chromium

A minimal script establishes the core lifecycle: launch a browser, create a page, navigate, assert, capture, close.

use Playwright\Playwright;

$playwright = Playwright::create();
$browser = $playwright->chromium()->launch(['headless' => true]);

try {
    $page = $browser->newPage();
    $page->goto('https://example.com');

    $page->getByRole('heading', ['name' => 'Example Domain'])->waitFor();
    $page->screenshot()->save(__DIR__ . '/example.png');
} finally {
    $browser->close();
}

Look first at:

  • The Playwright factory and Browser, BrowserContext, Page, Locator, and assertion classes. The API intentionally mirrors upstream Playwright, so the official Playwright docs are your best reference for selector and waiting semantics.
  • The package’s own tests/ directory for exact constructor options, assertion names, and process-management conventions.
  • The browser install/update path, because the PHP package and installed browser binaries must stay compatible.

The best first use case is a smoke test against a staging URL: navigate, assert a heading or login button, take a screenshot, and fail the command on a missing element. Once that works, expand into authenticated flows and CI integration.

Implementation Patterns

Share the browser, isolate every test. Launch one browser per test suite, but create a fresh BrowserContext and Page per test. This keeps cookies, storage, and service workers isolated while avoiding repeated browser startup costs.

protected static Browser $browser;
protected BrowserContext $context;
protected Page $page;

public static function setUpBeforeClass(): void
{
    self::$browser = Playwright::create()->chromium()->launch();
}

protected function setUp(): void
{
    $this->context = self::$browser->newContext([
        'baseURL' => 'http://localhost:8000',
        'viewport' => ['width' => 1440, 'height' => 900],
    ]);
    $this->page = $this->context->newPage();
}

protected function tearDown(): void
{
    $this->context->close();
}

public static function tearDownAfterClass(): void
{
    self::$browser->close();
}

Drive everything through locators. Prefer role, label, text, and data-testid selectors over brittle CSS paths. Playwright locators auto-wait and retry, which is the main defense against flaky tests.

$this->page->getByLabel('Email')->fill('[email protected]');
$this->page->getByLabel('Password')->fill('secret');
$this->page->getByRole('button', ['name' => 'Sign in'])->click();

$this->expect($this->page->getByRole('heading', ['name' => 'Dashboard']))->toBeVisible();

Use web-first assertions instead of manual sleeps. Assertions should poll the DOM. Do not write sleep(2) or manual retry loops around selectors; adjust timeouts or fix the underlying race instead.

Control the network for deterministic tests. Route interception is the cleanest way to remove third-party dependencies, simulate error states, or stub slow APIs.

$this->page->route('**/api/recommendations', fn ($route) => $route->fulfill([
    'status' => 200,
    'contentType' => 'application/json',
    'body' => json_encode(['items' => []]),
]));

Reuse authentication state. Log in once in a setup step, save storageState to a file, and create authenticated contexts from it. This cuts minutes from suites with many logged-in tests. Keep one state file per role and regenerate it when auth changes.

Capture artifacts on failure. In tearDown(), check the test outcome and save a screenshot, trace, or video with a sanitized test name. In CI, upload these as build artifacts. This is often the difference between a five-minute diagnosis and a two-hour investigation.

if ($this->hasFailed()) {
    $this->page->screenshot()->save(
        __DIR__ . "/artifacts/{$this->name()}.png",
        ['fullPage' => true]
    );
}

Create page objects for repeated flows. Keep selectors and multi-step journeys in tests/Browser/Pages or equivalent classes: LoginPage, CheckoutPage. Tests should read as business intent, not selector soup. For Laravel applications, pair this with RefreshDatabase, a seeded test database, and either a real php artisan serve process or your existing test HTTP server. Playwright should hit the running application over HTTP, not bootstrap it in-process.

Parallelize with separate contexts, not shared pages. Run tests in parallel via PHPUnit/Pest workers, each with its own browser context. Do not share a Page across processes, and avoid parallel tests that mutate the same user or storage-state file.

Gotchas and Tips

  • Node and browser versions must match the package. After upgrading the Composer package, rerun the browser install step. A mismatched Playwright driver and browser build produces confusing protocol errors.
  • Always close browsers in finally or teardown. A failed assertion or PHP fatal error can leave a Node process and headless browser running. Long-lived CI runners will accumulate zombies. Register a shutdown cleanup if your test bootstrap allows it.
  • CI needs headless browser dependencies. Use an official Playwright Docker image or install OS dependencies. In containers, add --no-sandbox launch args if running as a non-root user, and increase /dev/shm (--shm-size=1g) for Chromium stability.
  • Understand the two timeout layers. Navigation and action timeouts are separate from assertion timeouts. Set a sensible default per context, but override locally for legitimately slow operations rather than raising the global timeout.
  • Prefer explicit waits over waitForLoadState assumptions. Single-page apps often render after load. Wait for a visible element or URL change; use waitForURL after navigation-triggering clicks.
  • The PHP/JS boundary is real. Arguments passed to evaluate must be serializable. You cannot pass PHP closures into browser-side JavaScript. Keep browser logic as string expressions or small serializable payloads, and return plain arrays/scalars.
  • Use absolute artifact paths. Screenshot, video, download, and upload paths are resolved by the driver process, whose working directory may differ from PHP’s. Ensure directories exist before saving.
  • Do not share mutable context state across tests. Cookies, local storage, and route handlers leak. Create a new context per test, or explicitly reset state. Storage-state files written by parallel workers are a common race condition.
  • Debug with headed mode and traces, not var_dump. Run locally with headless => false and a small slowMo delay. Record traces for CI failures and inspect them in the Playwright trace viewer. Attach console and pageerror listeners early so browser errors appear in PHP output.
  • Selector quality is the main flakiness lever. Avoid CSS chains tied to layout classes. Add data-testid attributes in the application where role/text selectors are ambiguous. This is cheaper than repeatedly patching tests.
  • Assertions are retrying; plain PHP checks are not. expect($locator)->toBeVisible() polls. A raw count() or isVisible() call is a one-shot check and will be flaky against dynamic pages.
  • Extension points to invest in: a base test case that owns browser/context lifecycle, a page-object layer, custom assertion helpers, event listeners for console/network diagnostics, and a CI job that caches browser binaries keyed by package version. These wrappers keep the raw Playwright API out of most tests and make upgrades far less painful.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource