> For the complete documentation index, see [llms.txt](https://learning.contextqa.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://learning.contextqa.com/testing-with-ai-coding-agents/cypress-to-playwright.md).

# 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](/migrate-to-contextqa/migrating-existing-tests.md) 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](https://docs.cypress.io/app/core-concepts/retry-ability) and [Playwright assertions](https://playwright.dev/docs/test-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

```ts
/// <reference types="cypress" />

describe('timezone preference', () => {
  it('persists the selected timezone', () => {
    cy.intercept('POST', '**/api/preferences').as('savePreferences');
    cy.visit('/preferences');
    cy.get('[data-testid="timezone"]').select('UTC');
    cy.get('[data-testid="save-preferences"]').click();
    cy.wait('@savePreferences').then(({ response }) => {
      expect(response?.statusCode).to.equal(200);
      expect(response?.body).to.deep.include({ timezone: 'UTC' });
    });
    cy.reload();
    cy.get('[data-testid="timezone"]').should('have.value', 'UTC');
  });
});
```

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](https://docs.cypress.io/api/commands/intercept).

### Equivalent Playwright test

```ts
import { test, expect } from '@playwright/test';

test('persists the selected timezone', async ({ page }) => {
  await page.goto('/preferences');
  await page.getByTestId('timezone').selectOption('UTC');
  const savedResponse = page.waitForResponse((response) =>
    new URL(response.url()).pathname === '/api/preferences' &&
    response.request().method() === 'POST');
  await page.getByRole('button', {
    name: 'Save preferences', exact: true,
  }).click();
  const response = await savedResponse;
  expect(response.status()).toBe(200);
  expect(await response.json()).toMatchObject({ timezone: 'UTC' });
  await page.reload();
  await expect(page.getByTestId('timezone')).toHaveValue('UTC');
});
```

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](https://playwright.dev/docs/network) 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](https://docs.cypress.io/api/commands/session) and the [Playwright authentication guide](/testing-with-ai-coding-agents/playwright-authentication-testing.md).

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](https://playwright.dev/docs/test-fixtures); choose the scope based on state ownership.

Check the existing Cypress [test-isolation setting](https://docs.cypress.io/app/core-concepts/test-isolation). 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

```
Inspect this Cypress spec, its custom commands, fixture setup, and plugins.
List every assertion, mock, network wait, state dependency, and cleanup step.
Convert only the approved slice to our installed Playwright Test version.
Preserve expected outcomes and real-vs-mocked boundaries. Flag unsupported
component/plugin behavior instead of dropping it. Do not delete Cypress tests.
Run both versions independently against the same build and reset data.
Report exact commands, first-attempt results, assertion mapping, artifacts,
and remaining gaps. Do not weaken assertions to match the implementation.
```

Use the existing [Claude Code](/testing-with-ai-coding-agents/claude-code-testing.md), [Cursor](/testing-with-ai-coding-agents/cursor-testing.md), or [Codex](/testing-with-ai-coding-agents/codex-testing.md) 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](/migrate-to-contextqa/migrating-existing-tests.md). 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](/testing-with-ai-coding-agents/export-and-portability.md) 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](/migrate-to-contextqa/planning-checklist.md) to record one slice. For a managed alternative, [book a test-migration workflow demo](https://contextqa.com/book-a-demo/?utm_source=learning.contextqa.com\&utm_medium=referral\&utm_campaign=docs_demand_gen\&utm_content=cypress-to-playwright) 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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://learning.contextqa.com/testing-with-ai-coding-agents/cypress-to-playwright.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
