One architectural fact predicts everything
Three tools. Three ways to drive a browser.
Selenium, Cypress, and Playwright each talk to the browser differently. Read the process boundary in each lane. Selenium and Playwright sit outside the browser and drive it over a wire protocol. Cypress lives inside the browser. That one difference forecasts real Safari, any language, auto-wait, and grid scale before you read another word.
Selenium vs Cypress vs Playwright, chosen by how each drives the browser
There is no universally best browser automation tool. There is a best fit for how your team works, and the fastest way to find it is to look at how each tool physically drives the browser. This guide makes that one fact the spine, then derives every trade-off from it, and hands you a vendor-neutral picker that will tell a solo JS team to skip the grid.
This is tool selection, not test strategy#
First, a boundary the ranking pages never draw. This guide answers which tool to adopt. It does not answer what to test, how deep your pyramid should sit, or which flows deserve coverage. Those are test-strategy questions, and they matter more than the tool. However, they are a separate decision, and blurring the two is why most comparison pages leave you no clean answer.
The one fact behind Selenium vs Cypress vs Playwright: how each drives the browser#
Here is the spine of the whole comparison. Each tool talks to the browser in one specific way, and that one choice sets every downstream trade-off. Two of the three sit outside the browser and drive it over a wire protocol. One lives inside the browser. Because that boundary is fixed by design, you can forecast a tool's answer to a brand-new question from the drive mechanism alone.
Notice what the boundary predicts. Because Selenium and Playwright run in a separate process, they can attach to any real browser and, for Selenium, speak from any language. Because Cypress runs in the page, it sees the application from the inside, yet it inherits the browser's same-origin and single-tab rules. That single distinction drives the three sections below.
Selenium: out-of-process over the W3C WebDriver protocol, and BiDi in version 4#
Selenium sends commands from your test process to a browser driver over the W3C WebDriver protocol. The driver then controls a real browser. Because the wire protocol is a public standard, any language with a client can drive it, and any real browser with a driver can be a target. That is where Selenium's two signature strengths come from directly.
First, the language reach. Python, Java, C#, JavaScript, and Ruby all speak WebDriver, so the tool never dictates your stack. Second, the browser matrix. A remote Grid runs Chrome, Firefox, Edge, and real Safari as nodes, so the matrix is the widest of the three. The honest cost is the flip side of the same fact. Because the driver acts on its own clock, WebDriver does not auto-wait, so you add explicit waits. Selenium 4 adds WebDriver BiDi to narrow that gap, which we return to later.
Cypress: a resident inside the browser event loop#
Cypress does not drive the browser from outside. Instead, your spec code runs inside the browser event loop, next to the application. Because the test and the app share one loop, Cypress sees every command, network call, and DOM change from the inside. That is exactly why its time-travel debugging and automatic waiting are best in class. It is watching the app from within, not guessing from across a wire.
The limits come from the same residency, so they are structural, not bugs. Because the runner lives in one browser context, it is JavaScript and TypeScript only. Because it lives in one origin by default, a cross-origin step needs cy.origin. Because it lives in one tab, it cannot drive two browsers at once, and a second tab needs a plugin. None of that is fixable with a flag, because it is the architecture.
Playwright: out-of-process over CDP and patched browser builds#
Playwright also sits outside the browser, but it speaks a different protocol. It drives patched builds of Chromium, Firefox, and WebKit out-of-process, mostly over the Chrome DevTools Protocol. Because it ships and controls its own browser builds, it can wait on the right signals automatically and intercept network traffic cleanly. Therefore auto-waiting and request control are built in, not bolted on.
The out-of-process design also unlocks scale and language reach. Because the runner is separate from the browser, Playwright shards runs across machines with a first-class parallelism model, and it offers official clients in JavaScript, Python, Java, and C#. The honest caveat is age. Playwright is the newest of the three and moves fast, so expect more churn between releases than the older tools carry.
From drive mechanism to the trade-off you feel in CI#
Now read the spine as a table. Each row starts from the architectural fact and ends at the consequence you actually feel in a pipeline. Because the cause sits next to the effect, you can extend the logic to a question the table does not list. That is the whole point of leading with architecture.
| Question | Selenium | Cypress | Playwright |
|---|---|---|---|
| Drive mechanism | SeleniumOut-of-process over WebDriver and BiDi | CypressIn-process, inside the event loop | PlaywrightOut-of-process over CDP and patched builds |
| Any language? | SeleniumYes: Python, Java, C#, JS, Ruby | CypressNo: JS or TS only | PlaywrightYes: JS, Python, Java, C# |
| Real Safari? | SeleniumYes: safaridriver plus a Grid node | CypressNo: WebKit engine, experimental | PlaywrightNo: WebKit engine, not a device |
| Multiple tabs or windows? | SeleniumYes, via the driver | CypressNo, one tab by design | PlaywrightYes, contexts and pages |
| Cross-origin in one test? | SeleniumYes | CypressOnly via cy.origin | PlaywrightYes |
| Auto-wait by default? | SeleniumNo, explicit waits (BiDi narrows it) | CypressYes, built in | PlaywrightYes, built in |
| Parallel across machines? | SeleniumYes, you provision the Grid | CypressYes, via its own model | PlaywrightYes, first-class sharding |
| Ecosystem maturity | SeleniumWidest and most portable | CypressFocused, JS-native | PlaywrightNewer, fast-moving |
Try the table on an unlisted question. Suppose you ask whether a tool can hit a login popup on a second domain mid-test. For Cypress the answer follows from residency, because one tab and one origin means no. For Selenium and Playwright the answer follows from running outside the browser, because a separate process can open another context. You did not need a feature list. You needed the drive mechanism.
A worked example: certify one app on three engines for a 600-test suite#
Abstract claims convince nobody, so here is where Selenium vs Cypress vs Playwright stops being theory. Certify a single web app on three rendering engines, which are Chromium, Firefox, and WebKit. The suite holds 600 tests. Across three engines that is 600 by 3, which is 1,800 executions. Watch how each tool declares that matrix, because the difference is the architecture made visible.
// playwright.config.ts — one config, three engines, one shard flag
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests', // the ~600-test suite
fullyParallel: true, // parallel worker processes per machine
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
// npx playwright install # pulls all three engines
// npx playwright test --shard=1/3 # 600 x 3 = 1,800 execs, split 3 ways // cypress.config.ts — Chrome and Firefox are real; WebKit is experimental
import { defineConfig } from 'cypress';
export default defineConfig({
// Turns on the WebKit engine. This is NOT real Safari on a device.
experimentalWebKitSupport: true,
e2e: {
baseUrl: 'https://app.example.test',
// A cross-origin step in those 600 tests needs cy.origin().
// Cypress cannot drive two browsers at once, and a second tab
// needs the @cypress/puppeteer plugin.
},
});
// cypress run --browser chrome
// cypress run --browser firefox
// cypress run --browser webkit # experimental engine, JS or TS only # grid_matrix.py — the only path to genuine remote Safari, in any language
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# One remote Grid node per engine: Chrome, Firefox, Edge, and real Safari.
GRID = "http://grid.example.test:4444/wd/hub"
for browser in ("chrome", "firefox", "safari"): # safari via safaridriver
Opts = {"chrome": webdriver.ChromeOptions,
"firefox": webdriver.FirefoxOptions,
"safari": webdriver.SafariOptions}[browser]
driver = webdriver.Remote(command_executor=GRID, options=Opts())
# WebDriver does not auto-wait, so you add explicit waits.
# BiDi in Selenium 4 narrows this, but is not the zero-config default.
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable(("id", "checkout")))
driver.quit() Playwright 1.61.1: one config, three projects, one shard flag#
Playwright declares the whole matrix in one place. One config lists three projects, and a single npx playwright install pulls all three engines. The 1,800 executions then split across three machines with --shard=1/3, --shard=2/3, and --shard=3/3. That is roughly 600 executions per machine, and each machine runs parallel workers on top. One language, one command, the whole matrix.
Cypress 15.18.1: Chrome and Firefox, with WebKit still experimental#
Cypress reaches part of the same goal, and it is honest about the gap. Chrome-family and Firefox are fully supported. However, the third engine is WebKit, and Cypress still flags WebKit support as experimental. That engine is not real Safari on a device either. So the three-engine target is only partly reachable here.
Selenium 4.46.0: the only path to genuine remote Safari#
Selenium is the one tool that reaches real Safari for this matrix. A remote Grid runs Chrome, Firefox, Edge, and Safari nodes, and safaridriver drives an actual Safari, in any language you like. The cost is equally plain. You provision and wire the Grid yourself, and you add explicit waits. WebDriver BiDi in version 4 narrows the wait gap, yet it is not the zero-config default the other two ship.
Read the three panels as one takeaway. The same 1,800-execution matrix is one install and one shard flag in Playwright, a partial and experimental third engine in Cypress, and the only route to genuine remote Safari in Selenium. In short, the drive mechanism wrote all three outcomes before you typed a line.
The six axes that actually decide Selenium vs Cypress vs Playwright#
Set your real constraints and the instrument returns a reach-for-X verdict, with the architectural reason attached. It is deliberately neutral. Because no testing-cloud grid is selling you anything here, a solo JavaScript team on modest CI is told to reach for Cypress and skip the grid entirely. Move the six controls and watch the verdict and the scores change.
The two hard rules are what keep it honest. First, must-have real Safari on a device points to Selenium every time, because out-of-process WebDriver is the only path to it. Second, a polyglot team removes Cypress from the running, because a resident in the browser event loop is JavaScript and TypeScript by construction. Every other axis then nudges the score.
Required browser and device matrix#
This axis forecloses the choice more often than any other. Because real Safari on a device needs safaridriver and a Grid, that requirement points straight to Selenium. Chromium-only work, in contrast, frees you to pick on developer experience instead, since all three cover it well.
Team language#
Language is decided by the drive mechanism, not by preference. Because Cypress runs inside the browser event loop, it is JavaScript and TypeScript only. Selenium and Playwright both run out-of-process, so both are polyglot. A Python or Java team is therefore choosing between those two before any other axis is weighed.
CI parallelism and scale#
Scale rewards the tools built to run outside the browser. Because Playwright shards runs across machines by design, heavy parallel matrices are its home turf. Selenium scales too, yet you provision and maintain the Grid that makes it happen. Cypress parallelizes through its own model, which suits modest suites well.
Flakiness tolerance and auto-wait#
Auto-waiting tracks where the runner sits. Because Cypress and Playwright can watch the app closely, they auto-wait by default, which cuts a common source of flakiness. Selenium historically leaned on explicit waits. However, WebDriver BiDi now streams events that close much of that gap, so the old critique is dated.
Developer experience#
Developer experience is where residency pays off most. Because Cypress lives in the page, its time-travel debugger replays each step with the DOM snapshot attached. Playwright answers with a strong trace viewer and codegen. Selenium is the most manual of the three, which is the trade for its reach.
Ecosystem and lock-in#
Finally, weigh maturity against momentum. Because Selenium is the oldest and standard-based, its ecosystem is the widest and the most portable across languages and vendors. Playwright carries the momentum and ships features fast. Cypress sits between them, focused and JavaScript-native. Pick the trade you can live with for years.
2026 reality check#
Much of the folklore around Selenium vs Cypress vs Playwright is stale, so here is the current state on three points that change the decision. Some limitations get repeated long after they stop being true.
Reach for X when, including the anti-upsell case#
The picker gives a verdict per scenario. These blocks settle Selenium vs Cypress vs Playwright as plain prose instead, and each one names the real weakness, not just the strength. That is the honesty every grid-owned ranking page skips.
Reach for Selenium when you need real Safari, any language, or a mature grid#
Reach for Selenium when the browser matrix or the team language forecloses the others. Because it drives out-of-process over WebDriver, it reaches real Safari through safaridriver and speaks from any language. The weakness is setup. You provision the Grid and add explicit waits, and BiDi only narrows that, so budget for the wiring before you commit.
Reach for Cypress when a JS team wants great DX and never needs Safari or multi-tab#
This is the anti-upsell case, stated plainly. A JavaScript or TypeScript team, on a single-origin app, running modest CI, should reach for Cypress and skip the grid entirely. Because it lives in the event loop, the debugging is superb and the waits are automatic. The weakness is the same residency. No real Safari, one origin by default, and one tab, so if you need any of those, choose elsewhere.
Reach for Playwright when you need parallelism, many languages, and modern auto-wait#
Reach for Playwright when scale and breadth lead the decision. Because it drives patched builds out-of-process over CDP, it shards cleanly across machines, auto-waits by default, and runs from several languages. The weakness is youth. It is the newest tool and it moves fast, so pin versions and expect churn between releases you would not see in Selenium.
When the tool choice is NOT the decision#
Sometimes the tool is not your problem, and swapping it will not help. Before you migrate a suite, rule out the two cases where the driver is a distraction from the real fix.
So keep the order straight. Get the strategy and the test design right first. Then, and only then, use the drive mechanism to pick the runner. In short, Selenium vs Cypress vs Playwright is a real decision, but it is downstream of knowing what you are testing and why.
Weighing a runner for a real suite, or trying to turn a flaky matrix back into a fast one? This pairs naturally with accessibility testing inside your automated suite, and the Atyantik team is happy to talk it through. No pressure and no lock-in.
See Atyantik's approach to scaling and optimizing existing software