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
<?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:

bash
vendor/bin/playwright-install chromium

Pass several targets when one environment needs them:

bash
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:

bash
vendor/bin/playwright-install chrome
php
<?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:

text
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.

php
$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
<?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:

php
$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:

php
$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:

  1. create a context;
  2. open a page;
  3. navigate to a stable URL;
  4. read a title or assert a heading;
  5. close cleanly.

If the browser does not launch, check browser binaries first:

bash
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