Have you played the puppeteer?
Tags: automation, test, puppeteer
Published: 2018-05-31 14:44:59

---
title: Have you played the puppeteer?
published: true
description: open-sourced project puppeteer released by Google
tags: #automation #test #puppeteer 
---
I got a chance recently working on a UI test automation project which is on top of the Google puppeteer. It's a tool highly customizable for developers to control the behavior of Google Chromium browser. The details of puppeteer can be found [here](https://github.com/GoogleChrome/puppeteer).

We have a Google Chrome web extension project and the tests before each release a series of tests will be performed by interns. We have tried to maintain 100% coverage by executing the unit tests for the project. However, the integration tests like hitting APIs and monitor how the web extension UI behaves according to web pages or websites the web extension is on is still done manually. Indeed, manual tests are still important. But, we sometimes found out that the data collected by interns through manual tests are not that accurate for any kinds of reasons. We have surveyed UI test automation solutions like Selenium with WebDrivers and others but we ended up with Google puppeteer since we only support Google chrome at this stage.

The [APIs](https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md) of Google Puppeteer is pretty straightforward. Starting from creating a browser is very simple like codes below
```javascript
const browser = await puppeteer.launch({
    headless, // if true then no UI will be shown
    ignoreDefaultArgs: true,
    ignoreHTTPSErrors: true,
    slowMo, // delay executing each actions performed on web broswer
    args, // other args
  })
return browser;
```

Due to the test target is the browser extension, I need to specify where the browser extension is so that the Google Chromium browser can locate the assets and then start the installation. **extPath** is the absolute path to the folder holding browser extensions assets.
```javascript
  const args = new Array();
  args.push(`--disable-extensions-except=${extPath}`);
  args.push(`--load-extension=${extPath}`);
  const b = await puppeteer.launch({
    headless,
    ignoreDefaultArgs: true,
    ignoreHTTPSErrors: true,
    slowMo,
    args,
  });
  return b;
```

The codes above will show you the Google Chromium browser with the web extension icon on the top-right corner and probably open another tab landing to the website the web extension specify in [menifest.json](https://developer.chrome.com/extensions/manifest).

There are few key [components](https://developer.chrome.com/extensions/getstarted) composing the Google web extensions. Among which, the main UI commonly called **popup.html** is one of the test targets in the project. To the best of my knowledge, there no is [API](https://developer.chrome.com/extensions/api_index) currently able to trigger the click event of the extension icon or button sitting on top-right corner to show the main UI which is the popup.html. However, there is a way around the issue, which is directly visiting the popup.html page. Therefore, you need to create a [Page](https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md) and specify the URL you want to visit. In codes below, there is a variable called extId. If you don't have this id you probably cannot load the popup.html.
```javascript
  const page = await browser.newPage(); // create a new tab
  const chromeExtPath = `chrome-extension://${extId}/popup.html`; // where to find extId?
  await page.goto(chromeExtPath, { waitUntil: 'domcontentloaded', });
  if (bringToFront) await page.bringToFront();
  await page.reload();
  return page;
```

For the normal installation, you should find out the web extensions IDs at this directory **~/Library/Application Support/Google/Chrome/Default/Extensions** if you're on the Macbook. However, you won't find any IDs in this directory related to the [unpacked](https://developer.chrome.com/extensions/hosting) extensions. 

```bash
➜  ls -l ~/Library/Application\ Support/Google/Chrome/Default/Extensions
total 0
drwx------@ 3 guoguang  staff  96 May 21 10:33 bmnlcjabgnpnenekpadlanbbkooimhnj
drwx------@ 3 guoguang  staff  96 May 31 12:57 chhjbpecpncaggjpdakmflnfcopglcmi
drwx------@ 3 guoguang  staff  96 May 26 13:37 djjjmdgomejlopjnccoejdhgjmiappap
drwx------@ 3 guoguang  staff  96 May 30 13:59 jifpbeccnghkjeaalbbjmodiffmgedin
drwx------@ 3 guoguang  staff  96 May 31 12:55 kbfnbcaeplbcioakkpcpgfkobkghlhen
drwx------@ 3 guoguang  staff  96 May 18 14:01 niloccemoadcdkdjlinkgdfekeahmflj
drwx------@ 3 guoguang  staff  96 May 16 10:31 nmmhkkegccagdldgiimedpiccmgmieda
drwx------@ 3 guoguang  staff  96 May 16 10:31 pkedcjkdefgpdelpbcmbmeomcjbeemfm
```

There are two methods address the missing extension id. First, [load the unpacked extension](https://developer.chrome.com/extensions/getstarted) in Google Chrome browser and also turn on Developer mode to show extension ids. However, this must go through the manual process, which is a bit problematic if you want to have a fully automated testing process. The second way is to generate the extension [key](https://developer.chrome.com/apps/manifest/key) and id on the fly. Following this [thread](https://stackoverflow.com/questions/37317779/making-a-unique-extension-id-and-key-for-chrome-extension?rq=1) on StackOverflow about how to create the key and id I have a shell script generating key and id as follows. You probably need to enlarge the key size to 4096 while creating the pem.key so as to make the web extension id and key works out for the extensions. Btw, the Google Chromium version I am using is 68.0.3419.0 Developer Build 64-bit.
```bash
  # genereate the web extension key.
  ret=$(openssl rsa -in ./extensions/key.pem -pubout -outform DER | openssl base64 -A)
  # based the key above to generate the **extension id**
  ret=$(openssl rsa -in ./extensions/key.pem -pubout -outform DER | shasum -a 256 | head -c32 | tr 0-9a-f a-p)
```

Not only the popup.html but how extensions interacting with web pages is also the test target. One important function of our web extension is to change its icon if a user is visiting our partners' website. In the beginning, I was trying the [Message Passing](https://developer.chrome.com/extensions/messaging) to sync up testing data between web extension and web pages. I found that I need to change codes a lot in the extensions especially for the [onMessage](https://developer.chrome.com/apps/runtime#event-onMessage) and [onExtenalMesasge](https://developer.chrome.com/apps/runtime#event-onMessageExternal) parts. To minimize the changes, I then used the Firebase as the medium to sync up the testing data. This probably is not the best choice but it really works for us currently.

```javascript
  firebase.initializeApp(JSON.parse(data));
  firebaseDB = firebase.database();
...
  firebaseDB.ref('urlPatternTest/').update({
    uuid: testKey,
    host: '',
    icon: '',
    ts: (new Date()).getTime(),
    key: '',
  }, err => {
    if (err) reject(err);
    else resolve();
  });
``` 

The rest is about codes how we doing UI automation tests like switching tab,  login/logout and etc.

```
async function selectTab(page, tabName) {
  const selector = 'div#buttonBox :first-child'; // querySelector
  let handle = await page.waitForSelector(selector); // blokc until the expected dom element loaded
  const text = await page.evaluate(sel => document.querySelector(sel).innerText, selector, handle); // evalute the selector and return the data
  if (text === tabName) {
    handle = await page.$(selector);
    await handle.click();
  } else {
    handle = await page.$('div#buttonBox :last-child');
    await handle.click();
  }
}
...
....
  let selector = 'input[type="email"]';
  await sgPage.waitForSelector(selector);
  const emailHandle = await sgPage.$(selector);
  await emailHandle.type(USER_EMAIL); // key in user email

  selector = 'input[type="password"]';
  const pwdHandle = await sgPage.$(selector);
  if (_.isNil(pwdHandle)) throw new TypeError('found no password input element!');
  await pwdHandle.type(USER_PWD); // key in use pwd
...

```

I really enjoy using Puppeteer so far since its simplicity getting UI test done with its full-fledged APIs and ways of intercepting data (HTML, image, URL, cookies and etc). Right now, we are still working on the project by adding more sophisticated test scenarios. Not sure if you will use Puppeteer in the near future. Any comments are so welcome!