Essentials
Locators, actions, and assertions side by side. Copy a line, drop it into your test, keep moving.
Locators
Find the element the way a user would. Locators are lazy: nothing touches the page until an action or an assertion runs.
| Call | Note |
|---|---|
getByRole('button', ['name' => 'Save']) |
Preferred: how users perceive it |
getByLabel('Email') |
Form fields, by their label |
getByText('Welcome') |
Visible text, substring by default |
getByPlaceholder('Search') |
Inputs with no label |
getByTestId('cart-total') |
Escape hatch, for data-testid |
getByTitle('Close') |
The title attribute |
getByAltText('Logo') |
Images, by alt text |
locator('.card')->first() |
CSS or XPath, when you must |
->filter(['hasText' => 'new']) |
Narrow any locator |
->and($other) / ->or($other) |
Combine two locators |
->first() ->last() ->nth(2) |
Pick one out of many |
->count() / ->all() |
How many, and each of them |
Actions
Each action waits for the actionability checks that apply to it. If you are reaching for sleep(), assert or wait for the page state the next step actually needs.
| Call | Note |
|---|---|
->click() / ->dblclick() |
Waits for actionable, then clicks |
->fill('ada@example.com') |
Clears, then types |
->clear() |
Empties the field |
->press('Enter') |
One key, including Meta+K |
->type('slowly') |
Key by key, for live search |
->check() / ->uncheck() |
Checkboxes and radios |
->selectOption('fr') |
Dropdowns |
->hover() / ->dragTo($target) |
Pointer gestures |
->setInputFiles('cv.pdf') |
Uploads, with no dialog to handle |
->focus() / ->blur() |
Move focus around |
$page->goto('/checkout') |
Navigates, and waits for load |
Assertions
Assert on the locator, not on a value you pulled out of it. expect() retries until it passes or the timeout runs out; a plain assertSame($el->textContent(), ...) checks once and flakes.
| Call | Note |
|---|---|
expect($el)->toBeVisible() |
Retries until it passes or times out |
->toContainText('Saved') |
Substring |
->toHaveText('Saved') |
Whole text |
->toHaveValue('42') |
Inputs and selects |
->toHaveCount(3) |
List length, without the flake |
->toBeChecked() / ->toBeEnabled() |
State |
->toHaveAttribute('href', '/x') |
Attributes |
->toHaveClass('is-open') |
Classes |
expect($page)->toHaveURL('/done') |
Page level |
expect($page)->toHaveTitle('Docs') |
Page level |
->not()->toBeVisible() |
Negate anything |
->withTimeout(10_000) |
Override the wait, for one check |
Engines
| Call | Note |
|---|---|
Playwright::chromium() |
Playwright-managed Chromium by default |
Playwright::firefox() |
Playwright-managed Firefox |
Playwright::webkit() |
Playwright's WebKit build, not installed Safari |
->withHeadless(false)->launch() |
Watch it drive itself |
PlaywrightFactory::create() |
The client, when you need several contexts |
$context->newPage() |
A tab in that session |
Devices
Ships separately, in playwright-php/devices.
use Playwright\Device\DeviceRegistry;
$device = (new DeviceRegistry())->get('iPhone 15 Pro');
$device->landscape();
Setup
composer require --dev playwright-php/playwright
vendor/bin/playwright-install chromium
Trace a failure
$context->startTracing($page, ['screenshots' => true, 'snapshots' => true]);
// ... the run ...
$context->stopTracing($page, 'trace.zip');
See record a trace for the whole loop.
Deeper reads
- Learn the model: Core concepts
- Locators sheet: Locators
- Assertions sheet: Assertions
- Actions guide: Actions and input
- First test: Write your first test