← All Posts

Cypress to Playwright Migration: A Complete Guide From 2000+ Tests

5/23/2026

We led a full migration of 2000+ E2E tests from Cypress to Playwright at a UK SaaS company with 50+ engineers. CI time dropped from 38 minutes to 12. Flake rate dropped from 18% to under 2%. Release frequency doubled.

This is the complete guide. Why we migrated, how we did it without disrupting shipping, what worked, and what we would do differently.

Why we migrated from Cypress

Cypress was fine when we had 200 tests. At 2000, it broke down. The main issues:

  • No native multi-tab support. We had workarounds everywhere for flows that open a new tab.
  • Single browser context per test. Couldn’t test user A and user B in the same flow without ugly hacks.
  • Slow CI. Cypress parallelization required a paid dashboard or fragile custom infrastructure.
  • Flaky tests were eating 2-3 hours of developer time per week just investigating false failures.
  • WebKit/Safari support was limited. Our enterprise customers used Safari and we couldn’t catch Safari-specific bugs reliably.

Why Playwright won

  • Native multi-tab, multi-context. Test multi-user flows naturally.
  • Auto-waiting that actually works. No more hardcoded waits. Tests are stable by default.
  • Real cross-browser support. Chromium, Firefox, WebKit. First-class.
  • Fast parallel execution. Free, built-in. No dashboard required.
  • Better debugging. Trace viewer is a game-changer for investigating CI failures.

The migration approach: phased, not big-bang

We didn’t rewrite everything at once. That’s how migrations fail. Here is the exact phased approach we used.

Phase 1: Run Playwright alongside Cypress (2 weeks)

We installed Playwright in the same repo as Cypress. Both ran in CI. We set up the test infrastructure (page objects, fixtures, base config) before writing a single migrated test. This phase was about proving the framework works in our environment without disrupting anything.

Phase 2: Audit the existing suite (1 week)

Before migrating, we audited every Cypress test. The result was sobering. About 30-40% of our suite was dead weight:

  • Duplicates testing the same flow at multiple levels
  • Tests covered by unit tests at a lower cost
  • Tests for features that no real user ever touched
  • Tests that had been skipped for months but never deleted
  • Tests for static UI text that changed every sprint

We deleted around 600 tests before migration started. This single decision saved us months of work and made the remaining migration faster.

Phase 3: Migrate the worst tests first (4 weeks)

We started with the flakiest 200 tests. Counter-intuitive, but the right call. Why? Because immediate wins build trust. When developers saw Playwright catching real bugs that Cypress had been silently failing on for months, they stopped resisting the migration.

The flake rate on these 200 tests dropped from 23% to under 1% overnight, just from Playwright’s auto-waiting.

Phase 4: Module-by-module migration (8 weeks)

Each sprint, one engineering team owned migrating their module. Auth team migrated auth tests. Checkout team migrated checkout tests. We provided the page object patterns and reviewed PRs. They wrote the migrations.

This was the most important decision in the whole project. Migration scales when ownership is distributed.

Phase 5: Cutover and Cypress removal (2 weeks)

Once coverage parity was confirmed, we removed Cypress entirely. Deleted the dependency, cleaned up the CI config, removed the docs. Cypress was completely gone after 4 months.

The shared page object layer that saved us

The single technical decision that mattered most: we built a shared page object layer before migrating any tests.

Instead of porting one test at a time, we built the abstractions first. A LoginPage, a CheckoutPage, a UserDashboard. Then porting individual tests was 10 minutes instead of 2 hours.

// Shared page object example
export class LoginPage {
  constructor(private page: Page) {}

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.page.getByLabel('Email').fill(email);
    await this.page.getByLabel('Password').fill(password);
    await this.page.getByRole('button', { name: 'Sign in' }).click();
    await this.page.waitForURL('/dashboard');
  }
}

// Test using the page object
test('user can log in', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

The mistakes we made

Mistake 1: We left CI pipeline migration until the end

We focused on writing tests first and migrating the CI config last. Wrong order. The CI work uncovered subtle issues (parallelism, environment variables, artifact handling) that delayed cutover by a week.

Do this instead: migrate the CI config first. Then migrate tests into a CI that already works.

Mistake 2: We tried to make Playwright tests look like Cypress tests

Early migrations were too literal. We translated cy.intercept into page.route one-for-one. The result was Playwright tests that felt clunky because they were Cypress in disguise.

Do this instead: let go of Cypress patterns. Playwright handles routing, waiting, and assertions differently. Embrace the differences.

Mistake 3: We underestimated test data complexity

Half our flakes weren’t test issues. They were data issues. Tests writing to a shared database, stale fixtures, ordering dependencies. Playwright didn’t fix this. We had to.

Do this instead: audit your test data layer early. Build fixtures that isolate state. Use test database resets between runs.

The results, in numbers

  • CI time: 38 min → 12 min
  • Flake rate: 18% → under 2%
  • Test count: 2000+ → 1400 (we deleted the dead weight)
  • Release frequency: bi-weekly → weekly (then twice-weekly)
  • Developer time investigating false failures: 2-3 hrs/week → almost zero

The biggest lesson

The migration itself wasn’t the hard part. Getting 50 developers to trust the new test suite was.

We ran both frameworks in parallel for two weeks at the start. Developers could see Playwright catching real bugs that Cypress was silently passing. That built trust faster than any internal documentation or demo could.

Migrations are organizational changes, not technical ones. Plan for the human side.

Should you migrate?

Yes, if any of these apply:

  • Your Cypress suite has 500+ tests and is slowing down
  • You have flaky tests that nobody investigates anymore
  • You need real Safari/WebKit support
  • You need multi-tab or multi-context flows
  • CI parallelization is a pain point

No, if:

  • Your suite is under 200 tests and works fine
  • You are pre-product-market-fit and changing features weekly
  • You don’t have engineering capacity to do it incrementally

When to start

The best time to migrate is before your Cypress suite becomes painful to maintain. At 500 tests it’s a 2-week project. At 2000 tests it’s a quarter. The longer you wait, the harder it gets.

Need help with your migration?

We have done this exact migration before, at the exact scale described in this post. If your team is thinking about moving from Cypress to Playwright, we can do it for you without disrupting your release cycle.

Learn about our Cypress to Playwright migration service or book a free 30-minute call to discuss your specific situation.

Need Help Implementing This?

We help engineering teams set up test automation, CI/CD, and quality infrastructure.