Skip to content

Our Journey from Cypress to Playwright for E2E Testing

Our Journey from Cypress to Playwright for E2E Testing

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources 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.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Quick Guide to Playwright Fixtures: Enhancing Your Tests cover image

Quick Guide to Playwright Fixtures: Enhancing Your Tests

Introduction Following our recent blog post on migrating E2E tests from Cypress to Playwright, we've identified opportunities to enhance our test scripts further. In this guide, we'll delve into the basics of Playwright fixtures, demonstrating their utility and flexibility in test environments. Playwright fixtures are reusable components that set up and tear down the environment or conditions necessary for tests. They are crucial for writing clean, maintainable, and scalable tests. Fixtures can handle tasks like opening a browser, initializing a database, or logging into an application—actions you might need before running your tests. As a practical example, we'll revisit one of the tests from our previous post, enhancing it with a new fixture to streamline the testing process and significantly improve maintainability. This post is designed to provide the foundational skills to integrate fixtures into your testing workflows effectively, giving you the confidence to manage and maintain your test scripts more efficiently. Creating Our First Fixture To illustrate the power of fixtures in Playwright, let’s consider a practical example from a test scenario in our project. Below is a snippet of a test case from our newsletter page: ` This test scenario navigates to the newsletter page, fills in an email, submits the form, and checks for a success message. To optimize our test suite, we'll refactor common actions like navigation, form completion, and submission into reusable fixtures. This approach makes our tests cleaner and more maintainable and reduces redundancy across similar test scenarios. Implementing the Fixture Here’s how the fixture looks: ` This fixture encapsulates the actions of navigating to the page, filling out the email field, and submitting the form. By abstracting these actions, we simplify and focus our test cases. Refactoring the Test With the fixture in place, let’s see how it changes our original test file: ` A beforeEach method to reset the state of the NewsletterPage fixture ensures a clean and consistent environment for each test scenario. This practice is crucial for maintaining the integrity and reliability of your tests. By leveraging the NewsletterPage fixture, each test within the "Newsletter page" suite starts with a clean and pre-configured environment. This setup improves test clarity and efficiency and aligns with best practices for scalable test architecture. Conclusion As we've seen, fixtures are powerful tools that help standardize test environments, reduce code redundancy, and ensure that each test operates in a clean state. By abstracting common setup and teardown tasks into fixtures, we can focus our testing efforts on what matters most, verifying the behavior and reliability of the software we're developing. Remember, the key to successful test management is choosing the right tools and using them wisely to create scalable, maintainable, and robust testing frameworks. Playwright fixtures offer a pathway towards achieving these goals, empowering teams to build better software faster and more confidently....

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

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

Enhancing Your Playwright Workflow: A Guide to the VSCode Extension cover image

Enhancing Your Playwright Workflow: A Guide to the VSCode Extension

Introduction In my last post, Quick Guide to Playwright Fixtures: Enhancing Your Tests, I delved into some of the enhancements we've been implementing in our end-to-end (E2E) tests using Playwright. As I refine our testing strategies, I've come across a tool that has quickly become an essential part of my workflow: the Playwright VSCode extension. If you're like me and constantly looking for ways to streamline testing and debugging, you'll appreciate any tool that can make the process more efficient and enjoyable. That's where this extension comes in. It's not just about writing tests - it's about enhancing the entire development experience. In this post, I'll walk you through getting started with the Playwright VSCode extension, sharing some tips and tricks that have made a real difference in my day-to-day work. Installing the Extension & Basic Setup Before diving into the Playwright VSCode extension, it's essential to have Playwright installed on your machine. If you haven't done so already, you can quickly install it by running: ` This command will set up Playwright and ensure all necessary dependencies are installed. Once Playwright is ready, the VSCode extension will be installed next. Open Visual Studio Code, navigate to the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of the window, and search for “Playwright”. The official extension, ID: ms-playwright.playwright / named: “Playwright Test for VSCode” by Microsoft, should appear at the top of the list. Click "Install," and you're all set. With the extension installed, you can start leveraging its powerful features to enhance your Playwright testing workflow within VSCode. Running the Tests and Identifying Outputs To run a test, simply open the test file in VSCode. The Playwright extension will automatically detect test files and display a "Run" icon next to each test and test suite. You can click on this icon to run individual tests or test suites. Alternatively, you can run all the tests in your project using the Playwright Test Explorer, accessible from the sidebar. Once you start running your tests, the extension provides real-time feedback within the editor. You'll see the status of each test - whether it passes, fails, or is skipped - right next to the corresponding test in your code. This immediate feedback loop is incredibly helpful for catching issues as you write your tests. The output of your tests will be displayed in the VSCode terminal. You'll see detailed information about each test run. Debugging Step-by-Step I find debugging particularly useful when a test fails unexpectedly or when I want to verify that certain actions are being performed as intended. Instead of guessing what might be wrong, I can see exactly what's going on in each test step, making debugging a much more straightforward and less frustrating process. To start debugging, you can easily set a breakpoint in your test file by clicking on the left margin next to the line number where you'd like the execution to pause. Once your breakpoints are in place, you can initiate the debug process by selecting the "Debug" option next to the test you'd like to investigate. Once the debugger is running, the extension allows you to step through your code, inspect variables, and evaluate expressions - all within VSCode. This real-time insight into your test execution is a game-changer, enabling you to pinpoint issues more effectively and confidently refine your tests. Using the Pick Locator Tool Another handy feature is the "Pick Locator" tool. If you've ever struggled with selecting the right element in your tests, this tool can be a time saver. It helps you generate reliable locators by letting you interact directly with the webpage elements you want to target. To use the Pick Locator tool, click the "Pick Locator" button in the Playwright Test Explorer. This will open a new window where you can navigate to the site you're testing. As you hover over elements on the page, the tool will suggest locators, allowing you to select the most appropriate one for your test. While the Pick Locator tool is handy, it’s important to ensure that the locators you generate are robust and maintainable. This is especially true when integrating them with the fixtures I discussed in my previous blog post. Combining the proper locators with well-designed fixtures can create more reliable and reusable test setups, ultimately making your E2E tests more efficient. Conclusion The Playwright VSCode extension has quickly become indispensable in my development workflow. It significantly enhanced my experience of writing and running Playwright tests. Whether you’re just starting with Playwright or looking to optimize your existing tests, this extension offers a range of features that can save you time and effort. Combining these tools with thoughtful test design, such as leveraging fixtures, you can create a more efficient and effective testing process. I hope this guide has given you a good overview of what the Playwright VSCode extension can do and how it can benefit your work. If you haven’t tried it yet, I highly recommend giving it a go. And as always, feel free to explore further and experiment with the features that best suit your needs....

Understanding Sourcemaps: From Development to Production cover image

Understanding Sourcemaps: From Development to Production

What Are Sourcemaps? Modern web development involves transforming your source code before deploying it. We minify JavaScript to reduce file sizes, bundle multiple files together, transpile TypeScript to JavaScript, and convert modern syntax into browser-compatible code. These optimizations are essential for performance, but they create a significant problem: the code running in production does not look like the original code you wrote. Here's a simple example. Your original code might look like this: ` After minification, it becomes something like this: ` Now imagine trying to debug an error in that minified code. Which line threw the exception? What was the value of variable d? This is where sourcemaps come in. A sourcemap is a JSON file that contains a mapping between your transformed code and your original source files. When you open browser DevTools, the browser reads these mappings and reconstructs your original code, allowing you to debug with variable names, comments, and proper formatting intact. How Sourcemaps Work When you build your application with tools like Webpack, Vite, or Rollup, they can generate sourcemap files alongside your production bundles. A minified file references its sourcemap using a special comment at the end: ` The sourcemap file itself contains a JSON structure with several key fields: ` The mappings field uses an encoding format called VLQ (Variable Length Quantity) to map each position in the minified code back to its original location. The browser's DevTools use this information to show you the original code while you're debugging. Types of Sourcemaps Build tools support several variations of sourcemaps, each with different trade-offs: Inline sourcemaps: The entire mapping is embedded directly in your JavaScript file as a base64 encoded data URL. This increases file size significantly but simplifies deployment during development. ` External sourcemaps: A separate .map file that's referenced by the JavaScript bundle. This is the most common approach, as it keeps your production bundles lean since sourcemaps are only downloaded when DevTools is open. Hidden sourcemaps: External sourcemap files without any reference in the JavaScript bundle. These are useful when you want sourcemaps available for error tracking services like Sentry, but don't want to expose them to end users. Why Sourcemaps During development, sourcemaps are absolutely critical. They will help avoid having to guess where errors occur, making debugging much easier. Most modern build tools enable sourcemaps by default in development mode. Sourcemaps in Production Should you ship sourcemaps to production? It depends. While security by making your code more difficult to read is not real security, there's a legitimate argument that exposing your source code makes it easier for attackers to understand your application's internals. Sourcemaps can reveal internal API endpoints and routing logic, business logic, and algorithmic implementations, code comments that might contain developer notes or TODO items. Anyone with basic developer tools can reconstruct your entire codebase when sourcemaps are publicly accessible. While the Apple leak contained no credentials or secrets, it did expose their component architecture and implementation patterns. Additionally, code comments can inadvertently contain internal URLs, developer names, or company-specific information that could potentially be exploited by attackers. But that’s not all of it. On the other hand, services like Sentry can provide much more actionable error reports when they have access to sourcemaps. So you can understand exactly where errors happened. If a customer reports an issue, being able to see the actual error with proper context makes diagnosis significantly faster. If your security depends on keeping your frontend code secret, you have bigger problems. Any determined attacker can reverse engineer minified JavaScript. It just takes more time. Sourcemaps are only downloaded when DevTools is open, so shipping them to production doesn't affect load times or performance for end users. How to manage sourcemaps in production You don't have to choose between no sourcemaps and publicly accessible ones. For example, you can restrict access to sourcemaps with server configuration. You can make .map accessible from specific IP addresses. Additionally, tools like Sentry allow you to upload sourcemaps during your build process without making them publicly accessible. Then configure your build to generate sourcemaps without the reference comment, or use hidden sourcemaps. Sentry gets the mapping information it needs, but end users can't access the files. Learning from Apple's Incident Apple's sourcemap incident is a valuable reminder that even the largest tech companies can make deployment oversights. But it also highlights something important: the presence of sourcemaps wasn't actually a security vulnerability. This can be achieved by following good security practices. Never include sensitive data in client code. Developers got an interesting look at how Apple structures its Svelte codebase. The lesson is that you must be intentional about your deployment configuration. If you're going to include sourcemaps in production, make that decision deliberately after considering the trade-offs. And if you decide against using public sourcemaps, verify that your build process actually removes them. In this case, the public repo was quickly removed after Apple filed a DMCA takedown. (https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md) Making the Right Choice So what should you do with sourcemaps in your projects? For development: Always enable them. Use fast options, such as eval-source-map in Webpack or the default configuration in Vite. The debugging benefits far outweigh any downsides. For production: Consider your specific situation. But most importantly, make sure your sourcemaps don't accidentally expose secrets. Review your build output, check for hardcoded credentials, and ensure sensitive configurations stay on the backend where they belong. Conclusion Sourcemaps are powerful development tools that bridge the gap between the optimized code your users download and the readable code you write. They're essential for debugging and make error tracking more effective. The question of whether to include them in production doesn't have a unique answer. Whatever you decide, make it a deliberate choice. Review your build configuration. Verify that sourcemaps are handled the way you expect. And remember that proper frontend security doesn't come from hiding your code. Useful Resources * Source map specification - https://tc39.es/ecma426/ * What are sourcemaps - https://web.dev/articles/source-maps * VLQ implementation - https://github.com/Rich-Harris/vlq * Sentry sourcemaps - https://docs.sentry.io/platforms/javascript/sourcemaps/ * Apple DMCA takedown - https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co