Understanding Playwright & Cypress
Two browsers-as-test-runners — one new, one familiar.
What the two frameworks do, where their philosophies diverge, and the locator strategy that survives a redesign.
End-to-end, in the real browser.
Both Playwright (Microsoft) and Cypress drive a real browser to test the application as a user would. Click, type, wait, assert. Unlike unit tests, they catch interactions across the whole stack: framework router, network calls, CSS, real DOM. Unlike Selenium, they're built for fast loops — auto-waiting, network mocking, time-travel debugging. Worth one of every five tests in a healthy pyramid.
The philosophical split.
Cypress runs inside the browser context — your test code shares the same JavaScript runtime as the app. That gives Cypress unique abilities (read app state directly, spy on internal calls) but limits it to one tab, one browser at a time, and same-origin pages. Playwright runs as an external driver — your test is in a Node process talking to the browser over CDP. That makes Playwright work across tabs, domains, and browsers (Chromium, Firefox, WebKit) and gives it parallelism Cypress doesn't have. Cypress is friendlier; Playwright is more powerful.
Auto-waiting.
The killer feature both adopted from the start: instead of sleep(500)before every interaction, the framework waits implicitly until the element is attached, visible, enabled, and stable. Playwright's auto-wait is more aggressive — it'll wait up to 30 seconds by default and retry the entire action chain. Cypress's retry happens on assertions, not actions. Either way, the dominant cause of flaky legacy Selenium suites is solved by both.
Locator strategy.
Brittle test suites die on CSS selectors. .btn-primary.large.with-iconbreaks the moment a designer touches the stylesheet. The modern advice: prefer locators users see — getByRole('button', { name: 'Submit' }),getByLabel('Email'), getByText('Sign in'), plusdata-testid as the escape hatch for elements without natural labels. Tests written this way survive redesigns; tests written with CSS selectors don't. Playwright and Cypress both expose the same Testing Library-style getters.
A worked test.
Playwright: test("login", async ({ page }) => { await page.goto("/login"); await page.getByLabel("Email").fill("a@b.com"); await page.getByLabel("Password").fill("pw"); await page.getByRole("button", { name:
"Sign in" }).click(); await expect(page).toHaveURL("/dashboard"); });. Five steps; no manual waits; runs in 2-3 seconds. The Cypress version is structurally identical with cy. instead of page.. Both ship acodegen recorder that writes this for you from a clicked-through demo.
Login flow test
goto → fill → click → assert
Drive the browser like a user.
5 commands, auto-waited, role-based locators
= 2-3 seconds, survives redesigns
What to test E2E.
The pyramid: lots of unit tests, fewer integration tests, fewest E2E. E2E tests are slow, expensive, and flaky relative to the others — reserve them for the user journeys that actually matter. Sign up, sign in, the critical purchase flow, the critical export flow. Don't test every form validator in E2E; that's a unit test job. The Playwright/Cypress codegen output should be edited down ruthlessly before merging.