Browsers and contexts
Pick an engine, launch it, isolate state, and decide when to use one browser, one context, or many.
Playwright PHP drives real Chromium, Firefox, and WebKit browsers. Browser selection has three separate parts: the installation target downloads a browser, the browser type selects an API family, and a channel selects a branded Chromium distribution.
A browser process runs the selected browser. A browser context is an isolated session inside it. A page is a tab inside the context. Most test suites should launch the browser once where practical, then create fresh contexts for scenarios that must not share cookies, storage, permissions, or routes.
Choose an engine deliberately
Start with Chromium for fast local feedback unless your product has a stronger reason to start elsewhere. Chromium is usually the quickest path to a stable developer loop.
Add other engines when they answer a real product question:
- add WebKit when Safari behavior matters;
- add Firefox when you support Firefox users or want a second independent engine;
- run all three for flows where rendering, input, layout, or browser APIs have already caused risk.
Do not treat "three engines" as a substitute for good test design. A brittle selector is still brittle when it fails in three browsers.
For a single script, use the static facade. It launches a browser, creates a context, and returns that context:
<?php
require __DIR__.'/vendor/autoload.php';
use Playwright\Playwright;
$context = Playwright::chromium(['headless' => true]);
$page = $context->newPage();
$page->goto('https://example.com');
echo $page->title().PHP_EOL;
$context->close();
Expected result: the script prints Example Domain and closes the browser context.
Available static entries are Playwright::chromium(), Playwright::firefox(), Playwright::webkit(), and Playwright::safari() as a WebKit alias. The Safari alias launches Playwright's WebKit build; it does not automate the Safari application installed on macOS.
Install the browser you launch
Install only the browser target used by the script or CI job:
vendor/bin/playwright-install chromium
Pass several targets when one environment needs them:
vendor/bin/playwright-install chromium webkit
The installer accepts these targets:
| Target | What it installs |
|---|---|
chromium |
Playwright-managed Chromium |
firefox |
Playwright-managed Firefox |
webkit |
Playwright-managed WebKit |
chrome |
Google Chrome stable |
chrome-beta |
Google Chrome Beta |
msedge |
Microsoft Edge stable |
msedge-beta |
Microsoft Edge Beta |
Use vendor/bin/playwright-install --browsers when the project needs the complete managed set: Chromium, Firefox, and WebKit. The shortcut cannot be combined with target names.
Chrome and Edge install in the operating system's global location. They can replace an existing branded browser installation, and PLAYWRIGHT_BROWSERS_PATH does not relocate them. Prefer managed Chromium unless a test specifically needs Chrome or Edge.
Launch Chrome or Edge through a channel
Chrome and Edge are Chromium distributions, not separate Playwright browser types. Install the matching target, then select its channel when launching Chromium:
vendor/bin/playwright-install chrome
<?php
require __DIR__.'/vendor/autoload.php';
use Playwright\Configuration\PlaywrightConfigBuilder;
use Playwright\PlaywrightFactory;
$config = PlaywrightConfigBuilder::create()
->withChannel('chrome')
->build();
$playwright = PlaywrightFactory::create($config);
$browser = $playwright->chromium()->launch();
$context = $browser->newContext();
$page = $context->newPage();
$page->goto('https://example.com');
$context->close();
$browser->close();
$playwright->close();
The supported branded targets and channel names are chrome, chrome-beta, msedge, and msedge-beta. Installing a channel does not select it at runtime; installation and launch configuration remain separate choices.
Understand the lifecycle
Use this hierarchy when deciding where a setting belongs:
Playwright client
└── Browser process
└── BrowserContext: isolated user/session
└── Page: one tab
Browser-level settings affect the process: engine, channel, launch arguments, proxy, downloads path, headed/headless mode, and slow motion.
Context-level settings affect the session: cookies, local storage, permissions, viewport, locale, timezone, geolocation, request routing, storage state, video, and tracing.
Page-level work is the user journey: navigation, locators, actions, assertions, screenshots, dialogs, console messages, and page events.
When in doubt, put user state on the context. That keeps tests isolated without paying for a full browser launch each time.
Use contexts for isolation
A BrowserContext is the normal test boundary. Give each scenario a fresh context unless the scenario explicitly needs shared state.
$context = Playwright::chromium([
'context' => [
'viewport' => ['width' => 1280, 'height' => 720],
'locale' => 'en-US',
'timezoneId' => 'UTC',
],
]);
$page = $context->newPage();
$page->goto('https://app.example.test');
This creates one isolated session with predictable environment settings before the page loads.
Avoid using one context for a whole suite because it feels faster. The failures are subtle: a cookie left by one test, a permission prompt already accepted, a route still installed, or local storage from a previous account.
There are valid exceptions. A suite may load a saved authentication state to avoid repeating a slow login flow, or it may create two contexts to test collaboration between users. The difference is intent: shared or preloaded state should be visible in the test setup, not an accident left by a previous run.
Use PlaywrightFactory when you need the browser object
The static facade is intentionally short. It returns a context. If you need several contexts from one browser, launch through PlaywrightFactory:
<?php
require __DIR__.'/vendor/autoload.php';
use Playwright\PlaywrightFactory;
$playwright = PlaywrightFactory::create();
$browser = $playwright->chromium()->withHeadless(true)->launch();
$anonymous = $browser->newContext();
$loggedIn = $browser->newContext([
'storageState' => __DIR__.'/var/authenticated-state.json',
]);
$anonymousPage = $anonymous->newPage();
$loggedInPage = $loggedIn->newPage();
$anonymousPage->goto('https://app.example.test/pricing');
$loggedInPage->goto('https://app.example.test/dashboard');
$anonymous->close();
$loggedIn->close();
$browser->close();
$playwright->close();
Use this pattern for multi-user tests, several isolated sessions, or process-level browser control.
Common mistake: calling launch() after Playwright::chromium(). That method has already launched the browser and returned a context. If your variable is a context, call newPage() or close() on it.
Headless, headed, and slow motion
Use headless mode for CI and repeatable local runs. Use headed mode when debugging a specific behavior:
$context = Playwright::chromium([
'headless' => false,
'slowMo' => 150,
]);
slowMo is a diagnostic tool. It makes actions easier to watch, but it should not be required for correctness. If a flow passes only with slow motion, the test is probably missing a locator, assertion, or event wait.
Before committing or making CI changes, return the normal path to headless mode and keep evidence through screenshots, traces, logs, or videos instead.
Devices belong to context configuration
Device emulation is not a separate browser. It is a coherent set of context options: viewport, user agent, device scale factor, mobile mode, and touch support.
For explicit options:
$context = Playwright::webkit([
'context' => [
'viewport' => ['width' => 393, 'height' => 852],
'deviceScaleFactor' => 3,
'isMobile' => true,
'hasTouch' => true,
],
]);
For named presets, use the devices companion package and pass the descriptor to the context. Keep device testing representative. Testing every device descriptor on every pull request usually adds cost without adding insight.
A useful device matrix is usually small: one desktop baseline, one narrow mobile viewport, and targeted presets for product-critical experiences. Add more only when a product decision depends on them.
Verification and debugging
A browser setup is healthy when a tiny script can:
- create a context;
- open a page;
- navigate to a stable URL;
- read a title or assert a heading;
- close cleanly.
If the browser does not launch, check browser binaries first:
vendor/bin/playwright-install chromium
If a test passes locally and fails in CI, compare the environment before changing assertions: browser engine, headless mode, viewport, timezone, locale, permissions, environment variables, and stored state.
If a test leaks state, make the context lifecycle visible. The fix is usually a fresh context, explicit storageState, or moving route setup into the test that needs it.
Common mistakes
Using pages as isolation. A second page in the same context shares cookies and storage. Use another context for another user.
Changing viewport after navigation. Many responsive apps decide layout during initial load. Set context options before opening the page.
Debugging with headed mode only. Headed mode is useful, but CI is usually headless. Keep artifacts for the failing environment.
Running every engine for every test. Start with one fast lane. Add engines to flows where the product risk justifies the cost.
Go next
- Previous guide: Core concepts
- Next guide: Pages and frames
- Subject map: Browser
- Device details: Devices
- State recipes: Reuse a login session
- API reference: BrowserContext, Browser