Halmurat T.
· 6 min read

I Migrated 3 Teams Off Cypress — Here's When It's Still the Right Choice

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.

playwright.config.ts
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/Button.cy.jsx
// Cypress component testing — tight feedback loop for devs
import 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:

QuestionStay on CypressMove to Playwright
Does your app cross origin boundaries?NoYes — Playwright handles this natively
Do you test inside iframes?Rarely or neverRegularly — Playwright’s iframe support is superior
Is component testing a priority?Yes — Cypress component runner is best-in-classNo — E2E is the focus
Can you use Cypress Cloud in CI?YesNo — Playwright’s built-in sharding is self-hosted
How large is your existing Cypress suite?500+ tests with custom pluginsUnder 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/e2e/dashboard.cy.js
// Cypress — synchronous-looking chain API
describe('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');
});
});
tests/dashboard.spec.ts
// Playwright — explicit async with built-in auto-waiting
import { 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 stabilitycontains('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

Get weekly QA automation insights

No fluff, just battle-tested strategies from 10+ years in the trenches.

No spam. Unsubscribe anytime.