Migrate Cypress end-to-end tests to Playwright. This codemod transforms test structure, selectors, actions, assertions, and navigation commands.
This codemod uses a two-phase approach to ensure reliable transformations:
-
AST-based transformations (automatic, reliable): Handles standard Cypress patterns using traditional AST-based codemods. These transformations are deterministic and cover the majority of common patterns.
-
AI-assisted transformations (optional): Handles tricky cases that require context-aware conversion. When enabled via Codemod Campaign, AI will automatically migrate complex patterns that were marked with TODO comments by the AST-based step.
The following transformations are handled reliably by AST-based codemods:
| Cypress | Playwright |
|---|---|
describe() | test.describe() |
it() | test() |
before() | test.beforeAll() |
beforeEach() | test.beforeEach() |
after() | test.afterAll() |
afterEach() | test.afterEach() |
| Cypress | Playwright |
|---|---|
cy.get('.selector') | page.locator('.selector') |
cy.contains('text') | page.getByText('text') |
cy.get('.el').click() | await page.locator('.el').click() |
cy.get('.el').type('text') | await page.locator('.el').fill('text') |
cy.get('.el').check() | await page.locator('.el').check() |
cy.get('.el').select('opt') | await page.locator('.el').selectOption('opt') |
cy.get('.el').first() | page.locator('.el').first() |
cy.get('.el').last() | page.locator('.el').last() |
cy.get('.el').eq(n) | page.locator('.el').nth(n) |
cy.get('.el').find('.child') | page.locator('.el').locator('.child') |
| Cypress | Playwright |
|---|---|
.should('be.visible') | await expect(...).toBeVisible() |
.should('exist') | await expect(...).toBeAttached() |
.should('have.text', 'x') | await expect(...).toHaveText('x') |
.should('contain', 'x') | await expect(...).toContainText('x') |
.should('have.value', 'x') | await expect(...).toHaveValue('x') |
.should('have.class', 'x') | await expect(...).toHaveClass(/x/) |
.should('have.attr', 'a', 'v') | await expect(...).toHaveAttribute('a', 'v') |
.should('be.disabled') | await expect(...).toBeDisabled() |
.should('have.length', n) | await expect(...).toHaveCount(n) |
.should('not.exist') | await expect(...).not.toBeAttached() |
| Cypress | Playwright |
|---|---|
cy.visit('/path') | await page.goto('/path') |
cy.reload() | await page.reload() |
cy.go('back') | await page.goBack() |
cy.go('forward') | await page.goForward() |
cy.wait(1000) | await page.waitForTimeout(1000) |
cy.url().should('include', '/x') | await expect(page).toHaveURL(/\/x/) |
cy.title().should('eq', 'x') | await expect(page).toHaveTitle('x') |
| Cypress | Playwright |
|---|---|
cy.clearCookies() | await page.context().clearCookies() |
cy.screenshot('name') | await page.screenshot({ path: 'name.png' }) |
cy.viewport(w, h) | await page.setViewportSize({ width: w, height: h }) |
The codemod also automatically migrates Cypress configuration files:
cypress.config.ts→playwright.config.ts- Converts Cypress-specific settings to equivalent Playwright configuration
The codemod scans for custom Cypress commands in support files and reports them for review. These are typically handled by the optional AI-assisted step when enabled.
When running via Codemod Campaign with autoAIReview enabled (default), the AI will automatically handle these tricky patterns that cannot be reliably transformed by AST-based codemods:
-
.then()patterns (includingcy.visit().then()):cy.visit().then((window) => {...})→ Usespage.evaluate()to access window objectlocator.then((el) => {...})→ Extracts logic and useslocator.evaluate()or locator methods- Generic
.then()callbacks → Converts to async/await patterns with proper Playwright APIs
-
Custom Cypress commands:
- Custom commands like
cy.nextStep(),cy.prevStep(),cy.compareSnapshot(), etc. - AI analyzes the command definition and converts to appropriate Playwright helpers or
page.evaluate()
- Custom commands like
-
Complex
.should()callbacks:- Assertions with callback functions that contain custom logic
- Converts Cypress assertions within callbacks to Playwright matchers
These patterns are initially marked with TODO comments by the AST-based codemod, then automatically resolved by AI when the optional AI step is enabled.
Some Cypress features may still require manual attention:
cy.intercept()→ Usepage.route()in Playwright (may be handled by AI if simple cases)cy.wait('@alias')→ Usepage.waitForResponse()or similar (may be handled by AI)cy.task()→ Use Playwright fixtures or global setup
describe('Login', () => { beforeEach(() => { cy.visit('/login'); }); it('should login successfully', () => { cy.get('#email').type('user@example.com'); cy.get('#password').type('password123'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/dashboard'); cy.get('.welcome').should('be.visible'); }); });import { test, expect } from '@playwright/test'; test.describe('Login', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); }); test('should login successfully', async ({ page }) => { await page.locator('#email').fill('user@example.com'); await page.locator('#password').fill('password123'); await page.locator('button[type="submit"]').click(); await expect(page).toHaveURL(/\/dashboard/); await expect(page.locator('.welcome')).toBeVisible(); }); });