DEV Community

Luc Gagan
Luc Gagan

Posted on

Migrating from Cypress to Playwright

Before you go to deep into the article, check our Cypress to Playwright miration tool. It will help you to migrate your tests in a few clicks.

Why Migrate from Cypress to Playwright

Cypress has been a popular choice for end-to-end testing in recent years. However, many QA engineers have encountered challenges and limitations with Cypress, leading them to consider alternatives like [Playwright(https://playwright.dev/)]. Let's discuss some of the key reasons for migrating from Cypress to Playwright.

Flakiness in Cypress Tests

One of the primary concerns with Cypress is the flakiness of tests, especially in CI/CD pipelines. This often leads to more time spent debugging test code rather than shipping releases, which is not ideal for fast-moving development teams.

Developer Experience with Cypress

Cypress has a unique chaining pattern for commands, which can be frustrating for developers. The lack of support for async/await and the inconsistent APIs can make writing tests in Cypress more difficult than necessary. Moreover, running Node.js functions or custom commands in Cypress involves a steep learning curve and can be error-prone.

Cypress Performance and Parallelization

Cypress can be slow when it comes to test execution, and running tests in parallel is a premium feature. This means teams might end up paying for inefficiencies and may not fully utilize their local development machines' capabilities.

Benefits of Using Playwright

Playwright offers several advantages over Cypress, making it an attractive choice for end-to-end testing. Some of the key benefits include:

Improved Developer Experience

Playwright supports async/await, providing a more intuitive and modern approach to writing tests. The API is consistent and easy to use, making test development faster and more enjoyable.

Parallel Test Execution

Playwright actively promotes parallel test execution, allowing you to run multiple tests simultaneously, both locally and in CI environments. This can significantly speed up test execution times and make better use of available resources.

Better Test Stability and Less Flakiness

Tests written in Playwright tend to be more stable and less prone to flakiness compared to Cypress. This results in more reliable test results and fewer false positives.

Faster Test Execution

Playwright tests generally run faster than Cypress tests, resulting in quicker feedback loops and reduced wait times for test results.

Example: This team saw 3.87x increase in test execution speed after migrating from Cypress to Playwright.

Support for Multiple Browsers

Playwright supports testing in multiple browsers, including Chrome, Firefox, and Safari, providing better test coverage across various browser environments.

Migrating a Basic Test Case

First things first, let's start migrating a basic test case from Cypress to @playwright/test. In this section, we'll cover the basics—setting up a test, navigating to a page, interacting with elements, and making assertions.

Let's consider the following Cypress test case:

describe('Login Page', () => {
  it('should log in', () => {
    cy.visit('https://ray.run/login')

    cy.get('#username').type('admin')
    cy.get('#password').type('password123')

    cy.get('#login-button').click()

    cy.url().should('include', '/dashboard')
  })
})
Enter fullscreen mode Exit fullscreen mode

Let's migrate this to @playwright/test:

import { test, expect } from '@playwright/test'

test('should log in', async ({ page }) => {
  await page.goto('https://ray.run/login')

  await page.fill('#username', 'admin')
  await page.fill('#password', 'password123')

  await page.click('#login-button')

  expect(await page.url()).toContain('/dashboard')
})
Enter fullscreen mode Exit fullscreen mode

Notice that in @playwright/test:

  • We use await page.goto(URL) to navigate to a page instead of cy.visit(URL).
  • We interact with elements using await page.fill(selector, value) and await page.click(selector) instead of cy.get(selector).type(value) and cy.get(selector).click().
  • We make assertions using the expect function instead of cy.should().

Testing Form Submission

Now, let's say we have a contact form that we need to test. Here's what the Cypress test might look like:

describe('Contact Page', () => {
  it('should submit contact form', () => {
    cy.visit('https://ray.run/contact')

    cy.get('#name').type('John Doe')
    cy.get('#email').type('john.doe@example.com')
    cy.get('#message').type('Hello there!')

    cy.get('#submit-button').click()

    cy.contains('Your message has been sent!')
  })
})
Enter fullscreen mode Exit fullscreen mode

Here's how we could migrate it to @playwright/test:

import { test } from '@playwright/test'

test('should submit contact form', async ({ page }) => {
  await page.goto('https://ray.run/contact')

  await page.fill('#name', 'John Doe')
  await page.fill('#email', 'john.doe@example.com')
  await page.fill('#message', 'Hello there!')

  await page.click('#submit-button')

  expect(await page.textContent('body')).toContain('Your message has been sent

!')
})
Enter fullscreen mode Exit fullscreen mode

The main difference here is that we use await page.textContent(selector) to check if a particular text exists in the page content.

Working with Fixtures and Hooks

Just like Cypress, @playwright/test provides hooks (beforeEach, afterEach, etc.) and test fixtures for setting up and tearing down tests.

Consider this Cypress test with fixtures and hooks:

describe('Dashboard Page', () => {
  beforeEach(() => {
    cy.visit('https://ray.run/login')

    cy.get('#username').type('admin')
    cy.get('#password').type('password123')

    cy.get('#login-button').click()

    cy.url().should('include', '/dashboard')
  })

  it('should display user profile', () => {
    cy.get('#profile-button').click()

    cy.contains('Admin User')
  })

  // Other tests...
})
Enter fullscreen mode Exit fullscreen mode

Let's migrate this to @playwright/test:

import { test } from '@playwright/test'

test.beforeEach(async ({ page }) => {
  await page.goto('https://ray.run/login')

  await page.fill('#username', 'admin')
  await page.fill('#password', 'password123')

  await page.click('#login-button')

  expect(await page.url()).toContain('/dashboard')
})

test('should display user profile', async ({ page }) => {
  await page.click('#profile-button')

  expect(await page.textContent('body')).toContain('Admin User')
})

// Other tests...
Enter fullscreen mode Exit fullscreen mode

Side-by-Side Comparison

Let's explore some of the most common methods you might use in Cypress and how they translate to Playwright:

Visiting a URL

In Cypress:

cy.visit('https://ray.run/')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.goto('https://ray.run/')
Enter fullscreen mode Exit fullscreen mode

Clicking on an element

In Cypress:

cy.get('#my-button').click()
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.click('#my-button')
Enter fullscreen mode Exit fullscreen mode

Typing into an input field

In Cypress:

cy.get('#my-input').type('Some text')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.fill('#my-input', 'Some text')
Enter fullscreen mode Exit fullscreen mode

Checking if an element contains specific text

In Cypress:

cy.get('#my-element').should('contain', 'Expected text')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

expect(await page.textContent('#my-element')).toContain('Expected text')
Enter fullscreen mode Exit fullscreen mode

Checking the URL of a page

In Cypress:

cy.url().should('include', '/expected-path')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

expect(await page.url()).toContain('/expected-path')
Enter fullscreen mode Exit fullscreen mode

Handling alerts

In Cypress:

cy.on('window:alert', (str) => {
  expect(str).to.equal('Expected alert text')
})
Enter fullscreen mode Exit fullscreen mode

In Playwright:

page.on('dialog', async dialog => {
  expect(dialog.message()).toBe('Expected alert text')
  await dialog.dismiss()
})
Enter fullscreen mode Exit fullscreen mode

Waiting for a specific request

In Cypress:

cy.intercept('GET', '/my-endpoint').as('myRequest')
cy.wait('@myRequest')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.waitForRequest(request => request.url().includes('/my-endpoint') && request.method() === 'GET')
Enter fullscreen mode Exit fullscreen mode

Waiting for an element to be visible

In Cypress:

cy.get('#my-element').should('be.visible')
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.waitForSelector('#my-element', { state: 'visible' })
Enter fullscreen mode Exit fullscreen mode

Taking a screenshot

In Cypress:

cy.screenshot()
Enter fullscreen mode Exit fullscreen mode

In Playwright:

await page.screenshot()
Enter fullscreen mode Exit fullscreen mode

Real-World Examples of Migrating from Cypress to Playwright

Several development teams have successfully migrated from Cypress to Playwright and shared their experiences in blog articles. Here are a few examples:

These articles provide valuable insights into the migration process, the challenges encountered, and the benefits of using Playwright for end-to-end testing.

Conclusion

Migrating from Cypress to Playwright can significantly improve your end-to-end testing experience, offering better developer experience, faster test execution, and improved test stability. By following the practical steps outlined in this guide and learning from the experiences of other development teams, you can successfully transition your testing framework to Playwright and reap the benefits of this powerful testing tool.

Remember to try our Cypress to Playwright migration tool and keep an eye on the Playwright project for updates and new features which will continue to enhance your end-to-end testing experience.

Top comments (1)

Collapse
 
codewander profile image
codewander

Can you explain why playwright lends itself to less flakiness?

Other comparisons have mentioned that cypress has tighter control over test execution because it forgoes CDP and runs in same browser as the system under test.