Quick Answer

Selenium is the oldest and most portable - it automates any major browser from Java, Python, C#, Ruby, JavaScript, and more, via the W3C WebDriver protocol. Cypress runs your test code inside the browser alongside your app, which makes debugging pleasant but limits it to what a page can do. Playwright drives Chromium, Firefox, and WebKit from a Node process using each browser's automation protocol, with fast auto-waiting and strong parallelism. For a new JavaScript or TypeScript project, Playwright is the usual pick.

Three different architectures

The tools differ most in where the test code runs relative to the browser, and almost everything else follows from that.

Selenium speaks the W3C WebDriver protocol to a browser-specific driver (chromedriver, geckodriver) which controls a real, unmodified browser. Your test is an ordinary program in whatever language you like, sending commands over HTTP to the driver. Maximum portability, but each command is a round-trip and the driver layer adds latency and version-matching chores.

Cypress loads your test code into the browser itself and runs it in the same event loop as your application. It can reach into your app's internals, stub network requests at the fetch layer, and give you time-travel debugging. The cost: it is fundamentally a browser page, so multi-tab, multi-origin, and multi-browser scenarios are awkward or unsupported, and it is JavaScript-only.

Playwright runs your test in a Node process and drives the browser from outside using the browser's own automation protocol (Chrome DevTools Protocol and equivalents). It bundles patched builds of Chromium, Firefox, and WebKit. This gives real cross-browser coverage, true parallel isolation, and low-latency control, without running inside the page.

Browser and platform support

Selenium covers the widest matrix: Chrome, Firefox, Edge, Safari, and historically Internet Explorer, on desktop, plus real mobile browsers through Appium. If you must test Safari on actual macOS or a specific legacy browser, Selenium is often the only option.

Playwright tests Chromium, Firefox, and WebKit. WebKit is the engine behind Safari, so Playwright gives you a close approximation of Safari behavior on Linux CI - but it is Playwright's WebKit build, not Safari itself, so a small set of Safari-specific quirks will not show up.

Cypress runs in Chrome-family browsers, Firefox, and Electron. It added WebKit support as an experimental option. In practice most teams run Cypress against Chrome.

For a public site where Safari share matters and you need certainty, Selenium against real Safari (or a device cloud) is the safe answer. For most apps, Playwright's three engines are enough signal.

Auto-waiting and flakiness

The classic source of flaky E2E tests is acting on an element before it is ready. How each tool handles this is a real differentiator.

Selenium historically made you manage waits yourself - explicit WebDriverWait with expected conditions. Newer versions and wrappers improved this, but a lot of Selenium suites still carry hand-written waits and the occasional sleep().

Cypress retries commands and assertions automatically until they pass or time out. cy.get('.item').should('be.visible') keeps re-checking. This is one of Cypress's best features.

Playwright auto-waits on every action: before a click it waits for the element to be attached, visible, stable, and able to receive events. Its locators are lazy and re-resolve each time, so a locator captured before a re-render still points at the right element. In a quick test, an assertion like expect(locator).toBeVisible() transparently waited through a 400ms delayed DOM update and passed without any explicit wait.

Language options and developer experience

Selenium is the only one of the three with first-class bindings in Java, Python, C#, Ruby, and JavaScript. If your team is a Java or .NET shop and wants tests in the same language as the code, Selenium is the natural fit.

Cypress is JavaScript and TypeScript only. Its runner UI - a browser window showing every command, with DOM snapshots you can step back through - is genuinely excellent for writing and debugging tests.

Playwright has official bindings in JavaScript/TypeScript, Python, Java, and C#, though the JS/TS experience is the most complete. It ships codegen (record actions into a test), a trace viewer (a full timeline with DOM snapshots, network, and console for a failed run), and a UI mode similar to Cypress's. Its parallelism is built in - tests shard across workers with isolated browser contexts by default.

The in-browser gotcha

Cypress's biggest limitation flows directly from running inside the browser: it cannot easily do things a single page cannot do. Testing a flow that opens a new tab, a login that redirects across origins, an OAuth popup, or a download that triggers an OS dialog - all of these are historically painful or need workarounds in Cypress. Cypress has added multi-origin and tab support, but it remains against the grain of the design.

Playwright and Selenium drive the browser from outside, so multiple tabs, multiple origins, popups, downloads, and even multiple independent browser contexts in one test are normal operations.

If your critical user journeys involve third-party auth, payment redirects, or multi-window flows, verify how your candidate tool handles them before committing - this is where teams get stuck six months in.

Which to choose

Playwright is the default recommendation for a new JS/TS project: fast, real cross-browser, great debugging tools, built-in parallelism, and it handles multi-tab and multi-origin flows cleanly.

Cypress suits teams who value its authoring and debugging UX above all, whose app is a single-origin SPA without heavy redirect flows, and who are all-in on JavaScript.

Selenium is right when you need a language other than JavaScript, must test real Safari or a legacy browser, need real-device mobile via Appium, or already have a large working Selenium suite and a team that knows it.

All three can produce reliable suites. The flakiness people blame on the tool is usually missing waits, shared test state, or environment differences between local and CI - fix those first, whatever you pick.

Frequently Asked Questions

Does Playwright test real Safari? It tests WebKit, the engine behind Safari, using Playwright's own build. That catches most Safari-relevant issues on Linux CI, but a few Safari-specific quirks will not appear. For certainty, use Selenium against real Safari or a device cloud.
Why is Cypress limited to one browser tab? Cypress runs your test code inside the browser page, in the same event loop as your app. Anything outside that single page - new tabs, cross-origin redirects, OS dialogs - works against the design. Recent versions added partial support, but it stays awkward.
Can I write Selenium tests in Python? Yes. Selenium has first-class bindings in Python, Java, C#, Ruby, and JavaScript. It is the only one of the three tools with broad multi-language support.
Which tool has the best debugging experience? Cypress's runner UI and Playwright's trace viewer are both excellent - a visual timeline with DOM snapshots for each step. Selenium relies on screenshots, logs, and your language's debugger.
Is Playwright faster than Selenium? Generally yes. Selenium sends each command as an HTTP round-trip to a separate driver process, while Playwright uses a persistent connection over the browser's automation protocol, plus built-in parallel workers.