E2E Testing - Pulling Strings with Puppeteer

On a recent QA automation assignment my team needed to quickly build and deploy some basic UI smoke tests for an enterprise web application. After some discussion we decided to go with Puppeteer. This is my first exposure to Puppeteer and I want to share a little of what I've learned so far.

E2E Testing - Pulling Strings with Puppeteer
Photo by Robert Zunikoff / Unsplash

On a recent QA automation assignment my team needed to quickly build and deploy some basic UI smoke tests for an enterprise web application. After some discussion we decided to go with Puppeteer. This is my first exposure to Puppeteer and I want to share a little of what I've learned so far.

So what is Puppeteer? Puppeteer is an open source Node library that provides a high-level API which allows an automation developer to drive the browser via the Dev Tool Protocol.

The first step to exploring the features of Puppeteer is to get it installed, so let's get started!

Puppeteer setup

 npm i puppeteer

And there you go! Once you successfully install puppeteer you have also downloaded the version of Chromium that is guaranteed to work with the installed Puppeteer APIs.

If you don't want the overhead of that download and want to test with an existing install of Chrome, you can install puppeteer-core instead. Just be sure the browser version you plan to connect to is compatible with the version of Puppeteer you're installing, which is found in the Puppeteer package.json file.

Taking a screenshot

We're now ready to to create our first test, and we'll start with something basic. For this test we'll open the browser, navigate to the Leading EDJE home page, save a screenshot of the page, and close the browser.

Create a new folder for your tests, and then create new file named screenshot.js:

const puppeteer = require('puppeteer');

(async () => {
 const browser = await puppeteer.launch();
 const page = await browser.newPage();
 await page.setViewport({ width: 1680, height: 1050 })
 await page.goto('http://leadingedje.com', {waitUntil: 'networkidle2'});
 await page.screenshot({path: 'le-screenshot.png'});
 await page.pdf({path: 'le-screenshot.pdf'});

 await browser.close();
})();

If you're familiar with other UI automation frameworks, this all probably looks familiar. We open the browser, override the default resolution of 800x600, navigate to the page, capture the screenshot, then close the browser. We are also taking a screenshot in both PNG and PDF format, with just 2 lines of code.

That's the code, so now let's run it!

node screenshot.js 

If this runs successfully, you should see no errors on the command line, and new files created named le-screenshot.png and le-screenshot.pdf. Open the PDF file and notice the full page is captured.

What you won't see is the browser opening. That's because by default Puppeteer run headless, which is necessary when running as an automated CI process. If you want to see the browser in action, simply set the headless option when launching the browser:

const browser = await puppeteer.launch({headless: false});

Google search automation

Let's create another test and name it google.js:

const puppeteer = require('puppeteer');
const { expect } = require('chai');

// puppeteer options
const opts = {
 headless: false,
 slowMo: 100,
 timeout: 10000
};

(async () => {
 const browser = await puppeteer.launch(opts);
 const page = await browser.newPage();
 await page.setViewport({ width: 1680, height: 1050 })
 await page.goto('https://www.google.com', {waitUntil: 'networkidle2'});
 await console.log('search page loaded');

 const searchTextbox = await page.waitFor('input[name=q]');
 
 await searchTextbox.type('meeseek');
 await page.keyboard.press('Enter');

 const [response] = await Promise.all([
 page.waitForNavigation(),
 page.once('load', () => console.log('meeseek results page loaded'))
 ]);

 expect(await page.title()).to.contain('Google Search');

 await page.screenshot({path: 'meeseek.png'});

 await browser.close();
})();

With this test we are navigating to google.com, performing a search, waiting for the results, and validating the results page title.

In addition, we are slowing the test by 100ms for each operation by using the sloMo option when launching the browser. This can be useful if you have a fast running test and want to be sure to see all the browser interactions.

We've also set the timeout to 10000ms. Any test that test longer than 10 seconds will fail.

Performance Tracing

For our last example we're going to step away from basic UI automation and use Puppeteer to capture performance trace information.

The Performance tab in Chrome dev tools allows you to records critical browser performance metrics as you navigate through your website. With these metrics you can troubleshoot performance issues by analyzing what Chrome is doing under the hood to render your site.

We are going to modify our Google example a bit to automatically capture a trace file during the automated test. From there we can load that trace file into Chrome dev tools and see what's really happening during our test.

Create a new file names trace.js:

const puppeteer = require('puppeteer');

// puppeteer options
const opts = {
 headless: false
};

(async () => {
 const browser = await puppeteer.launch(opts);
 const page = await browser.newPage();
 await page.setViewport({ width: 1680, height: 1050 })
 
 await page.tracing.start({path: 'trace.json',screenshots:true});
 
 for (i = 0; i < 10; i++) { 
 await page.goto('https://www.google.com', {waitUntil: 'networkidle2'});

 await console.log('search page loaded');
 const searchTextbox = await page.$('input[type=text]');
 await searchTextbox.type('meeseek box');
 await page.keyboard.press('Enter');

 await Promise.all([
 page.once('load', () => console.log('meeseek results page loaded'))
 ]);

 await page.screenshot({path: 'meeseek.png'});
 }

 await page.tracing.stop();

 await browser.close();
})();

For this test we are looping through our Google search 10 times, but more importantly we are starting a trace prior to the automation with the line:

await page.tracing.start({path: 'trace.json',screenshots:true});&nbsp;

With this line of code we'll be creating a trace.json file of the entire automated session, including screen prints. From there we can load that file into Chrome dev tools and manually troubleshoot, or automate further by parsing the trace file programmatically and proactively identifying performance issues.

Here's what the trace file looks like when I manually load it into Chrome:

Alt Text

Conclusion

Although Puppeteer provides functionality similar to Selenium, it is not meant as a replacement. Selenium provides a single common API for performing browser automation across all major browsers. Puppeteer targets only Chrome and Chromium, and it's strengths include a broader set of services, and an event-driven architecture that allows for less test flakiness and failures.

Feel free to take a look at my github project that contains all of these examples. Give Puppeteer a test drive and make Chrome dance!