Technology handbook
Playwright — API, Locators, Fixtures & Tooling
104 entries covering the Playwright API: locators, actions, web-first assertions, fixtures, network mocking, configuration and the tracing tools.
- Topics
- 104
- Sections
- 9
Core API
@playwright/test
The test runner: fixtures, parallelism, retries, reporters and assertions in one package.
- The test runner package supplies test, expect, fixtures and the config.
- Runner and library ship together, which is why fixtures and assertions align.
- Importing expect from elsewhere loses the retrying web-first assertions.
playwright
The automation library alone, for scripts and scraping without the runner.
- The library alone drives browsers without the test runner.
- It suits scraping and automation where no test structure is wanted.
- Using it for tests means writing fixtures and reporting yourself.
chromium / firefox / webkit
The three bundled engines. WebKit is the closest available stand-in for Safari.
- Three engines are bundled and driven through one API.
- WebKit is the closest available proxy for Safari behaviour.
- One API does not mean identical behaviour across the three.
browserType.launch
Starts a browser process, with headless, channel, args and slowMo.
- A browser process starts with options such as headless and slowMo.
- Launching is expensive, which is why contexts exist to reuse it.
- A launched browser must be closed or the process survives the run.
browserType.connect
Attaches to a remote browser server, for a grid or a container.
- A remote browser is driven over a websocket.
- Connecting is how a browser grid or container is shared.
- Latency per action rises, which changes how a suite feels.
browser.newContext
An isolated profile — its own cookies, storage and permissions. Cheap, unlike a new browser.
- An isolated profile is created with its own cookies and storage.
- Isolation per context is what makes parallel tests independent.
- A context is cheap; a browser launch is not.
browser.newPage
A tab within a context. Most tests need one page and one context.
- A page is opened, creating a context implicitly if needed.
- The implicit context is why state can leak when it is reused.
- Creating the context explicitly is what gives control over isolation.
BrowserContext
Where isolation lives: storageState, geolocation, permissions, viewport and offline mode.
- Cookies, storage, permissions and geolocation are scoped here.
- Storage state is loaded and saved at this level, not per page.
- Closing it closes every page it owns.
Page
The tab: navigation, locators, evaluation, screenshots and event handling.
- A single tab is navigated, queried and acted upon.
- Most of the API hangs off the page, which is the usual entry point.
- A page belongs to a context, so its state is the context's state.
Frame / FrameLocator
Addresses content inside an iframe, which locators otherwise cannot reach.
frameLocator targets elements inside an iframe and waits for the frame to appear.- It is strict by default and works with web-first assertions.
- An ordinary locator cannot cross a frame boundary at all.
Worker
Access to web workers and service workers running in the page.
- Web workers and service workers are observed and interacted with.
- A service worker can serve requests that route interception must account for.
- Workers outlive the page that registered them.
page.goto
Navigates, waiting for load by default. waitUntil changes the condition.
- A URL is loaded and the navigation awaited.
- The wait condition decides what counts as loaded.
- Waiting for full load is slower and often less reliable than waiting for content.
page.evaluate
Runs a function in the page and returns a serialisable result.
- JavaScript runs in the page and returns a serialisable value.
- Only serialisable data crosses the boundary, so a DOM node cannot come back.
- It performs no waiting, so the element must already exist.
page.exposeFunction
Installs a Node callback the page can call, for bridging back out.
- A Node function is callable from inside the page.
- It is how page code reports back into the test process.
- It stays bound for the page's lifetime, including after navigation.
page.addInitScript
Runs a script before any page script — used for stubbing Date, Math.random or feature flags.
- A script runs before any page script on every navigation.
- Running first is what allows a global to be stubbed before use.
- It runs on every navigation, so it must be idempotent.
Locators
page.locator
A lazy, auto-retrying query. It resolves at use time, so it survives re-rendering.
- A CSS or XPath selector becomes a lazy, retrying locator.
- Laziness is what lets it be declared before the element exists.
- A CSS selector breaks on restyling that changed nothing a user sees.
getByRole
The preferred query: matches the accessibility role and name, so it tests what users perceive.
- Elements are found by accessible role and name.
- It is the closest match to how assistive technology sees the page.
- If it cannot find the element, the accessibility tree is probably wrong.
getByText
Matches visible text, exact or substring.
- An element is matched by its visible text.
- Matching visible text keeps the test aligned with what users read.
- It matches substrings by default, which can catch more than intended.
getByLabel
Finds a form control by its associated label — the right way to reach inputs.
- A form control is found through its associated label.
- Going through the label is what proves the control is actually labelled.
- An unlabelled input is invisible to this locator, which is the point.
getByPlaceholder
Matches a placeholder, for inputs that have no real label.
- An input is matched by its placeholder text.
- It is a fallback where a real label is absent.
- Placeholder is not a label, so relying on it hides an accessibility gap.
getByAltText
Matches an image by its alternative text.
- An image is found by its alternative text.
- It asserts the image has alt text as a side effect of locating it.
- Decorative images have empty alt and cannot be found this way.
getByTitle
Matches the title attribute.
- An element is matched by its title attribute.
- It is useful for icons that carry no visible text.
- Title text is not reliably surfaced to users, so it is a weak anchor.
getByTestId
Matches an explicit test id. The escape hatch when no accessible name exists.
- An element is found by a dedicated test attribute.
- A test id is the most stable anchor because nothing else uses it.
- It says nothing about whether the element is usable or accessible.
locator.filter
Narrows a set by contained text or by a nested locator — how you pick one row out of a table.
- A locator is narrowed by text or by a locator it must contain.
- Filtering by content identifies a row without depending on its position.
- It is the documented answer to a strict mode violation on a list.
locator.and / locator.or
Combines locators for elements that must match both, or either, condition.
- Two locators combine by intersection or alternative.
- or is what handles a control whose markup differs between states.
- or resolving to two different elements still violates strict mode.
locator.nth / first / last
Selects by index. A sign the query could be more specific.
- One element is selected from several matches by position.
- Position is a last resort when nothing distinguishes them by content.
- An index reorders the moment the list does.
Strict mode
An action on a locator matching several elements fails rather than picking one. On by default, and correct.
- A locator resolving to more than one element throws a strict mode violation.
- The error lists the matching elements, so the fix is visible in the message.
- Frame locators are strict by default too.
Chaining
page.getByRole('row').getByRole('button') scopes a query inside another match.
- Locators compose to scope a search within another element.
- Scoping is what makes a locator unique without a brittle selector.
- Each link is resolved at use time, not when it was written.
Auto-waiting
Every action waits for the element to be attached, visible, stable and enabled first.
- Actions wait for the actionability checks: visible, stable, enabled, editable and receiving events.
- The docs state there are no artificial timeouts — waiting replaces them.
dispatchEvent performs no actionability checks at all, which is why it bypasses this.
Actions
click / dblclick
Clicks after the actionability checks, with modifiers, button and position options.
- A click is dispatched after the element becomes actionable.
- It waits for the element to stop moving, which manual clicks do not.
- Forcing a click skips those checks and hides a real problem.
fill
Sets an input's value in one step. Faster and more reliable than typing for most cases.
- An input's value is set and the input event dispatched.
- It clears first, so it replaces rather than appends.
- It does not produce per-key events, which some inputs rely on.
pressSequentially
Types character by character, for inputs with per-keystroke behaviour such as autocomplete.
- Text is typed one key at a time with real key events.
- Per-key events are what trigger autocomplete and masking logic.
- It is far slower than fill, so it is reserved for that need.
press
Sends a single key or chord, such as Control+Enter.
- A single key or chord is sent to the focused element.
- It is how Enter, Escape and shortcuts are exercised.
- The element must be focused, which press does not do by itself.
check / uncheck
Sets a checkbox or radio to a known state, asserting the result.
- The control is set to a known state rather than toggled.
- The action performs the actionability checks and then verifies the resulting state.
- Clicking instead depends on whatever the state was beforehand.
selectOption
Chooses from a native <select> by value, label or index.
- An option is chosen in a native select by value, label or index.
- It targets the native element, not a custom dropdown.
- A styled dropdown needs ordinary clicks instead.
setInputFiles
Attaches files to a file input, including clearing it with an empty array.
- Files are attached to a file input without a system dialog.
- Bypassing the dialog is what makes uploads testable.
- A custom uploader that hides the input needs the file chooser event.
hover
Moves the pointer over an element, for menus and tooltips.
- The pointer is moved over an element.
- Hover is what reveals menus and tooltips that only appear on it.
- Hover state is lost as soon as another action moves the pointer.
dragTo
Drags one element onto another, with manual mouse control available when HTML5 DnD misbehaves.
- One element is dragged onto another.
- It issues the pointer sequence a drag implementation expects.
- HTML5 drag and drop often needs manual events instead.
focus / blur
Moves focus explicitly, for testing keyboard and validation behaviour.
- Focus is moved to or away from an element.
- Blur is what triggers validation that runs on leaving a field.
- Focusing directly skips the tab order a keyboard user would follow.
scrollIntoViewIfNeeded
Scrolls to an element. Rarely needed, since actions scroll on their own.
- An element is scrolled into the viewport.
- Actions scroll automatically, so this is for assertions and screenshots.
- Virtualised lists may not render the element until it is scrolled.
page.keyboard / page.mouse
Low-level input when a locator action is not expressive enough.
- Low-level input is dispatched without a target element.
- It is how a drag, a chord or a canvas interaction is composed.
- There is no auto-waiting, so readiness must be established first.
Assertions
expect(locator).toBeVisible
Web-first assertion that retries until the element is visible or the timeout expires.
- The assertion retries until it passes or the assertion timeout is reached.
- Retrying assertions are async, so the docs require them to be awaited.
- On timeout the error reports the last observed value, not just a failure.
toHaveText / toContainText
Asserts text content, exact or partial, with array forms for lists.
toHaveText matches the whole text; toContainText matches a substring.- Both retry, so they wait for content to finish rendering.
- Against several elements they compare an array rather than a concatenation.
toHaveValue
Asserts an input's current value.
- An input's current value is asserted.
- Value is not text content, so the text assertions do not apply.
- It reads the live value, not the value attribute.
toBeEnabled / toBeDisabled
Asserts interactivity, which matters for buttons gated on form state.
- An element's interactive state is asserted.
- Disabled state is what a submit button test usually hinges on.
- An element styled to look disabled is still enabled.
toBeChecked
Asserts checkbox and radio state, including indeterminate.
- A checkbox or radio state is asserted.
- It reads the control's real state rather than a class.
- Indeterminate is a third state neither checked nor unchecked.
toHaveAttribute / toHaveClass
Asserts attributes and classes, with regular expressions allowed.
toHaveClass matches the full class property; toContainClass checks for specific classes.toHaveAttribute asserts a DOM attribute value.- Class assertions couple the test to styling detail rather than behaviour.
toHaveCount
Asserts how many elements a locator matches — the assertion for list rendering.
- The exact number of matching elements is asserted, with retries.
- Retrying is what lets it wait for a list to finish rendering.
- Zero is a valid count, which is how absence of a list is asserted.
expect(page).toHaveURL
Asserts the current URL, by string or pattern, after navigation.
- The page's URL is asserted, with retries for navigation.
- Retrying is what handles a redirect that has not settled.
- It compares the full URL unless a pattern is used.
expect(page).toHaveTitle
Asserts the document title, by string or pattern, once navigation settles.
- The document title is asserted.
- Titles often update after data loads, so retrying matters.
- It reads the live title, not the server-rendered one.
toHaveScreenshot
Visual comparison against a committed baseline, with masking and a pixel threshold.
- The rendered appearance is compared against a stored baseline.
- Masking volatile regions is what stops a clock failing every run.
- Baselines are platform-specific, so local images rarely match CI.
expect.poll
Retries an arbitrary expression, for state that is not in the DOM.
- An arbitrary function is polled until it returns the expected value.
- It brings retrying to values the web-first assertions do not cover.
- The polled function runs repeatedly, so it must have no side effects.
expect.soft
Records a failure and carries on, so one run reports every problem.
- A failed assertion records but does not stop the test.
- Several failures can be reported from one run.
- The test still fails, so a soft assertion is not an ignored one.
expect.configure
Produces an expect with its own timeout and soft setting.
- A reusable expect is created with its own timeout or softness.
- Configuring once avoids repeating options at every call.
- It returns a new expect rather than changing the global one.
Fixtures & hooks
test.use
Overrides options for a file or a describe block — viewport, locale, storage state.
- Options are overridden for a file or a describe block.
- Declaring the override keeps it visible rather than buried in a hook.
- It applies to the whole scope, not to a single test.
test.extend
Defines custom fixtures, which is how page objects and seeded data are shared.
base.extend adds test-scoped and worker-scoped fixtures.- The value before
use() is setup and everything after it is teardown. - A fixture no test requests is never created, so unused cost is zero.
Worker fixtures
{ scope: 'worker' } sets something up once per worker rather than per test.
{ scope: 'worker' } creates the fixture once per worker process.- Teardown runs when the worker shuts down rather than after each test.
- Every test in that worker shares it, so it must not hold per-test state.
Automatic fixtures
{ auto: true } applies a fixture without a test asking for it.
{ auto: true } applies a fixture without any test requesting it.- It is how a policy is enforced across an entire suite.
- It then runs for every test, including those that gain nothing from it.
test.beforeEach / afterEach
Per-test setup and teardown, with fixtures available in both.
- Setup and teardown run around each test.
- They run inside the test's own context, so nothing leaks onward.
- Work here is paid once per test, not once per file.
test.beforeAll / afterAll
Per-worker setup, which runs again for each worker, not once globally.
- Setup and teardown run once per worker for a file.
- It suits a cost that genuinely can be shared.
- State created here outlives the per-test contexts inside it.
test.step
Groups actions into a named step that appears in the report and the trace.
- A named phase groups actions in the report and the trace.
- Steps turn a long test into a readable narrative.
- A failing step names the phase, which shortens diagnosis.
Setup projects
A project other projects depend on — where authentication runs once and saves storageState.
- A project declared as a dependency runs before the projects that need it.
- It is the documented way to authenticate once for the whole run.
- Its failure stops dependent projects rather than letting them run unauthenticated.
globalSetup / globalTeardown
Runs once for the whole run, before any worker starts.
- Code runs once for the entire test run.
- It suits seeding a database or starting an external service.
- It runs outside any worker, so test fixtures are unavailable.
storageState
Saves and restores cookies and local storage, so tests start already signed in.
- Cookies and origin storage are saved to a file and reused by later contexts.
test.use({ storageState: { cookies: [], origins: [] } }) resets it for a signed-out test.- The official per-worker pattern keys the file by
test.info().parallelIndex.
Network & mocking
page.route
Intercepts matching requests to fulfil, abort or modify them.
- Matching requests are intercepted before they leave the browser.
- Interception is what makes loading and failure states deterministic.
- A route registered after navigation misses requests already sent.
route.fulfill
Responds from the test, with a body, status and headers of your choosing.
- A response is supplied without contacting the server.
- It is how an error or an empty state is produced on demand.
- A fabricated shape that the real API never returns tests nothing.
route.abort
Blocks a request — how third-party scripts and images are kept out of a run.
- A request is failed with a chosen error.
- Aborting is how offline and blocked-resource behaviour is tested.
- Aborting a required asset can break the page rather than the feature.
route.fetch / continue
Forwards the request, optionally with changes, and lets you edit the response.
- A request is forwarded, optionally modified, and its response used.
- It allows a real response to be inspected or patched.
- Continuing with altered headers can break server-side validation.
page.routeFromHAR
Replays a recorded HAR file, so a whole session can be served offline.
- Recorded traffic replays as the network layer.
- A HAR is a realistic fixture captured from a real session.
- It goes stale as the API changes, silently testing an old contract.
waitForResponse / waitForRequest
Waits for a specific network event, for flows with no visible signal.
- Execution waits for a specific network event.
- Waiting on the event ties the test to the cause rather than the clock.
- The wait must be set up before the action that triggers it.
request fixture
A standalone API client sharing the browser's cookies — used to seed state without the UI.
- HTTP calls are made outside the browser from the test.
- It is how state is arranged without driving the interface.
- It does not share the browser's cookies unless configured to.
APIRequestContext
Full HTTP testing on its own, so API and UI tests live in one suite.
- A reusable API client carries its own base URL and headers.
- It is the right tool for API-only tests.
- Its storage state is separate from any browser context.
page.on('console')
Surfaces console messages, so a test can fail on an unexpected page error.
- Console messages from the page are observed.
- Failing on a console error catches problems no assertion looked for.
- Third-party scripts produce noise that must be filtered.
Configuration
playwright.config.ts
Projects, reporters, timeouts, retries and the use defaults, all in one file.
- Projects, reporters, timeouts and the web server live in one file.
- One file is what makes the run reproducible outside a developer machine.
- Per-project options override the top level, which is easy to miss.
projects
Named configurations — one per browser, viewport or setup dependency — run in one command.
- Named configurations run the same tests differently.
- Projects are what make browsers, devices and setup declarative.
- Dependencies between projects control ordering.
webServer
Starts the application before the run and waits for it to answer.
- The application is started before the suite and stopped after.
- It removes the assumption that a server is already running.
- Reusing an existing server locally can test a stale build.
baseURL
Lets tests navigate with relative paths, so the environment is a config change.
- Relative paths resolve against a configured origin.
- It is what lets the same suite run against any environment.
- An absolute URL in a test ignores it entirely.
testMatch / testIgnore
Selects which files a project runs.
- Which files are collected as tests is controlled.
- It separates unit, integration and end-to-end runs in one repository.
- An over-broad pattern collects helper files as empty suites.
fullyParallel
Runs tests inside a file in parallel too, not just across files.
- Tests within a file run in parallel as well as across files.
- It is the largest single throughput gain available.
- Tests sharing mutable data start failing under it.
workers
How many parallel workers to use. CI usually wants fewer than a laptop.
- The number of parallel worker processes is set.
- Matching workers to cores is usually the right starting point.
- More workers than the application can serve produces timeouts.
retries
Reruns a failed test. Useful in CI, and a flakiness detector rather than a fix.
- A failing test is retried a set number of times.
- Retries separate transient failures from real ones in the report.
- Treating retries as a fix leaves the underlying race in place.
timeout / expect.timeout
The per-test and per-assertion budgets, tuned separately.
- Per-test and per-assertion time limits are configured.
- Separate limits let a slow assertion not raise the whole test budget.
- Raising the global timeout hides which step is actually slow.
reporter
list, dot, html, json, junit, blob, or several at once.
- Run output is formatted for humans or for CI.
- Several reporters can run at once for both audiences.
- A reporter that writes files needs its output preserved by CI.
Blob reports
Sharded runs emit blobs that merge-reports combines into one HTML report.
- Shard results are emitted for later merging.
- Merging is what produces one report from a sharded run.
- Without merging, each shard reports only its own slice.
Debugging & tracing
trace: 'on-first-retry'
Records a trace only when a test retries, which is the setting most suites want.
- Tracing records only when a test is retried.
- It captures evidence exactly when something went wrong.
- A test that fails without retries enabled produces no trace.
Trace Viewer
Replays a run with DOM snapshots, network, console and a timeline per action.
- Actions, snapshots, network and console are replayed from a trace.
- The DOM snapshot per step shows the page as the test saw it.
- It only exists if tracing was enabled for that run.
--debug / PWDEBUG
Opens the Inspector, stepping through actions with live locator editing.
- The run pauses with an inspector attached.
- Stepping through beats reasoning about a failure from logs.
- It disables timeouts, so behaviour differs from a normal run.
--ui
The UI mode: watch, time-travel and pick locators while tests run.
- An interactive mode watches, filters and re-runs tests.
- It is the fastest loop while writing tests.
- It is a development tool and is not used in CI.
codegen
Records interactions into a script. A starting point, not a finished test.
- Interactions in a browser are recorded as test code.
- It is the quickest way to discover the right locators.
- Generated code needs assertions and structure added by hand.
page.pause
Stops execution and opens the Inspector at that exact point.
- Execution stops and hands control to the inspector.
- Pausing at a chosen point beats stepping from the beginning.
- A pause left in the code hangs the run in CI.
screenshot / video
Captures on failure, attached to the HTML report.
- Images and recordings are captured during a run.
- They are what make a CI-only failure explicable.
- Capturing always is expensive, so it is usually on failure.
--last-failed
Reruns only what failed in the previous run.
- Only the tests that failed previously are re-run.
- It shortens the loop when fixing a handful of failures.
- It depends on the previous run's results being present.
--repeat-each
Runs a test many times to prove whether it is genuinely flaky.
- Each test runs several times in one invocation.
- Repetition is how a suspected flake is confirmed.
- A test passing ten times is not proof it cannot fail.
Advanced automation
Component testing
Mounts React, Vue or Svelte components in a real browser under the same runner.
- A component mounts in a real browser and is asserted directly.
- Real styles and layout apply, unlike a virtual renderer.
- It bypasses routing and data loading, which stay untested here.
Visual comparisons
Baseline screenshots per project and platform, with --update-snapshots to accept changes.
- Rendered output is diffed against approved images.
- It catches regressions no functional assertion describes.
- Fonts and rendering differ per platform, which produces false diffs.
Accessibility scanning
@axe-core/playwright runs an audit inside an existing test.
- Automated accessibility checks run against a page.
- It catches a real subset of issues very cheaply.
- Most accessibility problems are not machine-detectable.
Emulation
Device descriptors, locale, timezone, colour scheme, reduced motion and geolocation.
- Viewport, device, locale, timezone and colour scheme are simulated.
- Emulation is what tests responsive and localised behaviour.
- It emulates the environment, not the device's real performance.
Permissions
Grants or denies camera, clipboard, notifications and geolocation per context.
- Browser permissions are granted or denied for a context.
- Granting up front avoids a dialog the test cannot click.
- Permissions are per context, so a new context resets them.
Clock API
Controls the page's clock so timers, polling and expiry can be tested deterministically.
- Time is controlled so timers and dates are deterministic.
- Controlling time is what makes a timeout testable in milliseconds.
- Installing it affects every timer, including the framework's own.
Sharding
--shard=1/4 splits a suite across CI machines, merged afterwards into one report.
- A suite is split across machines by index.
- Sharding is how a long suite fits a CI time budget.
- Shards divide by count, so slow tests can land unevenly.
Authentication reuse
Signing in once in a setup project and reusing the state is the single biggest speed win.
- Signing in once and reusing the saved state removes the slowest step from every test.
- The docs authenticate in a clean context by passing
storageState: undefined. - Waiting for the final URL matters, because a login can set cookies across redirects.
Page object model
Encapsulates locators and flows in classes, exposed to tests as fixtures.
- Locators and actions for a screen are grouped in a class.
- It keeps a selector change confined to one file.
- Over-abstracting hides what a test actually does.
Docker image
The official image ships the browsers and system dependencies, so CI matches local runs.
- An official image carries the browsers and their dependencies.
- It is what makes local and CI runs match.
- The image version must track the library version.
playwright install
Downloads browser binaries; --with-deps also installs the OS packages they need.
- Browser binaries are downloaded separately from the npm package.
- Keeping them out of the package is why the install step exists at all.
- The
--with-deps flag also installs the system libraries a CI image lacks.