Skip to main content
SeleniumDecoded

Selenium vs Playwright vs Cypress

An honest 2026 comparison of the three main browser automation frameworks: architecture, speed, flakiness, language support, browser coverage, ecosystem, and when each one is the right choice.

Selenium 4 Stable Updated 9 Sept 2026 · Verified against Selenium 4.48.0

“Should we use Selenium or Playwright?” is now the first question in most tooling discussions, and it comes up in senior interviews. This lesson gives you the facts as of September 2026 without the vendor spin, so you can answer with reasons rather than loyalty.

The 2026 Landscape

Industry surveys this year put Playwright ahead of Selenium for new end-to-end projects for the first time. Reported figures vary by survey population, but the direction is consistent: Playwright leads among JavaScript-first teams and new projects, Cypress is stable, and Selenium’s share is declining while its installed base remains the largest in enterprises. Playwright’s npm download volume is roughly an order of magnitude larger than Selenium’s JavaScript bindings, which says a lot about the JavaScript ecosystem and little about Java, Python or C# teams.

Two things are true at once:

  • Selenium is no longer the automatic default for a greenfield web suite.
  • Selenium remains the only framework with five official language bindings, a W3C standard protocol, a scalable open-source grid, and two decades of enterprise integrations.

Architecture: How Each Tool Talks to the Browser

SeleniumPlaywrightCypress
ProtocolW3C WebDriver (HTTP) plus WebDriver BiDi (WebSocket)Custom protocol over CDP (Chromium) and patched browser builds (Firefox, WebKit)Runs inside the browser; Node process for the rest
Browser buildsReal, installed browsers via vendor driversBundled, patched browser buildsReal browsers (Chrome, Edge, Firefox, Electron, WebKit experimental)
LanguagesJava, Python, JavaScript, C#, Ruby (official); Kotlin, PHP, Go via communityJavaScript/TypeScript, Python, Java, C#JavaScript/TypeScript only
StandardisationW3C standard; browser vendors ship the driverPlaywright team maintains patches per browser versionCypress team maintains the runner
Multi-tab and multi-originNativeNativeHistorically limited; improved with cy.origin
MobileAppium (same WebDriver protocol)Device emulation onlyViewport emulation only

The architectural difference explains most behavioural differences. Selenium sends each command over HTTP to a driver process, which adds latency but means the browser vendor guarantees compatibility. Playwright holds a persistent WebSocket to a browser it ships itself, which is faster and enables auto-waiting, at the cost of using patched builds for Firefox and WebKit. Cypress executes in the same run loop as your app, which gives it time-travel debugging and makes cross-origin and multi-tab scenarios awkward.

Speed and Flakiness

Published benchmarks in 2026 typically show a Playwright test finishing roughly twice as fast as the equivalent Selenium test, with Cypress in between. The gap has two causes:

  1. Round trips: each Selenium command is an HTTP request; Playwright batches over a socket.
  2. Auto-waiting: Playwright’s actions wait for actionability by default. Selenium requires explicit waits, and suites that skip them become flaky.

A well-written Selenium suite with explicit waits, parallel execution and a Grid is fast enough for almost every product. The difference matters in the tail: very large suites and teams without wait discipline.

Selenium’s honest answer to flakiness is explicit waits and page load strategy, covered in Explicit Waits and Flaky Tests. BiDi closes some of the remaining gap by letting you wait on network events instead of DOM polling.

The Same Test in All Three

Login test, Selenium (Java, Python) vs Playwright and Cypress (JavaScript)
Selenium 4 Stable
// Selenium, Java
WebDriver driver = new ChromeDriver();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.get("https://app.example.com/login");
driver.findElement(By.id("email")).sendKeys("qa@example.com");
driver.findElement(By.id("password")).sendKeys("secret");
driver.findElement(By.cssSelector("button[type=submit]")).click();
WebElement heading = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.cssSelector("h1.welcome")));
assertEquals("Welcome, QA", heading.getText());
driver.quit();
# Selenium, Python
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
driver.get("https://app.example.com/login")
driver.find_element(By.ID, "email").send_keys("qa@example.com")
driver.find_element(By.ID, "password").send_keys("secret")
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
heading = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "h1.welcome")))
assert heading.text == "Welcome, QA"
driver.quit()
// Playwright (for comparison)
import { test, expect } from '@playwright/test';
test('login', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.getByLabel('Email').fill('qa@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Welcome, QA' })).toBeVisible();
});
// Cypress (for comparison)
it('login', () => {
cy.visit('https://app.example.com/login');
cy.get('#email').type('qa@example.com');
cy.get('#password').type('secret');
cy.get('button[type=submit]').click();
cy.get('h1.welcome').should('have.text', 'Welcome, QA');
});
// Selenium, C#
using var driver = new ChromeDriver();
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
driver.Navigate().GoToUrl("https://app.example.com/login");
driver.FindElement(By.Id("email")).SendKeys("qa@example.com");
driver.FindElement(By.Id("password")).SendKeys("secret");
driver.FindElement(By.CssSelector("button[type=submit]")).Click();
var heading = wait.Until(d => {
var h = d.FindElement(By.CssSelector("h1.welcome"));
return h.Displayed ? h : null;
});
Assert.That(heading.Text, Is.EqualTo("Welcome, QA"));

Notice what Playwright and Cypress bundle: a test runner, assertions with retry, and (Playwright) accessibility-based locators. Selenium deliberately does not; you pair it with JUnit, pytest, NUnit or Mocha and choose your own assertion library. That is either freedom or extra decisions, depending on your team.

Where Selenium Still Wins

  • Language choice: the only option for Java, C# and Python teams that want a first-party, W3C-standard binding without a JavaScript toolchain. Playwright has Java, Python and C# bindings, but they are ports of the Node core and lag behind.
  • Real browsers, vendor-supported drivers: Safari via Apple’s safaridriver, Edge via Microsoft, Chrome via Google, Firefox via Mozilla. No patched builds.
  • Scale: Selenium Grid on Kubernetes, with Redis-backed state and 160+ official browser images, is unmatched open-source infrastructure. Cloud vendors (BrowserStack, Sauce Labs, LambdaTest) built their businesses on it.
  • Mobile: Appium speaks WebDriver, so page objects and skills transfer to native app testing.
  • Longevity and governance: a Software Freedom Conservancy project with a W3C spec behind it, not a single company’s product.
  • Legacy coverage: older browsers and intranet apps that a bundled-browser tool cannot touch.

Where Playwright Wins

  • Speed and built-in auto-wait reduce flakiness for teams that do not enforce wait discipline.
  • Integrated runner, tracing, network mocking, fixtures and parallel workers with no extra setup.
  • Role-based locators (getByRole) push teams toward accessible markup.
  • Excellent TypeScript experience; the reference implementation is the JavaScript one.

Where Cypress Wins

  • Developer-friendly interactive runner with time travel.
  • Component testing for React, Vue, Angular and Svelte.
  • Simple mental model for single-page apps on one origin.

Cypress’s constraints (JavaScript only, single browser tab per test, historical cross-origin friction, WebKit still experimental) keep it out of many enterprise conversations.

How to Decide

Ask these in order:

  1. What language is the product team fluent in? A Java shop gets more value from Selenium plus JUnit than from a Playwright port. A TypeScript shop should look hard at Playwright.
  2. Do you need Safari, mobile, or legacy browsers on real devices? Selenium (and Appium) or a cloud grid.
  3. Do you already run a Grid or pay a cloud vendor? Sunk infrastructure and skills are real money.
  4. How big is the suite and how much wait discipline exists? Playwright forgives sloppy waits; Selenium punishes them. Either fix the discipline or pick the forgiving tool.
  5. Is this an interview question? Say all of the above, then add that the frameworks are converging: Selenium’s BiDi delivers events and interception, Playwright is adding language bindings, and both are adopting AI-assisted locators. The skill that transfers is test design, not API syntax.

Migrating Between Them

Teams rarely rewrite a working Selenium suite. The realistic paths are:

  • New projects on Playwright, legacy on Selenium, sharing page-object concepts and test data.
  • Selenium for cross-browser and mobile, Playwright for component and API-adjacent tests.
  • Wrapping Selenium with Selenide (Java) or WebdriverIO (JavaScript, supports both WebDriver and BiDi) to get auto-wait ergonomics without leaving the protocol. See Selenium Wrappers and Higher-Level Libraries.

Summary

  • Playwright leads new-project adoption in 2026; Selenium leads installed base, language breadth and infrastructure.
  • Speed and auto-wait are Playwright’s real advantages; explicit waits and BiDi narrow the gap.
  • Choose by team language, browser matrix, existing infrastructure and wait discipline, not by GitHub stars.
  • Good test design outlives any framework. Learn locators, waits and page objects deeply and you can switch tools in a week.

Related lessons