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();
});
});