Read this on my blog: https://ankit-m.github.io/blog/ui-testing-with-puppeteer-and-mocha/
1. Introduction 🤝
If you have ever built a web app, you know the pain of testing the entire application every time you add a new feature. Yes, you just modified one isolated module of the code and it should not affect anything else. But there a tiny sliver of doubt in a dark corner of your mind, “What if it broke something?”.
To remove this doubt, you spend quite a lot of time testing all other flows to make sure you did not accidentally break anything major. There is always a chance of you missing edge cases. So you go ahead and deploy with the hope that nothing breaks. By no means, is this a reliable way of testing. But we still do it. The only way to get rid of this unreliability is to write tests which verify your flows viz. UI Testing.
2. The Toolkit 🎒
before, etc. to organise your tests. It provides both command line and programmatic utilities to execute the tests.
Mocha by itself is not of much help to us. You need an assertion library with it to actually test things. We will use ChaiJS throughout this series. There are other options like expect.js, should.js, etc.
Puppeteer is a node wrapper around headless Chrome which started shipping with Chrome 59 (For Windows Chrome 60). It provides a lot of convenient methods to access and manipulate DOM, cookies, requests, etc.
We will use these APIs to access the rendered UI elements and run assertions on them to check if things are working as expected.
3. Basic testing setup 🐣
Open your terminal and create a new project. You need Node installed on your computer which will expose a
npm command. I recommend using nvm to manage node versions. This series uses Node
v8.9.1 and npm
npm init --yes
npm i --save firstname.lastname@example.org email@example.com firstname.lastname@example.org email@example.com
All packages are suffixed with versions to make this future proof. You can skip them if you want. We install
http-serverto start a basic http server for testing. If you already have a server running with a website, you can simply use that.
Now we will create
testfolder which will contain all the test files
srcfolder which house our test website
- Add npm scripts for starting our server and running tests
mkdir test src
To start off, we will create a very basic website which we can test. Here is what
index.html looks like.
Now add the following two scripts to your
package.json . The
test script tells mocha to execute all files in the folder
test . The recursive option tells it to go inside directories, if any, and get files. The
server script serves your
index.html on http://127.0.0.1:8080.
"test": "mocha --recursive test",
"server": "http-server src"
Finally, let’s add a sample test to check if everything is working as expected. Add the following code to
npm test and you should see an output like this -
sample test ✓ should work
🎉 Hurray! We are all set to integrate puppeteer now. You can view code at this stage here.
4. Bringing in Puppeteer 🚂
We want to start one instance of the browser (headless Chrome in this case) and reuse the same instance to run all our tests. Each test will open a new tab, browse to the URL, run tests against the view and then close the tab. We will do all the setup in
test/bootstrap.js and expose required variables to be used across tests.
before block is responsible to setup everything required for all the tests. It will execute once before all tests. It exposes the
browser instance and
expect function so that we don’t have to require it in all the test files. The
after block cleans up the environment, once all tests are completed. I have also used Lodash for some convenience methods.
The important things to note are
opts. As the name suggests,
launch starts a chrome browser based on the options provided.
- I have turned off the headless mode with
headless: false. This will open a Chromium window when you run the test.
- Added timeout of 10 seconds (any test taking longer than that will fail)
- Slowed down each operation by
100ms. This is not required right now, but comes in handy when you want to see what is happening in the browser.
You need to tweak your test script a little
"test": "mocha test/bootstrap.js --recursive test",
We will now update
test/sample.spec.js. To do something with this browser instance exposed. To check, we just log the browser version.
Now, when you run
npm test. You should see the Chrome version logged. The version might differ for you as puppeteer installs the latest version of Chromium available.
sample testChrome/64.0.3264.0 ✓ should work (103ms)
4.a Without using async/await
For many reasons you might not prefer to use
async/await syntax. You can achieve the exact same by just using promises. Since most puppeteer functions return promises, all you have to do is wait for them to resolve. The mocha
done method comes in handy.
I prefer using
async/await as it looks cleaner and is much more readable, especially for more complex tests.
You can view the code at this stage here.
5. Writing your first UI test 🚀
Now that we have set things up, it’s time to write tests. This is how our webpage looks right now. Pretty boring! I agree.
We want to test the following things:
- The page title is “Puppeteer Mocha”
- The heading is “Page Title” (Sorry for the confusing heading)
- There is a paragraph with “Some paragraph text” in it
Puppeteer has a lot of classes like
Request, etc. Each of these classes provide helper methods to access and interact with the page content. The usual workflow is to open the page, wait for a selector and then validate the content once the selectors resolve. This is how basic tests look like:
There is a lot going on in this spec file. Let’s look at it one by one.
We first open a new page in the
before block and navigate to http://localhost:8080. This is equivalent to you opening a new tab in chrome, typing the URL and pressing enter. Since, the variable
page is defined in the
describe block, all
it blocks (specs) have access to the page. This allows us to segregate our tests and make them more readable.
The first test simply checks the page title and expects it to be equal to “Puppeteer Mocha”.
The second test checks for the heading.
page.$eval takes two arguments
pageFunction. It runs a
document.querySelector in the browser environment and passes the result to the
pageFunction. Finally, it resolves with the value returned by
pageFunction. In this case, we are just returning the text for
The third test checks for the existence of just one element with class
page.$$ expects only the selector as a parameter. It runs a
document.querySelectorAll in the browser environment and returns an array of
elementHandle. You can imagine
elementHandle as a wrapper around an HTML element with helper functions which make it easier to interact and test them.
You can view the code at this stage here.
6. Conclusion 💯
In this part we have barely scraped the surface of puppeteer. I hope you have gotten a feel of UI testing. Over the next parts, we will dive deep into testing complex webpages and interactions.