Playwright vs Selenium vs Cypress

How Playwright, Selenium, Cypress, WebdriverIO and Puppeteer handle iframes, shadow DOM, drag and drop, downloads and auth — with a page for each.

Fifteen situations, and what Playwright, Selenium, Cypress, WebdriverIO and Puppeteer each ask of you. Every row links to a page on this site that reproduces it. Reviewed 2026-09; treat the vendor's current documentation as authoritative.

The frameworks

FrameworkLanguagesHow it drives the browser
PlaywrightJS/TS, Python, Java, .NETRuns out of process, drives the browser over CDP/WebDriver BiDi
SeleniumJava, Python, C#, JS, Ruby, moreW3C WebDriver, the interoperable standard across browsers and grids
CypressJS/TSRuns inside the browser alongside the application under test
WebdriverIOJS/TSWebDriver and BiDi with its own runner and service ecosystem
PuppeteerJS/TSChrome DevTools Protocol, Chromium-first with Firefox support

Situation by situation

SituationPlaywrightSeleniumCypress
Waiting for an elementActions auto-wait for visible and actionableExplicit WebDriverWait with named expected conditionsCommands retry for presence; visibility is a chained assertion
Native <select>selectOptionSelect helper classcy.select
Custom listbox widgetOrdinary clicks, role-based locatorsOrdinary clicks — the Select helper does not applyOrdinary clicks and assertions on aria-selected
HTML5 drag and dropBuilt-in drag often works; synthetic events are the fallbackActions API frequently needs synthetic dragstart/dropNeeds a plugin or hand-dispatched events
Native dialogs (alert/confirm/prompt)page.on("dialog"), registered before the clickswitch_to.alertAuto-accepts by default; stub the window event to assert
iframesframeLocatorswitch_to.frame and back to default_contentNo native switching — reach through the frame document
Open shadow DOMCSS locators pierce open roots by defaultgetShadowRoot() on the host (Selenium 4)includeShadowDom option
New windows and tabsWait for the popup event on the contextSwitch by window handleNot supported — assert the link target instead
File uploadsetInputFilessend_keys(path) on the inputselectFile
File downloadDownload event with a suggested filenameBrowser preferences plus filesystem assertionsDownloads folder plus filesystem assertions
Request interceptionpage.routeCDP or WebDriver BiDi network interceptioncy.intercept
HTTP Basic AuthhttpCredentials on the browser contextCDP/BiDi auth handler; URL credentials are being restrictedauth option on cy.visit or a request header
HTTP Digest AuthNot covered by httpCredentials — compute the responseNot covered — compute the response or use an HTTP clientNot covered — compute the response or use cy.request
Asserting a status codeRead the Response from navigation or APIRequestContextUse an HTTP client alongside the driver, or BiDicy.request returns the status directly
Cookies and web storageContext cookies plus storageStateDriver cookie API; storage via script executioncy.getCookie, cy.clearLocalStorage

What this site is for

Fun UI Playground is a target, not a framework: a stable set of pages that reproduce the situations above on demand, so you can practise a technique or reproduce a failure without standing up an application first. Every interactive element carries a documented id and data-testid, and every page states what is deterministic and what is not.

What it is not

It is not a benchmark, and nothing here measures one framework against another. The table is a map of where the same task needs a different approach, so that a test failing on this site tells you something about the technique rather than about the page.

How to use it with any framework

Point your runner at a challenge URL, locate by the documented id, data-testid or role, and assert the outcome the page publishes — the site-wide flash element, a URL change, or a named result element. The Getting Started guide has the same login test written five times, one per framework.

Updated

Fun UI Playground — all challenges, bug hunts, getting started, glossary, about.