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')
})
})
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')
})
Notice that in @playwright/test:
- We use
await page.goto(URL)
to navigate to a page instead ofcy.visit(URL)
. - We interact with elements using
await page.fill(selector, value)
andawait page.click(selector)
instead ofcy.get(selector).type(value)
andcy.get(selector).click()
. - We make assertions using the
expect
function instead ofcy.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!')
})
})
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
!')
})
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...
})
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...
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/')
In Playwright:
await page.goto('https://ray.run/')
Clicking on an element
In Cypress:
cy.get('#my-button').click()
In Playwright:
await page.click('#my-button')
Typing into an input field
In Cypress:
cy.get('#my-input').type('Some text')
In Playwright:
await page.fill('#my-input', 'Some text')
Checking if an element contains specific text
In Cypress:
cy.get('#my-element').should('contain', 'Expected text')
In Playwright:
expect(await page.textContent('#my-element')).toContain('Expected text')
Checking the URL of a page
In Cypress:
cy.url().should('include', '/expected-path')
In Playwright:
expect(await page.url()).toContain('/expected-path')
Handling alerts
In Cypress:
cy.on('window:alert', (str) => {
expect(str).to.equal('Expected alert text')
})
In Playwright:
page.on('dialog', async dialog => {
expect(dialog.message()).toBe('Expected alert text')
await dialog.dismiss()
})
Waiting for a specific request
In Cypress:
cy.intercept('GET', '/my-endpoint').as('myRequest')
cy.wait('@myRequest')
In Playwright:
await page.waitForRequest(request => request.url().includes('/my-endpoint') && request.method() === 'GET')
Waiting for an element to be visible
In Cypress:
cy.get('#my-element').should('be.visible')
In Playwright:
await page.waitForSelector('#my-element', { state: 'visible' })
Taking a screenshot
In Cypress:
cy.screenshot()
In Playwright:
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 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)
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.