First browser test
The model behind a first browser test: contexts, pages, locators, and retrying assertions.
Start walks you through installing the package, running a first script, and turning it into a PHPUnit test. This page explains what that first run actually did, so the rest of the chapter makes sense.
Four ideas, one flow
A typical user-interface test uses four ideas in the same order:
use Playwright\Playwright;
use function Playwright\Testing\expect;
$context = Playwright::chromium();
$page = $context->newPage();
$page->goto('https://example.com');
expect($page->locator('h1'))->toBeVisible();
$context->close();
| Idea | What it is |
|---|---|
| BrowserContext | An isolated session, with its own cookies and storage. Treat it as a clean user profile. |
| Page | One tab inside that session. Most flows start here. |
| Locator | A recipe for finding an element, resolved when an action or assertion needs it. |
| Expectation | An assertion that retries until the browser reaches the expected state. |
The last two are why there is no sleep() in the snippet. A locator waits for the element to be actionable; an assertion waits for the state to match. Adding a sleep on top only makes the suite slower, not steadier.
Point it at your own application
https://example.com is the right first target because it takes your application out of the equation. The second test should not be.
$baseUrl = getenv('APP_TEST_URL') ?: 'http://127.0.0.1:8000';
$page->goto($baseUrl.'/login');
$page->getByLabel('Email')->fill('ada@example.com');
$page->getByRole('button', ['name' => 'Sign in'])->click();
expect($page->getByRole('heading', ['name' => 'Dashboard']))->toBeVisible();
Reading the base URL from the environment keeps the test portable: your laptop, CI, and Docker each supply their own without touching the test.
Test through the running HTTP application, not through controllers. A browser test earns its cost by proving the route, HTML, JavaScript, cookies, redirects, and assets a real user receives.
Keep the first suite small
Three questions are worth answering first: can the browser start on every machine, can it reach the application, and can one user-visible flow pass and fail clearly.
Login, signup, checkout, search, or a create-edit-delete flow make good first candidates. Animation details, every validation message, and admin screens with heavy fixtures do not. A large first suite hides setup problems inside product failures.
Common mistakes
Launching a context and never closing it leaves browser processes behind. Close it in scripts; the provided PHPUnit base class or trait can own the lifecycle in tests.
Reaching for CSS too early makes tests fail after harmless redesigns. Start with getByRole(), getByLabel(), and visible text.
Raising a timeout to fix a failure hides the cause. Capture a screenshot, check console errors, and verify the action before the assertion actually happened.
Go next
- Understand the session model: Browsers and contexts
- Find elements well: Locators
- Set up the runner: Testing with PHPUnit
- Read a failure: Errors and failure analysis