For the complete documentation index, see llms.txt. This page is also available as Markdown.

Cypress to Playwright Migration

Convert Cypress tests to Playwright by mapping assertions, waits, intercepts, sessions, and fixtures, then verify equivalent behavior before cutover.

Quick answer

Migrate one representative Cypress workflow at a time. Record its assertions, real and mocked dependencies, authentication, fixtures, cleanup, and CI behavior before translating it into Playwright. Validate the new test against the same build and isolated data, including a deliberate defect that both suites must detect. A passing generated file or matching test count does not establish equivalent coverage.

This guide covers Cypress to repository-owned Playwright Test. For managed execution, use MCP-assisted suite migration as a separate decision. A coding agent can help with either path, but should not silently change the destination or test scope.

Start with a migration record

Choose a slice containing a real network operation, asynchronous UI state, and a meaningful failure condition. Record source commit, Cypress and Node versions, lockfile, exact command, environment, test accounts, baseline result, and retained failure artifacts. Inventory plugins and custom commands before estimating conversion effort.

Classify each source test as browser E2E, component, UI with mocked APIs, or another category. A Cypress component test does not become browser E2E merely because its output uses Playwright. Preserve the boundary or give its replacement an explicit owner.

Map behavior, not command names

Cypress construct
Playwright migration question

Queued cy.* chain

Which asynchronous actions and assertions need await?

.should()

Which locator assertion should retry until the UI meets the expectation?

cy.intercept()

Is this observing traffic, replacing a response, or modifying a request?

cy.wait('@alias')

Which exact request must be registered before the triggering action?

cy.session()

Which state is cached, how is it validated, and which account owns it?

Custom command or task

Is it browser interaction, Node setup, an API call, or cleanup?

Fixture and hook

Should setup be test-scoped, worker-scoped, or external?

Cypress retries linked queries and assertions according to its command model; Playwright uses awaited actions and retrying web assertions. Replacing .should('be.visible') with a one-time boolean check changes timing semantics. See Cypress retry behavior and Playwright assertions.

Do not translate a fixed sleep into a different fixed sleep. Wait for the actual response or observable condition that the requirement depends on. Preserve the original expected values instead of asking the implementation what result to expect.

Worked example: saved preference survives reload

These are illustrative application specs, not tests against a supplied app. Both require:

  • An approved isolated fixture app and configured runner baseUrl/baseURL.

  • /preferences renders a timezone selector with data-testid="timezone" and a Save preferences button with data-testid="save-preferences".

  • The initial timezone is a known non-UTC value. Saving sends POST /api/preferences, returns 200 with { "timezone": "UTC" }, and persists it.

  • Reload reads the stored preference. Test setup resets only this fixture's state before each independent run.

  • This small fixture has no login requirement. Adapt production-like account/session setup separately; do not remove authentication from your real application.

Original Cypress test

This cy.intercept observes the real request; it supplies no replacement response. Cypress supports both spying and stubbing, so inspect each source call before migrating it. Cypress intercept reference.

Equivalent Playwright test

Register the response wait before clicking so a fast response is not missed. The request predicate excludes unrelated methods; status and body assertions still run afterward. For a source test that deliberately stubs an API, use reviewed Playwright routing with the same request and response contract. Do not quietly turn this real-backend check into a mocked test.

The reload assertion adds evidence beyond a successful toast. It does not independently prove the database write: the app might restore state from browser storage. If the requirement needs server persistence, add an authenticated API read or server-state check to both suites and record the expanded scope.

Rebuild fixtures and sessions deliberately

Cypress cy.session() caches cookies, localStorage, and sessionStorage and offers session validation. Playwright's authentication-state workflow is a different mechanism; do not copy cached data blindly or assume sessionStorage is restored identically. Follow Cypress sessions and the Playwright authentication guide.

Map fixture lifetime explicitly. A shared mutable account can make individually passing tests fail in parallel. Account creation, initial data, reset, and cleanup must have named owners. Keep one test's cleanup from deleting another worker's records. Playwright supports test- and worker-scoped fixtures; choose the scope based on state ownership.

Check the existing Cypress test-isolation setting. If source tests depend on earlier tests leaving a page or record behind, make that prerequisite explicit before conversion. Retaining an accidental dependency makes the new suite fragile too.

Give the coding agent a bounded conversion task

Use the existing Claude Code, Cursor, or Codex setup. Review each conversion before scaling to the next slice.

Accept the migration before changing the release gate

Control
Acceptance evidence

Known-good build

Both independent runs pass required assertions

Seeded defect

Both fail at the intended assertion, not an environment error

Isolation

Same result with clean state and agreed parallelism

Failure diagnosis

Reviewer can locate the failing assertion and useful artifacts

CI behavior

Failure, timeout, and missing execution cannot be reported as pass

For the example, an isolated fixture change that stops persisting the timezone should make the reload assertion fail after a reset to the known non-UTC value. Restore the implementation and rerun. Preserve first attempts when retries occur.

Keep the old suite available until the replacement's assertion map and operational behavior are accepted. Compare like-for-like browser, build, data, and retry settings before making runtime claims. Migration success is preserved defect detection and maintainable operation, not a universal speed promise.

Optional managed testing and export

If shared authoring and managed execution are the objective, evaluate the same slice through ContextQA MCP migration. Inspect current tool availability, perform the documented analysis/dry run, and review skipped or unsupported behavior before creating cases. A remote MCP server does not automatically have access to your local repository.

ContextQA's export and portability guide explains how to review a Playwright export. Returned files and placeholders require acceptance testing; export does not establish lossless conversion or equivalent assertions.

Frequently asked questions

Can an AI agent convert the whole Cypress suite automatically?

It can assist translation, but plugin behavior, component tests, session setup, mocks, and business assertions require review. Start with a representative slice and expand after acceptance.

Must I replace every Cypress test at once?

No. Track each migrated behavior and its authoritative release check. A staged migration allows gaps to be resolved without silently removing coverage.

Why does the new Playwright test pass when Cypress fails?

Compare assertions, mocked responses, selected environment, account state, and retries before calling the old test flaky. A less demanding test may pass because it no longer checks the same outcome.

Next step

Use the migration planning checklist to record one slice. For a managed alternative, book a test-migration workflow demo with a representative spec and its assertions.

Official framework references reviewed September 25, 2026. Recheck APIs and plugin behavior against the versions pinned in your repository before adapting these examples.

Last updated

Was this helpful?