Questions,
answered.
Common questions about installing, using, testing, debugging, and integrating Playwright PHP.
Getting started
What is Playwright PHP?
Playwright PHP drives real browsers (Chromium, Firefox, WebKit) from PHP. It wraps the official Playwright automation engine and exposes a PHP-idiomatic API for navigation, locators, actions, and web-first assertions. Use it for end-to-end tests and browser automation.
Compared with Selenium, it uses a single persistent connection to the browser and waits for elements to be actionable before acting, which removes most manual sleeps and reduces flakiness. See Start and Core concepts.
Which browsers does it support?
Chromium, Firefox, and WebKit, through the factories Playwright::chromium(), Playwright::firefox(), and Playwright::webkit(). Playwright::safari() is a WebKit alias; it does not automate the installed Safari application.
Playwright manages matching browser builds for you. Install the target you use with vendor/bin/playwright-install chromium, or install all three managed browsers with vendor/bin/playwright-install --browsers. See Browsers.
What are the system requirements?
PHP 8.2 or newer and Node.js 20 or newer. Playwright PHP drives the official Playwright engine, which runs on Node; a lightweight Node server starts automatically, so there is no separate service to run. It works on Linux, macOS, and Windows.
Browser processes dominate memory use, so budget a few hundred MB per running browser and more when you run tests in parallel.
How do I install Playwright PHP?
composer require --dev playwright-php/playwright
# Download Playwright's managed Chromium build
vendor/bin/playwright-install chromium
# Fresh machine or CI: Chromium plus OS-level dependencies
vendor/bin/playwright-install --with-deps chromium
Browsers download to ~/.cache/ms-playwright. Verify the install with a short script that opens a page and prints its title.
What is the difference between Browser, BrowserContext, and Page?
A BrowserContext is an isolated browser session with its own cookies, storage, and cache. A Page is a single tab inside a context. The entry point returns a context directly (Playwright::chromium()), and you call $context->newPage() for tabs; the underlying browser process is managed for you.
Use one context per test for isolation, and multiple pages within a context when a flow spans tabs.
What is a Locator and why should I use it?
A locator is a lazy, re-findable description of the elements a selector matches. Unlike a cached element, it re-queries the DOM when you use it. Actions wait for their relevant actionability checks, and single-target actions fail when more than one element matches.
Build locators with getByRole, getByLabel, getByText, or locator(), then chain or filter them. See Locators.
What does auto-waiting mean?
Before each action, Playwright waits for the target element to be attached, visible, stable, enabled, and able to receive events. Web-first assertions poll until they pass or time out. This removes most manual sleeps.
Auto-waiting cannot wait for conditions it cannot observe, for example a background job finishing, so wait on the resulting UI change instead.
What is strict mode and why am I getting errors?
Single-target actions on a locator fail when the selector matches more than one element. This is the opposite of a "not found" error: it means too many matches, and the locator needs to express the intended target more precisely.
Fix it by narrowing the locator (add a container, role, or filter) or selecting explicitly with ->first(), ->last(), ->nth($i), or ->filter(['hasText' => '...']).
Writing and running tests
How do I integrate with PHPUnit?
Use PlaywrightTestCaseTrait from Playwright\Testing. Call $this->setUpPlaywright() in setUp() and $this->tearDownPlaywright() in tearDown(). The trait provides $this->page, $this->context (a BrowserContext), and $this->expect(...). PHPUnit 11 or newer is required.
final class LoginTest extends TestCase
{
use PlaywrightTestCaseTrait;
protected function setUp(): void { parent::setUp(); $this->setUpPlaywright(); }
protected function tearDown(): void { $this->tearDownPlaywright(); parent::tearDown(); }
public function testLoginRedirects(): void
{
$this->page->goto('https://app.example.com/login');
$this->page->getByLabel('Email')->fill('user@example.com');
$this->page->getByRole('button', ['name' => 'Sign in'])->click();
$this->expect($this->page)->toHaveURL('https://app.example.com/dashboard');
}
}
How do I mock or modify network requests?
Intercept requests with routing, then fulfill, modify, or abort them:
$page->route('**/api/**', function (Route $route): void {
$route->fulfill(['status' => 200, 'body' => '{"ok":true}']);
});
Match URLs with glob patterns. Register the route before the navigation that triggers the request, or the page may already have sent it. See Mock an API response and Block unwanted requests.
How do I reuse a login session across tests?
Log in once, then save cookies and localStorage with saveStorageState() and load them into new contexts. This skips the login flow in every test:
$context->saveStorageState(__DIR__.'/.auth/user.json');
// later, in another context
$authed = Playwright::chromium([
'context' => ['storageState' => __DIR__.'/.auth/user.json'],
]);
For multiple roles, save one state file per role. A common pattern runs a one-time setup script that logs in and writes the state file before the suite starts. See Reuse a login session.
What is a trace and when should I use it?
A trace records DOM snapshots, network activity, console output, and per-action screenshots. Start and stop it around the flow:
$context->startTracing($page, ['screenshots' => true, 'snapshots' => true]);
// ... actions ...
$context->stopTracing($page, 'trace.zip');
Open the zip with npx playwright show-trace trace.zip or the online viewer at trace.playwright.dev. Capturing traces on CI failures lets you debug without reproducing locally. See Record a trace.
How do I debug a failing test?
Run headed with slowMo, call $page->pause() at the point of interest, capture a trace and open it in the viewer, and take a screenshot at the moment of failure. Narrow the run with PHPUnit's --filter.
The timeout error names what it waited for, which is usually the fastest clue.
How do I handle flaky tests and retries?
Most flakiness comes from manual waits and unstable selectors. Use auto-waiting locators and web-first assertions instead of sleeps, and prefer role or label locators over brittle CSS. Investigate a failure with a trace.
The library has no built-in test retry, and PHPUnit has none natively; add a PHPUnit extension if you need automatic reruns. Fix the root cause first: a retried flaky test still hides a real race.
How do I integrate with CI/CD?
Install PHP 8.2+ and Node 20+, run composer install, install browsers, then run the suite:
- uses: shivammathur/setup-php@v2
with: { php-version: '8.2' }
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/playwright-install --with-deps chromium
- run: vendor/bin/phpunit --colors=always
Cache ~/.cache/ms-playwright to speed up runs. If browser installation fails, --with-deps also installs the OS-level libraries that minimal Linux images lack; behind a proxy, set HTTPS_PROXY first. See setup-playwright.
Why does my test fail in CI but pass locally?
Common causes: missing browser system dependencies (use --with-deps), a different viewport or headless rendering, slower machines exposing race conditions, missing fonts or locales, and time-zone differences.
Capture a trace on CI to see what the browser saw, and pin the same PHP, Node, and browser versions in both places.
Project and ecosystem
How do I use Playwright PHP with Symfony?
The playwright-php/playwright-symfony package integrates the two: it routes selected browser requests through the test kernel, provides a kernel-aware base test, and exposes profiler data on demand. It does not provide automatic database reset or ready-made application fixtures.
See the Symfony guide and the playwright-symfony package.
Are there Behat and Mink integrations?
Both exist. playwright-php/playwright-mink is a Mink compatibility driver: an existing Mink suite keeps its Session API and gains Playwright browsers. playwright-php/playwright-behat is a Behat extension that drives the browser directly, with a small set of built-in steps and a base context for your own.
Choose Mink when a Mink suite already exists, and the Behat extension when feature files are new or Mink is not in the picture. The extension is pre-1.0, so a minor version can still break compatibility.
See the playwright-behat and playwright-mink package pages.
Can I use Playwright PHP with Laravel?
Yes, indirectly. Playwright PHP is framework-agnostic and drives any HTTP server, so it tests a Laravel app served locally, for example with php artisan serve. Point goto() at the server URL and drive the UI as usual.
There is no dedicated Laravel package today, so you wire the server lifecycle yourself in your test bootstrap.
How does Playwright PHP compare to Selenium?
Both automate real browsers. Playwright PHP talks through its bundled Node bridge and Playwright protocol, while Selenium uses WebDriver. Supported actions perform their required actionability checks, and the installer manages the browser builds expected by the library.
Selenium has a larger ecosystem and broader language and grid support. Choose Playwright PHP when its locator, browser, and artifact model fits the suite; keep Selenium if you depend on Selenium Grid or WebDriver-only integrations. See /parity.
How do I migrate from Symfony Panther?
Panther drives a browser through WebDriver and returns a crawler; Playwright PHP uses locators with auto-waiting. Replace $client->request() with $page->goto(), crawler filters with locators, and Panther waits with web-first assertions.
Expect fewer explicit waits and faster runs. A page object is a plain class, so you can migrate its internals to Playwright locators one page at a time while keeping its public methods.
Can I use Playwright PHP for web scraping?
Yes. It renders JavaScript-heavy pages and exposes content through locators and textContent(), so it handles sites a plain HTTP client cannot. It is heavier than an HTTP client, so use it only when you need a real browser.
Respect each site's terms of service, robots rules, and applicable law.
Is Playwright PHP production-ready?
Yes. The 1.x core browser surface is stable and MIT-licensed. Each companion package publishes its own status, version, and release history on its package page.
How do I contribute or get help?
The project is open source (MIT), maintained by Simon André under the playwright-php/playwright repository. Fork it, run composer install, run the suite with vendor/bin/phpunit (exclude the integration group when no browser is available), and open a pull request.
For help, read the guides and reference, search existing issues, and open a GitHub issue with a minimal reproduction: PHP and Node versions, the browser, and a short script. See the integrations overview.