Migrating from Cypress to Playwright
Tags: playwright
Published: 2023-06-09 02:58:23

---
title: Migrating from Cypress to Playwright
published: true
description: We will explore the reasons for migrating from Cypress to Playwright, the benefits of using Playwright, and the practical steps to successfully transition your end-to-end testing framework.
tags: playwright
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-06-09 02:57 +0000
---

> Before you go to deep into the article, check our [Cypress to Playwright](https://ray.run/tools/cypress-to-playwright) miration tool. It will help you to migrate your tests in a few clicks.

## Why Migrate from Cypress to Playwright

[Cypress](https://www.cypress.io/) 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](https://blog.nocodb.com/moving-to-playwright-from-cypress-part-1/) 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:

```ts
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')
  })
})
```

Let's migrate this to @playwright/test:

```ts
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')
})
```

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:

```ts
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!')
  })
})
```

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

```ts
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

!')
})
```

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:

```ts
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...
})
```

Let's migrate this to `@playwright/test`:

```ts
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...
```

## 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:

```ts
cy.visit('https://ray.run/')
```

In Playwright:

```ts
await page.goto('https://ray.run/')
```

### Clicking on an element

In Cypress:

```ts
cy.get('#my-button').click()
```

In Playwright:

```ts
await page.click('#my-button')
```

### Typing into an input field

In Cypress:

```ts
cy.get('#my-input').type('Some text')
```

In Playwright:

```ts
await page.fill('#my-input', 'Some text')
```

### Checking if an element contains specific text

In Cypress:

```ts
cy.get('#my-element').should('contain', 'Expected text')
```

In Playwright:

```ts
expect(await page.textContent('#my-element')).toContain('Expected text')
```

### Checking the URL of a page

In Cypress:

```ts
cy.url().should('include', '/expected-path')
```

In Playwright:

```ts
expect(await page.url()).toContain('/expected-path')
```

### Handling alerts

In Cypress:

```ts
cy.on('window:alert', (str) => {
  expect(str).to.equal('Expected alert text')
})
```

In Playwright:

```ts
page.on('dialog', async dialog => {
  expect(dialog.message()).toBe('Expected alert text')
  await dialog.dismiss()
})
```

### Waiting for a specific request

In Cypress:

```ts
cy.intercept('GET', '/my-endpoint').as('myRequest')
cy.wait('@myRequest')
```

In Playwright:

```ts
await page.waitForRequest(request => request.url().includes('/my-endpoint') && request.method() === 'GET')
```

### Waiting for an element to be visible

In Cypress:

```ts
cy.get('#my-element').should('be.visible')
```

In Playwright:

```ts
await page.waitForSelector('#my-element', { state: 'visible' })
```

### Taking a screenshot

In Cypress:

```ts
cy.screenshot()
```

In Playwright:

```ts
await page.screenshot()
```

## 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:

* https://mtlynch.io/notes/cypress-vs-playwright/
* https://www.21risk.com/blog/migration-from-cypress-to-playwright-hype-or-great

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](https://ray.run/tools/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.