End-to-end testing of React application using Playwright

Dalle-3 and I visualized working on the task

Dalle-3 and I visualized working on the task

Hello, I’m Lisa, a web developer at a foreign company. I would like to share how I automated regression testing in a working React project using the fairly new Playwright framework. Let’s figure out why this particular framework, what the pitfalls are, how to bypass authorization and who uses it.

Why do we need end-to-end tests?

End-to-end (e2e) testing is a way to ensure that every step a user takes and every change to data goes smoothly. It does not replace other types of testing, but, on the contrary, complements them, providing a comprehensive check of the functionality, performance and stability of the entire application.

In our project, initially only unit tests were used, and then not in active mode and optional. There was no QA tester either, but there was a need to test the entire application and I wanted to automate important basic flows for regression testing.

Why Playwright

Playwright is an open source framework for automating end-user testing. It supports most modern browsers including Firefox, Chromium and Webkit using a single API. Playwright was created and supported by Microsoft and is gradually gaining popularity.

It’s also convenient that Playwright supports Jest, Mocha, Jasmine and other well-known continuous integration servers and provides support for various programming languages, including TypeScript, JavaScript, Python, .NET and Java.

Comparison with other frameworks

I compared this framework with other testing frameworks such as Puppeteer, Cypress and WebDriverIO. They were chosen as candidates due to their wide popularity both in the market and in my company.

Browser support for selected frameworks for analysis

Browser support for selected frameworks for analysis

As you can see, Puppeteer does not support testing in Safari, which was a critical factor for choosing a framework in our project. Cypress uses Mocha.js and is also not the best option because it does not support end-to-end testing for iOS. Additionally, experimental support for Safari in Cypress would be lacking due to missing features and known bugs (event capture and site navigation may be affected).

In the comparison between WebdriverIO and Playwright, the deciding factors were speed, video recording support, and used application languages.

 Npm download trends Playwright vs WebdriverIO

Npm download trends Playwright vs WebdriverIO

Additionally, looking at installation trends through Npm, it appears that the number of Playwright installations has recently surpassed the number of WebdriverIO installations. This trend may indicate that Playwright is likely to become the future leader among multi-browser e2e testing frameworks for web applications, which in turn will lead to an increase in the number of developers and community.

An additional decisive factor was that our project had already written unit tests in Jest before, and Playwright had beta support for component testing. Using it, it would be possible to transfer unit tests to the same framework and debug them in a real browser with breakpoints instead of a virtual DOM tree in the case of Jest.

Project preparation

We decided to use Playwright for e2e and potentially component testing, what next?

Setting up for end-to-end testing

For full testing you need to install the playwright library as in documentation. We use yarn

yarn create playwright

What will be downloaded:

playwright.config.ts
package.json
package-lock.json
tests/
  example.spec.ts
tests-examples/
  demo-todo-app.spec.ts

One of the important details here is playwright.config.ts. In this file you can configure the launch of tests specifically for your project and needs.

For example, configure browsers (in our case only Chrome):

projects: [
  {
     name: 'chromium',
     use: { ...devices['Desktop Chrome'] },
  },
],

Set up a specific URL if you need to go to the project not locally:

use: {
  trace: 'on-first-retry',
  baseURL: process.env.PLAYWRIGHT_URL ?? 'http://localhost:1234',
},

Set a timeout that will be executed before each check in tests:

expect: {
  timeout: 5000,
},

Setup for component testing (beta)

Described in a separate section documentation.

To install you need to run the following commands:

yarn create playwright --ct
yarn add @playwright/experimental-ct-react

This is where I discovered the first problem: component testing assumes that the project has React 18 installed (bug). Our version is 17, so you need to install it

yarn add @playwright/experimental-ct-react17

It is better to create a separate configuration file for component testing and configure a different path there for unit tests, so as not to be confused with end-to-end.

Writing tests

Next, if everything went like this, you can start writing tests.

An example of a full-fledged dummy test:

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

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  await page.getByRole('link', { name: 'Get started' }).click();

  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

Let’s launch

npx playwright test --ui

With parameter --ui the tests will open in a separate interface window. In addition to running tests, you can stop them anywhere, select any page element with a special locator picker, and watch the test progress step by step in a convenient timeline. I haven’t worked in these modes with other tests, so I just really like this one. I take an example interface from the documentation in order to maintain the privacy of our project.

 An example of an interface for running tests on Playwright

An example of an interface for running tests on Playwright

Problem Handling

What could go wrong after this? Let’s look at a few problems I encountered. The existing legacy code entailed the inability to use .js files with JSX code in them, as well as files and components that have default exports.

The first problem with .js file resolution can be solved by adding a setting to the config:

/* Vite config */

ctViteConfig: {
  optimizeDeps: {
  force: true,
  esbuildOptions: {
    loader: {
      '.js': 'jsx',
      },
    },
  },
},

Similar settings are suitable for projects that use the Vite server. Inside ctViteConfig You can add other constructs that can help with component testing. I tried to configure it for the second problem with default exports, but so far the only solution was to mechanically change each default export to a named one.

Double Google authentication

The second, more serious problem was double authentication, which we have in the project and was needed to open the application in tests. Double authentication means that after entering data into a Google form, you then need to confirm your identity in a second way, in our case from your phone. And this, in turn, means that it will be impossible to fully automate tests (integrate them into CI/CD) in this way.

Possible workarounds:

  • make mock requests from the backend so that authentication is not required to execute them (but then this is a dishonest end-to-end);

  • create a test user and disable double authentication for him (this turned out to be difficult due to the specifics of the project);

  • try to save the authorization token of a specific user in a local .env file and not commit it to the repository (this violates the privacy of a specific living user with his data);

  • create a separate environment on the backend specifically for tests with lightweight queries (this can be difficult and time-consuming).

In our case, it turned out that someone in the project had previously made a workaround by creating a temporary user with the data necessary for requests, under which you can log in without authorization. Thus, there was hope that running tests could be implemented in CI on Github.

Integration with CI/CD

This stage is still in the process of implementation due to the specifics of the project and the issue of the need to run all tests on each pull request. There are definitely advantages in such integration for tasks of medium and high complexity: regression tests will pass all basic flows and the number of undetected errors, bugs in production code and incidents will be reduced.

Implementation possibilities also exist, for example, running test cases on a stand already assembled in CI with the code of a branch of a specific pull request. But for now more information can be found Here. At this stage, we run tests locally when adding new functionality, and this really affects the project for the better, and also helps us better navigate the logic and add more coverage.

In addition, you can also rewrite existing Jest unit tests in Playwright to be able to visually look at individual components and debug the real DOM tree instead of the virtual one.

Useful tips and tricks

For me, this was the first experience of implementing a testing framework into a project from scratch. With the help of a lot of research, googling, communication with people from the team and the company as a whole, I was able to come to a rough understanding of where to move.

I am sure that each project has its own approach, its own technologies, so I think initially we can rely on the following things:

  • compatible with your technology stack

  • browser and platform support

  • test execution speed

  • community and support

I hope my experience will inspire other developers to consider Playwright as a testing framework. At the end of the day, our goal as developers is to create apps that are easy and enjoyable to use, and good testing is one of the keys to achieving this goal.

Sources and additional materials

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *