automative

by

photo

Cypress vs Playwright

October 1, 2024

When I first started my journey in test automation, my toolset primarily was only Cypress. I started by writing very basic simple tests without knowing best practices to writing really complex test cases. During that time I was curious also about JavaScript and was learning on that part. After creating fundamental skills in JS, I had questions about Cypress like what you can test, how it works, and other questions. Cypress is known for its easy-to-use syntax and ideal for end-to-end testing. While Playwright feels similar, for a newcomer in this world, from my perspective, Cypress is easier than Playwright.

In this article, we'll compare Cypress and Playwright, two popular tools in e2e testing, by examining their key features, use cases, and performance. This comparison will help you decide which tool is the best fit for your testing needs.


Cypress

Cypress is an end-to-end testing framework designed to make testing modern web applications easier and more reliable. It allows developers and testers to write tests that interact with a web application just like a user would, entering text, navigating pages, and more. Cypress is popular for testing SPAs and supports testing front-end behavior, but it is limited to the browser environment (we will talk more about that later).

Playwright

Playwright is an open-source testing framework developed by Microsoft that allows automated testing of web applications across multiple browsers. Playwright supports all modern engines including Chromium, WebKit, and Firefox. It's super easy to install.

During my time at my previous roles, I had the chance to develop strong skills in both Cypress and Playwright. In my first role, Cypress was already integrated with a few tests, and as a beginner, I found it easy to get started. Later, at my next company, Cypress had around 2000 tests integrated. Although Playwright wasn’t fully integrated at that point, I took the initiative to learn and experiment with it, despite having limited prior experience.


Downloads Comparison

Monthly downloads comparison between Cypress and Playwright (2024)


Comparing Playwright and Cypress

Playwright and Cypress are both powerful tools for end-to-end testing, but their features and use cases differ significantly. Here's a detailed comparison of these two frameworks based on key aspects:

  1. Cross-Browser Support:

    Playwright supports Chromium, Firefox, and WebKit (Safari), offering extensive cross-browser testing capabilities.

    Cypress, while strong in Chromium-based browsers like Chrome and Edge, fully supports Firefox. However, WebKit support remains experimental. Cross-browser compatibility in Cypress requires the browsers to be installed locally, a condition that applies to Playwright as well.

  2. Supported Platforms:

    Playwright supports mobile browser emulation for Android and iOS, alongside cross-operating system compatibility. Cypress also supports mobile emulation but requires manual configuration of viewport sizes rather than using predefined device names.

  3. Performance and Parallelization:

    Playwright excels in parallel test execution across multiple browsers, reducing test suite runtimes effectively. Cypress offers parallelization through cloud-based CI/CD pipelines. Local parallelization is supported via plugins.

  4. Network Interception and Control:

    Playwright: Offers advanced network interception capabilities using page.route(). You can modify requests before they are sent to the server, such as changing HTTP methods, headers, or redirecting requests to mock services. Playwright also provides seamless mocking of responses directly in the request flow.
    Cypress: Allows request interception with cy.intercept() and supports pre-request modifications like altering headers, methods, or mocking responses. Cypress also enables redirecting requests to mock services. However, Playwright may offer slightly more granular control in some cases, especially for advanced scenarios involving dynamic routing. I will discuss this advanced control and dynamic routing in more detail in the next section.

    // Playwright: Network Interception
    await page.route('**/api/v1/users', (route) => {
      // Modify the request before it reaches the server
      route.continue({
        method: 'POST',
        headers: {
          ...route.request().headers(),
          'x-custom-header': 'PlaywrightIntercept',
        },
      });
    });
    
    // Mocking a response directly
    await page.route('**/api/v1/users', (route) => {
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ success: true, message: 'Mocked Response' }),
      });
    });
    
    // Cypress: Network Interception
    cy.intercept(
      {
        method: 'GET', // Match specific method
        url: '**/api/v1/users', // Match the request URL
      },
      (req) => {
        // Modify the request before sending
        req.headers['x-custom-header'] = 'CypressIntercept';
        req.method = 'POST';
    
        // Mock response
        req.reply({
          statusCode: 200,
          body: { success: true, message: 'Mocked Response' },
        });
      }
    );
    
    // Verify interception
    cy.intercept('POST', '**/api/v1/users').as('createUser');
    cy.visit('/users');
    cy.wait('@createUser').then((interception) => {
      expect(interception.response.statusCode).to.equal(200);
      expect(interception.response.body.message).to.equal('Mocked Response');
    });
    
  5. Dynamic Request Routing and Granular Control:

    In some cases, Playwright offers more granular control, especially when dealing with advanced scenarios such as dynamic routing. You can conditionally modify requests and responses based on runtime data, URL parameters, or other dynamic factors. This flexibility is especially useful for testing complex APIs with multiple endpoints and variable parameters. Cypress does support network interception, but its routing flexibility is more static compared to Playwright's dynamic approach.

    // Playwright: Dynamic Request Routing and Advanced Interception
    await page.route('**/api/v1/users/**', (route, request) => {
      // Conditionally modify request based on dynamic URL parameters
      if (request.url().includes('specificUserId')) {
        route.continue({
          method: 'GET',
          headers: {
            ...request.headers(),
            'x-user-id': 'specialUser123'
          }
        });
      } else {
        // Handle generic requests
        route.continue();
      }
    });
    
    // Mocking a response dynamically
    await page.route('**/api/v1/users/**', (route) => {
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          success: true,
          message: route.request().url().includes('special') ? 'Special User Mock' : 'Generic Mock'
        }),
      });
    });
    
    // Cypress: Network Interception (with static routing)
    cy.intercept(
      {
        method: 'GET',
        url: '**/api/v1/users',
      },
      (req) => {
        // Modify request headers and mock response
        req.headers['x-custom-header'] = 'CypressIntercept';
        req.method = 'POST';
        req.reply({
          statusCode: 200,
          body: { success: true, message: 'Mocked Response' },
        });
      }
    );
    
    // Cypress allows basic conditional interception, but lacks the same dynamic routing flexibility as Playwright
    cy.intercept('POST', '**/api/v1/users').as('createUser');
    cy.visit('/users');
    cy.wait('@createUser').then((interception) => {
      expect(interception.response.statusCode).to.equal(200);
      expect(interception.response.body.message).to.equal('Mocked Response');
    });
    
  6. Multi-Tab Handling:

    Playwright offers native support for handling multiple tabs or windows, making it ideal for complex navigation flows. Cypress, however, lacks native multi-tab support and relies on workarounds, such as forcing new tabs to open in the same window.

    // Playwright: Native Multi-Tab Handling
    
    // Open a new tab
    const [newPage] = await Promise.all([
      context.waitForEvent('page'),
      page.click('a[target="_blank"]') // Click link opening a new tab
    ]);
    
    // Interact with the new tab
    await newPage.waitForLoadState();
    await newPage.type('#input', 'Playwright multi-tab test');
    await newPage.click('button.submit');
    
    // Switch back to the original tab
    await page.bringToFront();
    
    // Cypress: Workaround for Multi-Tab Handling
    
    // Force links to open in the same tab by removing 'target="_blank"'
    cy.get('a[target="_blank"]').invoke('removeAttr', 'target').click();
    
    // Interact with the newly loaded page
    cy.url().should('include', '/new-tab');
    cy.get('#input').type('Cypress single-tab workaround');
    cy.get('button.submit').click();
    
  7. Test Isolation:

    Both frameworks support test isolation by default. The perception of weaker isolation in Cypress often stems from tester practices rather than the tool itself.

  8. Programming Language Support:

    Cypress supports JavaScript and TypeScript, while Playwright extends its capabilities to additional languages, including Python, Java, and C#, making it a better choice for teams working in diverse language environments.

  9. Debugging Tools:

    Cypress simplifies debugging with its interactive Test Runner and time-travel snapshots. The .debug() command is particularly useful for inspecting the state during test execution.

    Playwright, on the other hand, integrates deeply with tools like VS Code, providing powerful debugging options such as breakpoints and a trace viewer.


Debugging

Debugging in Cypress and Playwright offers different approaches. In Cypress, you’re limited to using .pause() or.debug() on selected elements. When you use.debug(), Cypress will freeze the test and print the selector results in the Chrome console. You can then hover over the HTML element in Chrome to check if your selector is working.

However, the issue is that you can only see the value for that particular line. You don’t get access to any other states created by the test code. Adding breakpoints or using the debugger statement doesn’t help much because Cypress runs all code first and handles the rest asynchronously. If you want to inspect async code, you’d need to place the debugger inside a resolve function to make sure the code has finished. But even then, you can only see the value for that line, and you can't check the rest of the test state.

Another option is to use a third-party library like Promisify, which converts Cypress’s async functionality into Promises. This allows you to add debugger statements after await to inspect variables that have been awaited. The problem with this approach is that Cypress wasn’t designed to work with async/await, so you may run into issues. I tried this and faced several problems, so I switched back to using Cypress's built-in .debug() function.

Playwright, on the other hand, integrates well with Visual Studio Code and fully supports promises and async/await. You can debug your tests directly in VSCode, stop at any point, and check the state of any variable. It even highlights DOM elements in Chromium when you hover over a selector expression in VSCode (it works with multi-dot selectors too!).

Playwright also has its own inspector where you can run tests step by step while watching them in Chromium. This allows you to evaluate selectors in real-time while the test is paused.


Powerful Selectors & Nested Selectors

Both Cypress and Playwright support powerful ways to select elements on a page. While they share many similarities, the key difference lies in how selectors are managed and how each framework approaches deeply nested structures.Playwright provides native support for various selectors such as CSS, XPath, ARIA roles, and text-based queries, which offers developers more flexibility and power. On the other hand, Cypress can achieve similar results using the open-source Testing Library plugin, requiring additional setup but providing comparable functionality.

Powerful Selectors

// Playwright Selector Examples

// CSS Selector
await page.click('button.submit-btn');

// Text Selector
await page.click('text="Submit"');

// XPath Selector
await page.locator('//button[text()="Submit"]').click();

// ARIA Role Selector
await page.getByRole('button', { name: 'Submit' }).click();
    
// Cypress with Testing Library Selector Example

// Using Testing Library's findByRole
cy.findByRole('button', { name: /submit/i }).click();
    
// Using Testing Library's findByText
cy.findByText('Submit').click();

// Basic Cypress CSS Selector
cy.get('button.submit-btn').click();

Playwright natively supports a variety of selectors, including CSS, text, XPath, and ARIA roles. In contrast, Cypress requires the use of the Testing Library for role, label, and text-based selectors, but once set up, the functionality is comparable.


Conclusion

Both tools are powerful, and the choice often depends on specific project needs. Cypress is ideal for simple to moderately complex web apps with a focus on ease of use and a strong developer experience. Playwright is better suited for more complex scenarios, especially if cross-browser testing, multi-page interactions, or deep network control are required.