DEV Community

Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

End-to-end testing React apps with Puppeteer and Jest

In this tutorial, we’ll see how to write tests for a React app using Jest and Puppeteer. Testing is an important part of modern web application development, it helps to check if the code you wrote is acceptable and works as accepted. It’s a way of catching bugs in your code before you go “live”.

There are different approaches when it comes to testing;

Unit Testing —  Unit testing helps to check that individual unit of code (mostly functions) work as expected.

Integration Tests —  Integration tests are tests where individual units/features of the app are combined and tested as a group.

End-to-End Tests —  This test helps to confirm that entire features work from the user’s perspective when using the actual application.

For this tutorial, we’ll be doing end-to-end tests and checking if certain features actually work as expected. To do this, we’ll use Jest and Puppeteer (you can read about Puppeteer here).

Build a React app

We’ll be writing tests for a functional React app and see what happens when the tests pass and fail. To get started, we’ll use the create-react-app package to quickly scaffold a React app.

npx create-react-app react-puppeteer
Enter fullscreen mode Exit fullscreen mode

Once the project directory has been created and installed, navigate to the newly created directory and run the command below in your terminal.

npm i --save-dev jest jest-cli puppeteer faker
Enter fullscreen mode Exit fullscreen mode

Jest — a testing tool created by Facebook to test React apps or basically any JavaScript app

jest-cli — a CLI runner for Jest

Puppeteer — a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. We’ll use this to carry out tests from a user’s perspective.

faker — a tool that helps to generate massive amounts of fake data in the browser. We’ll use it to generate data for Puppeteer.

In your package.json file, add the following line of code in the scripts object.

"test": "jest"
Enter fullscreen mode Exit fullscreen mode

With all the necessary packages installed, you can run the React app with the command npm start and just leave it running in the background.

Write the tests

To start off with the tests, we’ll first write a test to check if there’s a particular text on a page and also a test to see if a contact form submits successfully. Let’s start off with checking if there’s a particular text on a page.

The App.test.js file is where we’ll be writing the tests. Jest is automatically configured to run tests on files have the word test in them. Open up the App.test.js and edit with the following code.

https://medium.com/media/449540e7e273566000fac7db2e28068c/href

In the first part of the code block above, faker and puppeteer are both imported and we generate a bunch of data from faker which will be used later.

The describe function acts as a container that’s used to create a block that groups related tests into one test suite. This can be helpful if you want your tests to be organized into groups. The test function is declared by Jest with the name of the test suite.

Inside the test function, a browser is launched with Puppeteer with the option of headless mode set to false. This means that we can see the browser while testing. browser.newPage() allows you to create a new page.

The .emulate() function gives you the ability to emulate certain device metrics and User-agent. In the code block above, we set it to have a viewport of 500x2400.

With the page open and it’s viewport defined, we then tell it to navigate to the app we’ll be testing with the .goto() function. The .waitForSelector() function tells Puppeteer to hold on until the particular selector has been loaded on the DOM. Once it’s loaded, the innerText of that selector is stored in a variable called html.

The next line is where the real testing happens.

expect(html).toBe('Welcome to React')
Enter fullscreen mode Exit fullscreen mode

In the line of code above, the Jest expect function is set to check if the content of the variable html is the same as Welcome to React. As previously mentioned, we are testing if a particular text on the app is what it should be, a very straightforward test. At the end of the test, the browser is closed with the .close() function.

Let’s now run the test, in your terminal, run the command below.

npm run test
Enter fullscreen mode Exit fullscreen mode

The test would pass, therefore your command output should be the same as above. You can actually change the content of the .App-title selector to see what would happen to failed tests.

Here, the output actually indicates both that the test failed and why it failed. In this case, the received value is not the same as the expected value.

More testing!

For the next test, we’ll simulate and test submitting a contact form on the React app.

In the React app code, open up the App.js file and edit with the code below.

https://medium.com/media/4545ea04bbff24175caac629e5e1a7bb/href

The App.js has being updated to have a contact form and the submit button simply logs the form data to the console.

Next, add the code block below to your App.test.js file.

https://medium.com/media/3e02e8a9a5d3df2d63acc543b0f878f0/href

This test suite is similar to the one above, the puppeteer.launch() function launches a new browser along with some config. In this case, there are two additional options, devtools which displays the Chrome devtools and slowMo which slows down Puppeteer processes by the specified amount of milliseconds. This allows us to see what is going on.

Puppeteer has the .click and .type actions that actually simulate the whole process of clicking on input fields and typing in the values. The details for the form fields are gotten from faker which set in the person object earlier.

This test suite fills the contact form and tests if the user can actually submit this form successfully.

You can run the npm run test command in your terminal and you should a Chrome browser open and watch the actual testing process.

For the next set of tests, we’ll write tests to assert the following:

— Users can login

— Users can logout

— Users are redirected to the login page for unauthorized view

— Nonexistent views/route returns a 404 page

The tests above are end to end tests that are carried out from the user’s perspective. We are checking if a user can actually use the app for the most basic things.

To carry out these tests, we’ll need a React app. We’ll use Robin Wieruch’s React Firebase Authentication Boilerplate code on GitHub. It ships with a built in authentication system, all that needs to be done is creating a Firebase project and adding the Firebase keys.

I modified the code a bit and added some selectors and IDs that makes the app suitable for testing. You can see that here on GitHub. Go ahead and clone the GitHub repo to your local system and run the following commands.

npm i

npm start
Enter fullscreen mode Exit fullscreen mode

Don’t forget to create a Firebase account and add your credentials in the src/firebase/firebase.js file.

React Firebase Authentication Boilerplate App

Let’s go ahead with writing tests for the React app. Once again, we’ll need to install jest faker, and puppeteer, also a App.test.js file is needed.

npm i --save-dev jest jest-cli puppeteer faker
Enter fullscreen mode Exit fullscreen mode

Once the installation is done, create a file named App.test.js and let’s begin to edit with the code block below.

**const** faker = require('faker');
**const** puppeteer = require('puppeteer');

**const** person = {
 email: faker.internet.email(),
 password: faker.random.word(),
};

**const** appUrlBase = 'http://localhost:3002'
**const** routes = {
**public** : {
 register: `${appUrlBase}/register`,
 login: `${appUrlBase}/login`,
 noMatch: `${appUrlBase}/ineedaview`,
 },
**private** : {
 home: `${appUrlBase}/home`,
 account: `${appUrlBase}/account`,
 },
};
Enter fullscreen mode Exit fullscreen mode

Just like the tests written above, faker and puppeteer are imported. A person object is created and it stores a random email and password that will be used for testing. The appUrlBase constant is the link to the React app — if you haven’t started the React app, run npm start in your terminal and change appUrlBase to the link.

The routes object contains the various URLs to the views we’ll be testing. The public object contains links to routes in the React app that can viewed by anyone (not logged in), while the private object contains links to routes that can only be viewed if you’re logged in.

Note that noMatch is what will be used to test for nonexistent views/route returns a 404 page, that’s why it aptly leads to /ineedaview.

Alright, let’s write the first test now.

Users can login

https://medium.com/media/0c89a8bcc331a569d843c1f7d4e72a1a/href

The code block above is different from the first set of tests we wrote above. First, the beforeAll function is used to launch a new browser with its options and create a new page in that browser as opposed to creating a new browser in every single test suite like we did in the tests earlier.

So how do we test here? In the test suite, the browser is directed to the login page which is routes.public.login and just like the contact form test, puppeteer is used to fill the form and submit it. After submitting the form, puppeteer then waits for a selector data-testid='homepage' which is a data-id that is present on the Home page — the page the React app redirects to after a successful login.

I already created an account with the user details in the code block, therefore this test should pass.

The afterAll function happens after the end of tests and it closes the browser.

Users can logout

🤮This is not a CSS tutorial

This is the view that’s shown after a successful login. Now we want to test what happens when a user clicks on the sign out button. The expected outcome is the localStorage is cleared, logged out and the user is redirected back to the Sign In page.

In the same App.test.js file, add the code below just before the afterAll function.

https://medium.com/media/9036e76353a94c796c7c6ab23f717094/href

This test is fairly straightforward. puppeteer waits for the .nav-link selector and it clicks on the button with a data attribute of data-testid=”signoutBtn” and that is actually testing if the button can be clicked. After the page.click() function, puppeteer waits for the selector .signin-form which can be found on the Sign In page.

Congratulations, another test passed.

Users are redirected to the login page for unauthorized view

We don’t want users having access to views and routes that they are not authorized to view. So let’s test if the code does that.

Add the code block below to the existing code, just before the afterAll function

https://medium.com/media/63a7606a3d47f691bbacc7a72c0e46c6/href

In the code block above, we test by going to a private route in the React and then wait for the signin-form selector.

This means after a user navigates to a private route, they are automatically redirected to the login form.

Nonexistent views/route returns a 404 page

It’s important for all apps to have a 404 page so as to explain to a user that that particular route doesn’t exist. It was implemented in this React app too, let’s test if it works as expected.

Add the code block below to the existing code, just before the afterAll function.

https://medium.com/media/718d38df549c0cde049d24fcef0da38c/href

The routes.public.noMatch link we created earlier points to a route that doesn’t exist. Therefore when puppeteer goes to that link, it’s expecting that it automatically redirects to the 404 page. The .no-match selector is located on the 404 page.

Conclusion

In this tutorial, we have seen firsthand how to write tests for React apps using Jest as a testing suite and puppeteer for simulations like typing in inputs, clicking etc.

Jest and Puppeteer are a combination that can surely never go wrong when it comes to testing to React apps. Puppeteer is still being actively developed so make sure to check the API reference for more features.

The codebase for this tutorial can be seen on GitHub here and here.

Resources

Jest: https://facebook.github.io/jest/

Puppeteer: https://github.com/GoogleChrome/puppeteer

Plug: LogRocket, a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.


Top comments (0)