Playwright
Playwright is an end-to-end testing framework for web applications. It drives a real browser, auto-waits for elements to be actionable, and runs the same test across Chromium, Firefox, and WebKit. It is fast, reliable, and the closest thing to a default choice for new browser-test suites in 2026.
In one paragraph
A Playwright test opens a browser, navigates to a URL, interacts with the page the way a user would, and asserts on the result. Tests run in parallel by default across Chromium, Firefox, and WebKit. The framework auto-waits for elements to be visible and actionable before firing events, which removes most of the explicit timing logic that older browser-test frameworks needed. The trade-off is that "actionable" depends on browser state, and components that re-render mid-action are a common source of flakes. See the flaky tests in Playwright guide for the patterns and fixes.
What Playwright actually is
Playwright is a Node.js library, a test runner, and a set of browser binaries packaged together. Microsoft maintains it. The first release shipped in 2020 and the project has moved quickly since then, picking up first-class TypeScript support, a trace viewer, codegen, component testing, and a wide enough plugin surface that it is now a realistic default for new projects.
A Playwright test, in TypeScript, looks like this:
import { test, expect } from "@playwright/test";
test("user can sign up", async ({ page }) => {
await page.goto("https://example.com/signup");
await page.getByLabel("Email").fill("test@example.com");
await page.getByLabel("Password").fill("hunter2");
await page.getByRole("button", { name: "Sign up" }).click();
await expect(page.getByText("Welcome")).toBeVisible();
});
That is the whole shape of it. No explicit waits, no sleep, no manual element polling. page.getByRole finds the button using accessibility tree information, which is more stable than CSS selectors. click() waits for the button to be actionable. expect(...).toBeVisible() retries until the assertion passes or the test times out. The framework absorbs the timing concerns that older browser-test frameworks made you write yourself.
Auto-wait, the thing that makes Playwright tests short
The single defining feature of Playwright is its actionability check. Before any user-style interaction (click, fill, hover, drag), Playwright waits for the target element to be:
- Attached to the DOM
- Visible (non-zero size, not
display: none, notvisibility: hidden) - Stable (no animation in flight)
- Enabled (not disabled by the form element's
disabledattribute) - Receiving pointer events (no other element on top)
If any of those is false, Playwright waits and retries until it is true or the action times out (default 30 seconds). This is why a clean Playwright test does not contain waitFor or sleep calls. The framework is doing that work for you.
The flip side: actionability is computed against a moving browser. If a component re-renders between the check and the action, the click can land on a stale element and the test fails with "element is not attached." This is one of the most common sources of Playwright flakes, and the fix is usually to anchor the locator on a stable element (a data-testid on the wrapping container) instead of on a label that gets swapped during state changes.
Playwright vs Cypress vs Selenium
The three names that come up in most evaluations.
| Framework | Architecture | Browsers | Languages | Best for |
|---|---|---|---|---|
| Playwright | Out-of-process. Talks to the browser via CDP / WebKit / Firefox automation protocols. | Chromium, Firefox, WebKit | TypeScript, JavaScript, Python, .NET, Java | New projects, multi-browser coverage, modern SPA testing |
| Cypress | In-process. Runs inside the browser, same JS context as the page. | Chromium-based, Firefox (experimental WebKit) | JavaScript, TypeScript | JavaScript-only teams, strong local DX preference |
| Selenium | Out-of-process via the W3C WebDriver protocol. | Chromium, Firefox, Safari, Edge | Java, Python, C#, Ruby, JavaScript, Kotlin | Existing Selenium suites, enterprise environments with grid infrastructure |
For greenfield projects the practical answer is usually Playwright. It is fast, multi-browser, multi-language, and the actionability model takes a class of bugs off the table that Selenium and (to a lesser extent) Cypress make you handle manually. Cypress remains popular when teams want the live test runner and have no need for non-Chromium coverage. Selenium remains relevant where existing infrastructure (grid setups, vendor lockin, regulated environments) is hard to displace.
Playwright in CI
Playwright is built for CI. The official Docker image (mcr.microsoft.com/playwright) ships with the supported browsers preinstalled, which removes the "install Chromium in CI" step that broke half of Selenium setups. The test runner has native support for sharding, JUnit reporters, and trace recording on failure. A typical GitHub Actions workflow is fewer than 20 lines.
Sharding across runners
Browser tests are slow. The standard pattern for keeping them under a CI budget is sharding: split the test set across N runners, run them in parallel, merge the results at the end. Playwright supports this natively with --shard, and the test runner produces a combined report that hides the split from whoever is reading the results.
Traces, screenshots, video on failure
When a test fails in CI, you usually do not have the option to rerun it locally to see what went wrong. Playwright records a trace by default on failure, which packages the network log, the DOM at each step, screenshots, and a timeline into a single zip. Open it in npx playwright show-trace and you have most of what a real debugger would give you, after the fact.
Where Playwright belongs in the pipeline
Playwright tests are slow and expensive to run. Putting them on every pull request push works for small suites and stops working around the 200-test mark. The pattern that scales is two-step CI: fast unit tests on every push, browser tests in a second stage that runs only when a PR is queued to merge. That way every merge is gated on a green browser suite, but engineers iterating on a PR are not paying for it on every push.
Why Playwright suites go flaky anyway
Auto-waiting removes a category of flakes (the explicit-sleep race). It does not remove flakiness itself. A Playwright suite that grows past a few hundred tests will hit a predictable set of patterns:
- Auto-wait racing a re-render. The element was actionable when the check ran, but the component re-rendered before the click landed.
page.route()registered afterpage.goto(). The initial request fires before the mock is in place.networkidlethat never settles. Service workers and websockets keep the network busy, so the test times out waiting.- Storage state leaking across tests. Logged-in fixtures share cookies and the order of execution starts to matter.
- Headed vs headless drift. The test passes when you run it locally with a UI and fails in CI's headless mode, usually because of viewport or font rendering differences.
Each of these has a known fix. Detecting them, naming them, and quarantining the test until the fix lands is what turns "the suite is flaky" from a permanent state into a finite list of tickets. See the Playwright flaky tests guide for the full pattern catalog and code-level fixes.
FAQ
What is Playwright?
Playwright is an end-to-end testing framework for web applications, maintained by Microsoft. A Playwright test drives a real browser (Chromium, Firefox, or WebKit), interacts with the page like a user would, and asserts on the result. It is the modern successor to Selenium and the leading alternative to Cypress.
What language is Playwright written in?
Playwright itself is written in TypeScript and Node.js, but the test runner has first-party support for TypeScript, JavaScript, Python, .NET, and Java. The API is intentionally identical across languages, so a test written in Python looks like the same test written in TypeScript with different syntax.
Playwright vs Cypress, which one should I use?
Playwright runs tests in real browser processes (Chromium, Firefox, WebKit) using the browser's own automation protocols. Cypress runs tests inside the browser, in the same JavaScript context as the page under test. Playwright is faster on the same machine, supports more browsers, and handles multi-tab and cross-origin flows natively. Cypress has a more polished local debugging experience and a larger plugin ecosystem. For new projects, most teams now pick Playwright.
Playwright vs Selenium, why migrate?
Selenium predates Playwright by about a decade and shows it. Playwright is faster, more reliable, and gives you better diagnostics. The biggest practical difference is auto-waiting: Playwright waits for elements to be actionable before firing events, which removes most of the sleep() and waitFor() calls that bloat Selenium suites. Selenium is still relevant for grid-style infrastructure and legacy enterprise environments.
What is auto-wait in Playwright?
Auto-wait is Playwright's name for actionability checks. Before clicking, typing, or asserting on an element, Playwright waits for the element to be attached, visible, stable, and able to receive events. This is what removes most of the explicit timing logic from a Playwright test. It is also a common source of flakes when components re-render between the check and the action.
Can Playwright test mobile?
Playwright can emulate mobile viewports and user agents from any of its supported browsers, which covers most mobile web testing needs. It does not drive native iOS or Android applications. For native app automation you still need Appium or a vendor SDK.
Do Playwright tests run in parallel?
Yes, by default. The test runner shards your test files across worker processes and runs them concurrently. Inside a single file, tests run sequentially unless you opt into file-level parallelism. The trick to scaling Playwright in CI is making sure that parallelism does not introduce shared state across tests (storage, cookies, fixtures), which is the most common source of flakes.
Is Playwright good for CI?
It is built for CI. Playwright Test ships with first-class support for sharding across runners, JUnit-compatible output, trace recording on failure, screenshot and video capture, and a containerized runtime image with the supported browsers preinstalled. The hard part is not running Playwright in CI, it is keeping the suite fast and flake-free as it grows.
Playwright catches the bug. Test Insights tells you why the same test failed twice last week.
Mergify Test Insights detects flaky Playwright tests, surfaces the failure pattern, and quarantines the test out of the required check until the fix lands. Auto-wait closes one door. Test Insights closes the rest.