I Migrated 3 Teams Off Cypress — Here's When It's Still the Right Choice
Table of Contents
Over the past three years, I’ve helped three enterprise teams migrate their test suites from Cypress to Playwright. Two of those migrations were the right call. One wasn’t — and the team wasted about six weeks before we admitted it and rolled back. The difference wasn’t about which framework is “better.” It was about understanding what each tool is actually bad at, and whether those weaknesses matter for your specific application.
Where Cypress Falls Apart at Scale
Cypress works beautifully for single-origin SPAs. The problem is that most enterprise apps aren’t single-origin SPAs. They’re sprawling ecosystems with OAuth redirects, embedded iframes from third-party vendors, and multi-tab workflows that users depend on daily.
Here’s where we hit walls on real projects:
Multi-origin testing. At a large insurance platform, the claims workflow redirected through three different domains — the main app, an authentication provider, and a document management system. Cypress’s single-origin architecture meant we couldn’t test the full user journey without workarounds that were more fragile than the tests they replaced. We spent two weeks building cy.origin() hacks before accepting that Playwright handled this natively.
Iframe-heavy applications. A financial services client embedded compliance forms from a regulated third-party vendor inside iframes. Cypress’s iframe support required plugins and custom commands that broke on every major upgrade. Playwright treats iframes as first-class citizens — you just call frame() or frameLocator() and move on.
CI parallel execution. Cypress’s parallelization depends on their Dashboard service (now Cypress Cloud) for load balancing. At one company, the security team wouldn’t approve an external SaaS dependency in the CI pipeline. Playwright’s sharding is built in and runs entirely within your infrastructure — no external service needed.
export default defineConfig({ // Shard across CI machines — no external service required workers: process.env.CI ? 4 : undefined, fullyParallel: true, retries: process.env.CI ? 2 : 0,});Where Cypress Still Wins
Here’s the part most “Playwright is better” articles skip: Cypress genuinely excels in areas that matter for certain teams.
Component testing developer experience. If your front-end team writes component tests (not just E2E tests), Cypress’s component testing runner is significantly more polished than Playwright’s experimental component testing. The hot-reload feedback loop, the visual component explorer, the mount API — it’s a better DX for developers who live in React or Vue all day.
// Cypress component testing — tight feedback loop for devsimport Button from './Button';
it('renders primary variant', () => { cy.mount(<Button variant="primary">Submit</Button>); cy.get('button').should('have.class', 'btn-primary'); cy.get('button').should('be.visible');});Time-travel debugging. Cypress’s test runner snapshots every command and lets you hover over each step to see the DOM state at that moment. When a test fails, you can literally scrub backward through the execution. Playwright’s trace viewer achieves something similar, but Cypress’s interactive runner feels more immediate during local development.
Teams with deep Cypress investment. The team where we rolled back the migration had 1,200 Cypress tests, custom plugins for visual regression, and three developers who’d built significant internal tooling around cy.intercept(). The migration cost would have been 8–10 weeks of engineering time. Their pain points (occasional flakiness, slow CI) didn’t justify that cost.
The Decision Framework
After three migrations, I’ve boiled the decision down to five questions:
| Question | Stay on Cypress | Move to Playwright |
|---|---|---|
| Does your app cross origin boundaries? | No | Yes — Playwright handles this natively |
| Do you test inside iframes? | Rarely or never | Regularly — Playwright’s iframe support is superior |
| Is component testing a priority? | Yes — Cypress component runner is best-in-class | No — E2E is the focus |
| Can you use Cypress Cloud in CI? | Yes | No — Playwright’s built-in sharding is self-hosted |
| How large is your existing Cypress suite? | 500+ tests with custom plugins | Under 500 tests or early in adoption |
If you answered “Move to Playwright” on three or more of those questions, the migration is probably worth it. If you answered two or fewer, you might be migrating for hype rather than for real engineering reasons.
Side-by-Side: The Same Test in Both Frameworks
Here’s a login-and-navigate test written in both frameworks so you can see the actual API differences:
// Cypress — synchronous-looking chain APIdescribe('Dashboard', () => { it('shows welcome message after login', () => { cy.visit('/login'); cy.get('[data-testid="email"]').type('user@example.com'); cy.get('[data-testid="password"]').type('password123'); cy.get('button').contains('Sign In').click(); cy.url().should('include', '/dashboard'); cy.contains('Welcome back').should('be.visible'); });});// Playwright — explicit async with built-in auto-waitingimport { test, expect } from '@playwright/test';
test('shows welcome message after login', async ({ page }) => { await page.goto('/login'); await page.getByTestId('email').fill('user@example.com'); await page.getByTestId('password').fill('password123'); await page.getByRole('button', { name: 'Sign In' }).click(); await expect(page).toHaveURL(/dashboard/); await expect(page.getByText('Welcome back')).toBeVisible();});The Playwright version is more explicit about async operations, which matters for understanding the async model and avoiding flaky tests. The Cypress version looks cleaner at first glance, but that synchronous appearance hides retry logic that can surprise you when tests interact with slow backends.
Both locator strategies use text-based approaches for stability — contains('Sign In') in Cypress, getByRole('button', { name: 'Sign In' }) in Playwright.
What I’d Do Differently
On the migration that failed, I should have run a proof-of-concept on the three most complex test files before committing to a full migration. We went all-in based on the simple tests working well, then discovered the complexity when we hit the custom plugin ecosystem. A two-day spike would have saved six weeks.
Before You Decide
Run this exercise: take your five most complex E2E tests — the ones that break most often or cover the most critical user journeys. Try rewriting them in Playwright. If the rewrite is cleaner and handles your edge cases better, the migration is worth pursuing. If the rewrite looks about the same or requires new workarounds, stay where you are.
The best framework is the one that makes your specific tests more reliable. That’s not always Playwright — even though it usually is.
Related Posts
XPath text() vs Dot — Why Your Text Match Fails
The real difference between XPath text(), dot, contains(), and normalize-space() for test automation — with examples that explain the flaky failures.
Why Your Playwright Tests Are Flaky — The Async Trap Every SDET Falls Into
The 3 async mistakes that cause flaky Playwright tests after a Selenium migration — and how we fixed a 23% intermittent failure rate.
Your Selectors Keep Breaking — Start Using Text
Why text-based locators reduce test maintenance by up to 60% and how to implement them in Selenium and Playwright with real enterprise examples.
Get weekly QA automation insights
No fluff, just battle-tested strategies from 10+ years in the trenches.
No spam. Unsubscribe anytime.