Javascript

Our Journey from Cypress to Playwright for E2E Testing

William Mimura
3 min read
WilliamM - How we converted our internal E2E tests from Cypress to Playwright
This article was written over 18 months ago and may contain information that is out of date. Some content may still be relevant, but please refer to official documentation for the latest information.

Our Journey from Cypress to Playwright for E2E Testing

Introduction

Adapting to unexpected technical challenges often leads to innovation. Our team faced such a moment when we discovered that our primary end-to-end (E2E) testing framework, Cypress, was incompatible with an essential component of our tech stack. This discovery led us to consider alternatives to resolve the compatibility issue and enhance our testing processes.

Our choice was Playwright, a modern testing framework acclaimed for its robust features and compatibility with multiple browsers. The shift to Playwright was swift and efficient: all our E2E tests were successfully migrated within a few hours. This rapid transition highlighted our team's ability to adapt and integrate new technologies quickly.

In this blog post, we will share the hands-on process we followed during the migration to Playwright, the challenges we encountered along the way, and the solutions we implemented to overcome them.

Using a Test Conversion Tool

We utilized a third-party test conversion tool to facilitate our migration from Cypress to Playwright, which significantly streamlined the process. Among several options available, we chose Ray's Cypress to Playwright Conversion Tool for its ease of use and effectiveness. This tool allowed us to automatically transpose our existing Cypress tests into the Playwright format.

The tool processes each test script by identifying equivalent functions and commands in Playwright, translating our entire suite with minimal input. Below are some before and after images of our test scripts, demonstrating the tool's conversion capabilities.

One test script written in Cypress:

describe("Newsletter page", () => {
  it("subscribing to email newsletter should show success message", () => {
    cy.visitWithTimeout("/newsletter");

    cy.getDataCy("newsletter-email-input").type(SIGN_UP_EMAIL_TEST);
    cy.getDataCy("newsletter-submit-button").wait(100).click();

    cy.contains("You subscribed to the newsletter successfully!");
  });

  it("home page should contain button to navigate to newsletter page", () => {
    cy.visitWithTimeout("/");

    // email registration signup is located at the bottom of the home page
    cy.scrollTo("bottom");

    cy.getDataCy("newsletter-sign-up-link").contains(
      "Sign up for our newsletters",
    );

    // adding a 100ms wait for safety, otherwise this command runs too fast and the click is not registered by the website
    cy.getDataCy("newsletter-sign-up-link").wait(100).click();

    cy.url().should("include", "/newsletter");
  });
});

And its result converted in Playwright:

import { test, expect } from "@playwright/test";

test.describe("Newsletter page", () => {
  test("subscribing to email newsletter should show success message", async ({
    page,
  }) => {
    page.FIXME_visitWithTimeout("/newsletter");
    page.FIXME_getDataCy("newsletter-email-input");
    await page.fill(SIGN_UP_EMAIL_TEST);
    page.FIXME_getDataCy("newsletter-submit-button");
    await page.waitForTimeout(100);
    await page.click();
    await expect(
      page.getByText(/You subscribed to the newsletter successfully!/).first()
    ).toBeVisible();
  });

  test("home page should contain button to navigate to newsletter page", async ({
    page,
  }) => {
    page.FIXME_visitWithTimeout("/");

    // email registration signup is located at the bottom of the home page
    await page.FIXME_scrollTo("bottom");
    page.FIXME_getDataCy("newsletter-sign-up-link");
    await expect(
      page.getByText(/Sign up for our newsletters/).first()
    ).toBeVisible();

    // adding a 100ms wait for safety, otherwise this command runs too fast and the click is not registered by the website
    page.FIXME_getDataCy("newsletter-sign-up-link");
    await page.waitForTimeout(100);
    await page.click();
    await expect(page).toHaveURL(/\/newsletter/);
  });
});

While the tool performed impressively, converting most of our tests accurately had limitations. Certain custom helper functions and test aliases did not translate directly, and the tool clearly marked these for manual intervention. This feature was beneficial as it allowed us to quickly identify and address the segments that required our attention.

Small Fixes and Fine-Tuning

The initial output from the conversion tool was highly useful, yet it required some tweaks to align with Playwright's capabilities perfectly. The tool conveniently marked each unconverted function with the identifier FIXME_<unconverted function>, making it straightforward to address these specific areas. Here’s how we tackled some of these functions:

FIXME_visitWithTimeout: Originally a custom Cypress command designed to visit a URL with a specified timeout (cy.visit(url, { timeout: 30000 })), this was straightforwardly converted to Playwright's page.goto(url) FIXME_getDataCy: Another custom command (cy.get('[data-cy="<a_selector>"]')) was efficiently translated to Playwright’s page.locator('[data-testid="<a_selector>"]') FIXME_scrollTo: This native Cypress command, which scrolls the window to a specific position, proved unnecessary in Playwright. Given Playwright’s automatic detection of elements on the page, we opted to remove this command entirely

import { test, expect } from "@playwright/test";

test.describe("Newsletter page", () => {
  test("subscribing to email newsletter should show success message", async ({
    page,
  }) => {
    await page.goto("/newsletter");
    await page
      .locator(`[data-testid="newsletter-email-input"]`)
      .fill(SIGN_UP_EMAIL_TEST);
    await page.locator(`[data-testid="newsletter-submit-button"]`).click();
    await expect(
      page.getByText(/You subscribed to the newsletter successfully!/).first(),
    ).toBeVisible();
  });

  test("home page should contain button to navigate to newsletter page", async ({
    page,
  }) => {
    await page.goto("/");

    await expect(
      page
        .locator(`[data-testid="newsletter-sign-up-link"]`)
        .getByText(/Sign up for our newsletters/)
        .first(),
    ).toBeVisible();

    await page.locator(`[data-testid="newsletter-sign-up-link"]`).click();
    await expect(page).toHaveURL(/\/newsletter/);
  });
});

Further refinements were made to the converted code, such as removing some "wait/delay" commands. Playwright’s robust handling of asynchronous events allowed us to streamline our tests by eliminating unnecessary pauses enhancing test execution speed and reliability.

Conclusion

The transition from Cypress to Playwright in our testing framework was driven by necessity but has significantly improved our testing practices. Utilizing a test conversion tool allowed us to migrate our entire suite of E2E tests efficiently, minimizing manual effort and accelerating the adoption of Playwright.

Our experience highlights the importance of flexibility and the willingness to embrace new technologies in the face of unexpected challenges. The marked improvements in test execution speed and reliability, along with Playwright's advanced features, have made this transition a resounding success for our team.

As we continue to refine our tests and explore the full capabilities of Playwright, we encourage other teams facing similar challenges to consider this approach. The tools and processes outlined here can serve as a roadmap for those looking to enhance their automated testing solutions.

About the author

William Mimura

William Mimura

Senior Software Engineer

Keep reading

View all posts →

Integrating Playwright Tests into Your GitHub Workflow with Vercel

Usually workflows configure Playwright to run against a project running on the GitHub action worker itself, maybe with dependencies in Docker containers as well, however why bother setting that all up and configuring yet another environment for your app...

Jamie Kuppens7 mins
VercelPlaywrightGitHub

Understanding the Difference Between `:focus` and `:focus-visible` in CSS

Understanding the Difference Between :focus and :focus visible in CSS I have learned my fair share about the importance of keyboard accessibility, so I know that visual indication of the focused element is very important. But the well known :focus pseudo class is not always the best fit for this job. That's where :focus visible comes in. Let's look at the differences between these two pseudo classes and explore the best practices for using them effectively. What is the :focus Pseudo Class? The :focus pseudo class is a CSS selector that applies styles to any element that receives focus, regardless of how that focus was triggered. This includes focus events from keyboard navigation, mouse clicks, and touch interactions. Example Usage of :focus In this example, the button will display a blue outline whenever it is focused, whether the user clicks on it with a mouse, taps it on a touchscreen, or navigates to it using the keyboard. What is the :focus visible Pseudo Class? The :focus visible pseudo class is more specialized. It only applies styles to an element when the browser determines that the focus should be visible. This typically occurs when the user navigates via the keyboard or assistive technologies rather than through mouse or touch input. Example Usage of :focus visible Here, the button will only show a blue outline when focused through keyboard navigation or another input method that usually requires visible focus indicators. Key Differences Between :focus and :focus visible :focus Behavior: Applies to any element that receives focus, regardless of the input method. Use Cases: Ensures that all interactions with the element are visually indicated, whether by mouse, keyboard, or touch. :focus visible Behavior: Applies styles only when the focus should be visible, such as using a keyboard or assistive technology. Use Cases: Ideal for scenarios where you want to provide focus indicators only to keyboard and assistive technology users while avoiding unnecessary outlines for mouse and touch users, typically required by design. Accessibility Implications :focus Pros: Guarantees that all users can see when an element is focused, which is critical for accessibility. Cons: Can lead to a suboptimal experience for mouse users, as focus styles may appear unnecessarily during mouse interactions. :focus visible Pros: Enhances user experience by showing focus indicators only when necessary, thus keeping the interface clean for mouse and touch users. Tailors the experience for keyboard and assistive technology users, providing them with clear visual cues. Cons: Additional considerations may be required to ensure that focus indicators are not accidentally omitted, especially in older browsers that do not support :focus visible. There may be cases where you want to show focus indicators for all users, regardless of input method. Best Practices for Using :focus and :focus visible To achieve the best accessibility and user experience, combining both :focus and :focus visible in your CSS is often a good idea. Combining :focus and :focus visible Here is a Stackblitz example of what such styling could look like for you to try out and play with. Additional Tips Test with Keyboard and Assistive Technology: Ensure that your web application is navigable using a keyboard (Tab, Shift + Tab, etc.) and that focus indicators are visible for those who rely on them. It's never a bad idea to include accessibility testing in your e2e testing suite. Provide Clear Focus Indicators: Make sure that focus indicators are prominent and easy to see. A subtle or hard to spot focus indicator can severely impact accessibility for users who rely on keyboard navigation. Conclusion The :focus visible pseudo class offers a more refined way to manage focus indicators, improving accessibility and user experience, particularly for keyboard and assistive technology users. By understanding the differences between :focus and :focus visible, and applying best practices in your CSS, you can create more accessible and user friendly web applications. Remember, accessibility should never be an afterthought. By thoughtfully applying focus styles, you ensure that all users, regardless of how they interact with your site, can easily navigate and interact....

Jan Kaiser3 mins
AccessibilityCSSPlaywright

How to Run End-to-End Tests on Vercel Preview Deployments

Learn how to run end-to-end (E2E) tests on Vercel preview deployments using GitHub Actions. This guide covers how to wait for deployments to be fully ready before executing tests, ensuring reliability and preventing false failures due to timing issues...

Jan Kaiser3 mins
InfrastructureVercelPlaywrightCypress

Enhancing Your Playwright Workflow: A Guide to the VSCode Extension

An introduction to the Playwright VSCode extension - a powerful tool that can streamline your end-to-end testing workflow. From installation and setup to running tests and debugging, learn how this extension can enhance your Playwright experience. ...

William Mimura5 mins
VSCodeTestingPlaywright