QAAll levels

Playwright

Playwright is a browser automation library and test runner that controls Chromium, Firefox and WebKit through one API. Almost everything people like about it — that tests wait for the page instead of racing it, that a failure ships with a replayable trace, that fifty cases run in parallel on a laptop — is a consequence of one architectural decision rather than a list of features. This page explains that decision, what it buys, and where the tool genuinely does not help.

Test runs
Outside the browser
Isolation unit
Browser context
Waits by
Actionability checks

The architecture is the feature list

Your test runs in its own process. It opens one persistent connection to a browser it launched, and every instruction and every event travels over that connection. That is the whole design, and it is worth understanding before any API, because each of the tool’s well-known properties turns out to be a consequence of it rather than a separate piece of engineering.

Playwright runs your test in its own process and talks to the browser over a single persistent connection. Every property the tool is known for is a consequence of that one arrangement. Because the driver sees browser state directly, it can check that an element is visible, stable and able to receive the click before performing an action, which removes the largest single cause of flaky browser tests. Because everything crosses one wire, it can record a trace of the run — DOM snapshots, network traffic, console output and video — that a developer opens afterwards and steps through frame by frame instead of trying to reproduce the failure. And because the unit of isolation is a browser context rather than a browser process, a test gets clean cookies and storage in milliseconds, which is what makes running dozens of cases in parallel practical. The same protocol layer is implemented for Chromium, Firefox and WebKit, so one test file runs against all three engines.

Because the driver sees browser state directly rather than through the page, it can inspect an element before acting on it and wait until acting makes sense. Because everything crosses one wire, the whole run can be recorded and replayed afterwards. And because the isolation unit is a browser context — a private cookie jar and storage inside an already-running browser — a clean session costs milliseconds instead of a browser launch, which is what makes real parallelism affordable.

Auto-waiting, and why it removes most flakiness

The single largest cause of unreliable browser tests is a test acting before the application is ready: the button exists in the DOM but is still covered by a loading overlay, or it is mid-animation, or React has rendered it but not yet attached the handler. Older tools answered this with sleeps and manual waits, which is a guess dressed up as code. Playwright answers it by checking, before every action, that the element is in a state where acting makes sense.

Check before an actionWhat it prevents
The element is attached to the DOMActing on a node that a re-render has already replaced — the classic stale element failure.
It is visibleClicking something with zero size or hidden behind a collapsed panel, which passes silently and does nothing.
It is stable — not moving between framesClicking during an animation and landing where the button was rather than where it is.
It receives events — nothing covers itClicking a modal overlay instead of the button under it, then failing three steps later for a mysterious reason.
It is enabledSubmitting a form while the button is still disabled by validation that has not finished.

The second half of the model is the locator. A locator is not an element — it is a description of how to find one, re-resolved every time it is used. That distinction is what makes the checks above possible on a page that re-renders underneath you: the same locator re-finds the current element rather than holding a reference to a node that no longer exists. Assertions work the same way, retrying until the condition holds or the timeout expires, which is why an assertion about text that arrives after a network response needs no wait around it.

Isolation, fixtures and getting a suite to finish in minutes

Every test gets a fresh browser context by default, so there is no shared cookie, no leftover local storage and no order dependency between cases. This is the quiet reason suites written here stay stable as they grow: the most common source of "it passes alone but fails in the suite" is removed by the default rather than by discipline.

  • Sign in once, reuse everywhere

    Authenticate in a setup step, save the storage state to a file, and have every test start already signed in. Removes the slowest and most repeated five seconds in the suite.

  • Fixtures instead of setup code

    A fixture is a named thing a test asks for — a signed-in page, a seeded order, an API client. It is built on demand and torn down after, so tests declare what they need instead of arranging it.

  • Workers, not threads

    Files run in parallel across worker processes, each with its own browser. The practical limit is usually your test data, not your CPU.

  • Sharding across machines

    The same suite splits across CI machines by index, and the reports merge afterwards. This is how a forty-minute suite becomes an eight-minute one without deleting anything.

Two capabilities beyond the browser are worth knowing because they change what belongs in the suite at all. Playwright can make API requests directly, in the same test and sharing the same session, so a case can create its fixture data over the API in fifty milliseconds and then check one thing in the UI, rather than clicking through four screens to arrive at the state it wanted. And it can intercept network traffic, so a third party that is slow, rate-limited or unavailable in CI can be replaced at the boundary without touching application code.

What to turn on so a CI failure is diagnosable

The expensive part of an end-to-end failure is never the failure — it is the hour spent trying to reproduce it on a laptop where it does not happen. The trace is the answer to that, and it is the one setting worth getting right before anything else.

  • Record a trace on first retry in CI. It captures a DOM snapshot before and after every action, the network log, the console and a video, and opens as an interactive timeline you step through locally.
  • Keep retries at one, not three. One retry plus a trace tells you whether the failure is real; three retries tell you nothing and turn a genuine intermittent bug into a green run.
  • Use the watch-and-inspect mode while writing tests, where the run pauses, the locator under the cursor is highlighted, and you can try a different one against the live page.
  • Treat generated code as a draft. Recording a flow produces working selectors fast, and they are almost always more brittle than the ones you would choose deliberately.

Where it does not help, and how it compares

Playwright is the reasonable default for new browser test suites in 2026, and defaults deserve their caveats stated plainly rather than discovered in month three.

  • WebKit is not Safari. It is the same engine, without Apple’s browser around it, so it catches rendering and API differences but not everything specific to the shipped Safari build.
  • Mobile emulation is viewport, user agent and touch — not a real device. It finds layout problems and will not find a bug in a real mobile browser or in a native app.
  • It does not test native desktop or mobile applications, and it is not a load testing tool, however tempting a hundred parallel contexts look.
  • It makes flaky tests less likely; it does not make a badly chosen suite good. Four hundred end-to-end cases are wrong here for exactly the same reasons they are wrong anywhere else.

Choose Playwright when

  • You are starting a suite now and have no constraint pushing you elsewhere.
  • You need several isolated users, several origins or several tabs inside one test.
  • Suite runtime matters, because parallelism per context is cheap and sharding is built in.
  • You want failures in CI to be diagnosable without reproducing them.

Look elsewhere when

  • Your team writes tests in Java, C# or Ruby and wants first-class support — Selenium covers that ground.
  • You must run against the real shipped Safari, Edge and older browsers on a device grid.
  • The team already has a large working Cypress suite — the migration cost usually exceeds the gain.
  • The thing you need to test is a native application, a desktop client or system-level behaviour.

Anti-patterns worth naming

  • Adding explicit waits "just in case" on top of auto-waiting, which slows the suite and hides the real timing question.
  • Selecting by CSS class or DOM position because the recorder generated it, which turns every redesign into a day of test repair.
  • Reusing one context across tests to save time, which reintroduces the order dependency the tool was designed to remove.
  • Raising retries until the pipeline is green, which converts real intermittent defects into passing runs.
  • Moving unit-level logic into browser tests because the tooling is pleasant, which is the most expensive way to be thorough.

When to use it

Use it when

  • A new browser suite where reliability and runtime both matter and no language constraint applies.
  • Tests involving more than one user, more than one origin or more than one tab in a single case.
  • Suites that must run on every merge, using workers locally and sharding across CI machines.
  • Wherever cross-engine coverage matters and Chromium, Firefox and WebKit should run from the same test file.

Avoid it when

  • As proof that a page works in the real shipped Safari or in a real mobile browser — emulation does not cover that.
  • For native desktop or mobile applications, or as a load testing tool.
  • As a reason to grow the end-to-end suite, since a pleasant tool does not change what belongs at that level.
  • As a migration target for a large working suite in another tool, where the cost usually exceeds the benefit.

Found this useful?

Share it with someone who is working on the same problem.