End-To-End React Apps Testing Made Easy With Puppeteer
Published: 2023-12-28 12:45:48

by [Emmanuel Uchenna](https://blog.openreplay.com/authors/emmanuel-uchenna)


<blockquote><em>
Testing is mandatory in software development, and many possible tools exist for that task. This article will explore how Puppeteer makes automated testing simple and effective and will teach you how to write simple, efficient E2E tests for your React applications and how to use Puppeteer to carry out tests from a user’s perspective.
</em></blockquote>

<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>

Software testing is an important aspect of [the software development life cycle (SDLC)](https://blog.openreplay.com/software-testing-life-cycle/). It is imperative to test your applications as doing so helps you identify bugs and defects early with the written code and allows you to build quality and standard software that works as expected.

However, many organizations still rely heavily on inefficient manual testing methods. The statistics make a compelling case for implementing more automated testing. Research indicates that [56% of defects are discovered via in-house testing](https://diffy.website/blog/QA-testing-web-development-statistics), yet only 5% of companies carry out fully automated testing. Two-thirds conduct tests in a [75:25 (manual:automation) ratio or 50:50](https://truelist.co/blog/software-testing-statistics/).

As the software testing market is [projected to grow at a 7% CAGR between 2021-2026](https://truelist.co/blog/software-testing-statistics), test automation is key to keeping up. A [research](https://truelist.co/blog/software-testing-statistics/) found that 79% of end users still encountered bugs in 2019, showing the need for better testing. With 70% of IT leaders affirming [that automated testing leads to greater efficiency](https://blog.openreplay.com/testing-principles-levels-and-libraries/), the trend toward automation is only increasing. Solutions like [Puppeteer](https://pptr.dev/) enable streamlined end-to-end (E2E) testing that can help organizations and companies improve software quality and user experiences.


Let's dive right in 🚀

## What is Puppeteer?

[Puppeteer](https://pptr.dev/) is a Node.js library developed by Google that automates the process of front-end testing. It provides high-level API to control Chrome or Chromium-based browsers over the DevTools Protocol. It allows testers to carry out headless as well as headful actions.

### Headless vs. Headful Browsers Actions

> A headless browser is a browser without a graphical user interface (GUI). They are executed via a command-line interface or using network communication. - [WikiPedia](https://en.wikipedia.org/wiki/Headless_browser)

Puppeteer allows controlling Chrome or Chromium in a headless mode, where the browser user interface is not displayed, or in a headful mode, where the browser UI is shown. Although the headless mode is the default, a headful mode can be useful for debugging. Websites also find it more difficult to detect headful mode browsers controlled by scripts as bots compared to headless mode browsers.

Puppeteer finds application in:

* Automating most UI tests, including keyboard and mouse movements
* Performing end-to-end testing
* Web page crawling and scraping
* Generating screenshots and PDFs
* Capturing a timeline trace of your site to help diagnose performance issues
* Automating form submission

## What is Jest?

[Jest](https://jestjs.io/) is an open-source [JavaScript testing framework](https://blog.openreplay.com/ten-must-have-tools-for-front-end-developers/) designed to test React and React Native web applications.

Usually, E2E tests on the front end of software are too cumbersome due to the required extensive, time-consuming configuration. However, testing with Jest reduces the complexities of front-end testing by minimizing the configuration required for unit tests.

## Setting Up Your Project

In this section, you will configure a React application with Puppeteer and Jest. You will start by bootstrapping a [new React application](https://blog.openreplay.com/react-101--getting-started-with-create-react-app/) with [CRA (Create React App)](https://create-react-app.dev/).

Run the following commands:

```bash
npx create-react-app react-puppeteer-test
```

Next, once the project directory and your `react-puppeteer-test` application have been created, navigate to the newly created directory and run the command below in your terminal to install the following packages:

* Jest - Our test runner
* Puppeteer - Our browser automation tool
* [Jest-Puppeteer](https://www.npmjs.com/package/jest-puppeteer) - A Jest plugin to run Puppeteer tests

```bash
npm install puppeteer jest jest-puppeteer
```

Once your app is set up, run the application by entering either of these commands in your terminal:

![Install packages](https://blog.openreplay.com/images/e2e-react-testing-with-puppeteer/images/image.png)

```shell
yarn start
```

Or using npm:

```shell
npm start
```

Your server should launch in your browser at [http://localhost:3000/](http://localhost:3000/), as shown below:

![React app](https://blog.openreplay.com/images/e2e-react-testing-with-puppeteer/images/68747470733a2f2f692e696d6775722e636f6d2f3354394130514a2e706e67.png)

## Configuring Jest Settings

Jest, by default, works great, but sometimes you just need more configuration power. To start, set up the Jest configuration by creating a file named `jest.config.js` in your project's root directory. Populate the file with the following code snippet:

```javascript
module.exports = {
  preset: "jest-puppeteer",
  testMatch: ["**/src/__tests__/**/*.test.js"],
  verbose: true,
};
```

This configuration instructs Jest to utilize the `jest-puppeteer` preset and locate tests within the `__tests__` folder.

Add a corresponding script to the `package.json` file:

```json
"scripts": {
  "test": "jest"
}
```

This command instructs Jest to handle the test script. You have successfully configured Jest in your application.

## Configure Puppeteer Settings

Next, create a file named `jest-puppeteer.config.js` in your project's root folder and paste the following snippet to create the Puppeteer configuration:

```javascript
module.exports = {
  launch: {
    headless: process.env.HEADLESS !== "false",
    slowMo: process.env.SLOWMO ? parseInt(process.env.SLOWMO, 10) : 0,
    devtools: process.env.DEVTOOLS === "true",
    product: "chrome",
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-dev-shm-usage",
      "--disable-accelerated-2d-canvas",
      "--disable-gpu",
      "--window-size=1920,1080",
    ],
  },
};
```

This configuration specifies running Puppeteer in headless mode, avoiding sandbox usage, disabling GPU, and setting the window size. It also allows customization of the browser product (Chrome or Firefox) and introduces the option to slow down test execution for better visibility.

## Examples Tests with Puppeteer

For a basic test using Puppeteer and Jest, consider the following example using the [OpenReplay website](https://openreplay.com/) homepage.

In this tutorial, I will walk you through carrying out your first automated test using Puppeteer. If you read this section, you have installed and set up Puppeteer in your project. If you have not, please follow the instructions above. If you have, let’s dive in.

1. **Create your test files**: You must create your actual test file to carry out your test. You must properly structure your test file in some directories to do that. Run the following command in your terminal:

```bash
cd src && mkdir __tests__ && cd __tests__ && touch homepage.test.js
```

What the above command does is:

* Change the directory into the `src` folder;
* Change the directory into the `__tests__` folder;
* Create a new file named `homepage.test.js`

```javascript
// src/__tests__/homepage.test.js
const puppeteer = require("puppeteer");

describe("OpenReplay.com page", () => {
  let browser;
  let page;

  beforeAll(async () => {
    browser = await puppeteer.launch();
    page = await browser.newPage();
  });

  it("should contain text", async () => {
    await page.goto("https://openreplay.com/", { waitUntil: 'networkidle2' });
    await page.waitForSelector(".display-4");
    const text = await page.$eval(".display-4", (e) => e.textContent);
    expect(text).toContain(`Session replay`);
  });

  afterAll(() => browser.close());
});
```

What this code above ⬆ does is:

* You started by describing your E2E test
* You navigate to the OpenReplay website
* You are testing that the class `display-4` contains the text `Session replay` on the website. The `beforeAll` function initiates a Puppeteer instance, creating a browser and a page. The test checks for the presence of the specified text using the page's selector.

![Test Openreplay website](https://blog.openreplay.com/images/e2e-react-testing-with-puppeteer/images/image1.png)

This serves as a template for creating custom tests. Now, let's explore a practical scenario.

## Real-World Example: Testing a Login Form

Consider a login form component:

```javascript
// App.js
import { useState } from "react";

import "./App.css";

function App() {
  const [isUserLoggedIn, setIsUserLoggedIn] = useState(false);
  const [error, setError] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const login = (event) => {
    event.preventDefault();

    if (email === "admin@openreplay.com" && password === "password") {
      setIsUserLoggedIn(true);
    } else {
      setIsUserLoggedIn(false);
      setError(true);

      setTimeout(() => {
        setError(false);
      }, 4000);
    }
  };

  return (
    <div className="App">
      <div className="form-wrapper">
        <h1 className="form-header">Welcome back</h1>

        {!isUserLoggedIn && (
          <form className="form" onSubmit={login}>
            {error && (
              <p className="form-error-text">
                Invalid email or password
              </p>
            )}

            <input
              type="email"
              required
              placeholder="Email Address"
              className="form-input form-input__email"
              onChange={(e) => {
                setEmail(e.target.value);
              }}
              name="emailAddress"
            />
            <input
              type="password"
              required
              placeholder="Password"
              className="form-input form-input__password"
              onChange={(e) => {
                setPassword(e.target.value);
              }}
              name="password"
            />

            <button type="submit" className="form-submit-button">
              Submit
            </button>
          </form>
        )}

        {isUserLoggedIn && (
          <p className="form-success-message">Login successful.</p>
        )}
      </div>
    </div>
  );
}

export default App;
```

In the code above, we are emulating a login scenario where:

1. We initialized state variables `isUserLoggedIn`, `error`, `email`, and `password` using the `useState` hook.
2. The `login` function handles form submission, checking credentials, and updating state accordingly.
3. JSX renders a form with email, password inputs, and a submit button based on the user's login status.
4. An error message is displayed if the login attempt fails, disappearing after 4 seconds.
5. Login only succeeds if the username is `admin@openreplay.com` and the password is `password`
6. A success message is displayed when the user is logged in.
7. The component is exported as the default export for use in other parts of the application.

![Sign In form](https://blog.openreplay.com/images/e2e-react-testing-with-puppeteer/images/test-login-form.gif)

```javascript
// App.test.js
import puppeteer from "puppeteer";

describe("App.js", () => {
  let browser;
  let page;

  beforeAll(async () => {
    browser = await puppeteer.launch();
    page = await browser.newPage();
  });

  it("shows a success message after submitting a form", async () => {
    await page.goto("http://localhost:5000");
    await page.waitForSelector(".form-header");

    await page.click(".form-input__email");
    await page.type(".form-input__email", "admin@openreplay.com");

    await page.click(".form-input__password");
    await page.type(".form-input__password", "password");

    await page.click(".form-submit-button");

    await page.waitForSelector(".form-success-message");
    const text = await page.$eval(
      ".form-success-message",
      (e) => e.textContent
    );

    expect(text).toContain("Login successful.");
  });

  it("shows an error message if authentication fails", async () => {
    await page.goto("http://localhost:5000");
    await page.waitForSelector(".form-header");

    await page.click(".form-input__email");
    await page.type(".form-input__email", "admin@openreplay.com");

    await page.click(".form-input__password");
    await page.type(".form-input__password", "password123");

    await page.click(".form-submit-button");

    await page.waitForSelector(".form-error-text");
    const text = await page.$eval(".form-error-text", (e) => e.textContent);

    expect(text).toContain("Invalid email or password");
  });

  afterAll(() => browser.close());
});
```

This test example above ⬆ demonstrates testing a login form with Puppeteer and Jest. It covers navigating to the application, interacting with form elements, and verifying the expected outcomes.

1. It includes setup code in a `beforeAll` block, launching a Puppeteer browser instance, and creating a new page before any test.
2. The first test (`it`) checks if a success message (`Login successful.`) is displayed after submitting the form with valid credentials.
3. In the first test, the form is interacted with by navigating to the specified URL, filling in the email and password fields, clicking the submit button, and then validating the presence of a success message.
4. The second test checks if an error message is displayed when attempting to authenticate with invalid credentials.
5. Similar to the first test, the second test interacts with the form by navigating to the URL, inputting an invalid email and password, clicking the submit button, and validating the presence of an error message (`Invalid email or password`).
6. A `afterAll` block ensures that the Puppeteer browser instance is closed after completing all tests.

![Tets results](https://blog.openreplay.com/images/e2e-react-testing-with-puppeteer/images/image2.png)

<CTA_Middle_Basics />


## Exploring Puppeteer's Debugging Features

Puppeteer offers a variety of debugging options that can be utilized for end-to-end testing of React applications. Let’s explore these options and demonstrate how to incorporate them into an existing test.

### Running in Headless Mode

By default, Puppeteer launches the Chromium browser in headless mode, which means there's no visible UI. This is ideal for automated testing and server environments. However, if you want to observe the browser during the test execution, you can launch the full version of the browser by setting `headless: false`:

```javascript
beforeAll(async () => {
  browser = await puppeteer.launch({ headless: false });
  page = await browser.newPage();
});
```

When you run the test, a Chromium browser window will be visible.

### Slowing Down Test Execution

Some actions, like form submissions, may happen too quickly to observe in headless mode. To address this, Puppeteer provides the `slowMo` option, which delays Puppeteer executions.

For example, slowing down by 300 milliseconds can be achieved as follows:

```javascript
beforeAll(async () => {
  browser = await puppeteer.launch({
    headless: false,
    slowMo: 300, // slow down by 300ms
  });
  page = await browser.newPage();
});
```

This allows you to better follow the test execution.

### Emulating Devices with `page.emulate`

The `page.emulate()` method enables the emulation of device metrics and user agents. You can customize properties such as viewport dimensions and user agent:

```javascript
beforeAll(async () => {
  browser = await puppeteer.launch({ headless: false });
  page = await browser.newPage();

  page.emulate({
    viewport: {
      width: 500,
      height: 900,
    },
    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
    deviceScaleFactor: 1,
    isMobile: false,
    hasTouch: false,
    isLandscape: false,
  });
});
```

Puppeteer provides a list of device descriptors for simplified emulation, accessible via `puppeteer.devices`. For example, emulating an iPhone 7:

```javascript
const iPhone = puppeteer.devices['iPhone 7'];

beforeAll(async () => {
  browser = await puppeteer.launch({
    headless: false,
    slowMo: 300, // slow down by 300ms
  });
  page = await browser.newPage();

  await page.emulate(iPhone);
});
```

### Using Jest Puppeteer

Jest Puppeteer streamlines the configuration for running Puppeteer tests. Follow these steps to set it up:

Install Jest Puppeteer with `yarn add -D jest-puppeteer`.
Create a `jest.config.js` file:

```javascript
module.exports = {
  preset: "jest-puppeteer",
  testRegex: "./*\\e2e\\.test\\.js$",
};
```

Create a `jest-puppeteer.config.js` file:

```javascript
module.exports = {
  server: {
    command: "yarn start",
    port: 3000,
    launchTimeout: 10000,
    debug: true,
  },
};
```

Jest Puppeteer eliminates the need to build the site for production before testing and simplifies the testing process.

### Jest Puppeteer in Action

Modify your test file (e.g., `e2e.test.js`) to take advantage of Jest Puppeteer features:

```javascript
// ... (existing setup)
describe("App.js", () => {
  it("shows a success message after submitting a form", async () => {
    // ... (existing test logic)
  });

  it("shows an error message if authentication fails", async () => {
    // ... (existing test logic)
  });
});
```

Jest Puppeteer minimizes the setup and provides a cleaner syntax for form filling, button clicking, and text matching.

Before running the test, update the form in `App.js` to include name attributes for email and password inputs.

```javascript
<input
  type="email"
  required
  placeholder="Email Address"
  className="form-input form-input__email"
  onChange={(e) => {
    setEmail(e.target.value);
  }}
  name="emailAddress"
/>

<input
  type="password"
  required
  placeholder="Password"
  className="form-input form-input__password"
  onChange={(e) => {
    setPassword(e.target.value);
  }}
  name="password"
```

Ensure ESLint compatibility by updating the `.eslintrc.js` file:

```javascript
module.exports = {
  env: {
    jest: true,
  },
  globals: {
    page: true,
    browser: true,
    context: true,
    jestPuppeteer: true,
  },
};
```

Execute the test with `yarn test:e2e` for a concise output with all tests passed.

By incorporating these Puppeteer debugging options and leveraging Jest Puppeteer, you can streamline the testing process and ensure robust end-to-end testing for your React applications.

## Conclusion

This article provided insights into using Puppeteer with Jest for end-to-end testing. The real-world example showcased testing a login form, emphasizing navigation, element identification, and user action simulation. Incorporate these principles into your testing workflow for robust React app testing.

Checkout the repository for this article on [https://github.com/Eunit99/react-puppeteer-test](GitHub)

## Further Readings

* [Playwright Vs. Puppeteer: Which To Choose?](https://blog.openreplay.com/playwright-vs-puppeteer--which-to-choose/)
* [Testing With Playwright: A Tutorial Guide With Examples](https://blog.openreplay.com/testing-with-playwright/)
* [End-To-End Testing: Unleash The Power Of WebDriverIO](https://blog.openreplay.com/end-to-end-testing--unleash-the-power-of-webdriverio/)
* [Optimizing Web Performance With Advanced Techniques](https://blog.openreplay.com/optimizing-web-performance-with-advanced-techniques/)