How To Monitor Your Website Speed with Puppeteer and Headless Chrome

Romario Fitzgerald
5 min readJun 14, 2019

--

Puppeteer Chrome

Puppeteer is the official tool for testing with Headless Chrome.
It’s used for many applications including web-crawling, page-testing and pre-rendering. One of the benefits of using Puppeteer is that, using Headless Chrome, it allows you to render full web pages, Javascript and all. Meaning you can get information that’s loaded onto web pages after the initial DOM load.

This can be useful in a number of ways, just a few that come to mind.

  • Testing for Javascript Errors
  • Crawling Sites which load information with Javascript
  • Testing for Page responsiveness
  • Checking full page load times for those pages with API calls.

You can extract much more information than just these from the browser but these are some common use cases.

Today, we’re going to have a look at how to get your full website timings with Puppeteer.

Let’s Get Started

You’re going to need to install a few dependencies to get the ball rolling. Namely;

Node and Puppeteer

So let’s do that, head to your terminal

First let’s install NPM (Node Package Manager)

$ apt-get install npm

Now that we’ve got that installed, let’s install Puppeteer

$ npm i puppeteer

We’ll also need to install some dependencies to get Puppeteer ready to go, here’s the command with the list of packages

$ sudo apt-get install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget

And with that we’re ready to roll!

Let’s create a file audit.js and edit it

$ vi audit.js

In our audit file, we’ll want to require Puppeteer.

I’ll comment each line to let you know what’s happening and summarise below the code block.

//We're requiring puppeteer for usage
const puppeteer = require("puppeteer");
//This will be our main function to get our timings
async function run() {
//We initialise a browser constant, it calls puppeteer, we add the --no-sandbox argument as we are logged into the root user account
const browser = await puppeteer.launch({args: ['--no-sandbox']});
//With our browser we start a new page
const page = await browser.newPage();
//This gets the url argument we passed to the function (you'll see this during testing)
const url = process.argv[2];
//Now, we navigate to our url that we passed, we should set a timeout so that our program doesn't hang
//You'll notice we have "networkidle0", this is a command that we issue that instructs the browser that the page is finished loading once there has been 0 network connections for 500ms.
await page.goto(url, { waitUntil: "networkidle0", timeout: 10000 });
//We have the page, so let's get our html (we don't need this for this example, but its a good thing to know)
const html = await page.content();
//Here we pass our page to our function that will extract the timings, and we console.log the result to return it
console.log(await getTimings(page));
//Finally, we should close the browser
await browser.close();
}

That’s a nice little bit of code, I case you didn’t get the Network idle bit, let me expound:

When a browser loads a web page, it attempts to assemble it as quickly as it can. I.e in a short period of time, milliseconds.

So when we use the argument, NetworkIdle0, we’re essentially saying

Hey Browser, please wait till you have all the assets and made all your API calls before giving me the page.

With that out of the way, let’s move to our getTimings Function.

async function getTimings(page) {//First things first, let's extract our timings from our window object
const performanceTiming = JSON.parse(
await page.evaluate(() => JSON.stringify(window.performance.timing))
);//Now, we'll call our last function to map our timings and convert them to milli-seconds(ms), we pass our performanceTiming constant along with the timings we want to get from it.
return extractPerformanceTimings(
performanceTiming,'responseEnd','domInteractive','domContentLoadedEventEnd','loadEventEnd');}

That function was simple enough, now let’s extract our timings

//Our function accepts the timing and the names of the data elements we want to extract
const extractPerformanceTimings = (timing, ...dataNames) => {
//Firstly, We need our start time, the times supplied by our timing object are integer timestamps, so to get the duration of each event, we need to know when the event started
const navigationStart = timing.navigationStart;
//We initialise our object which we'll use to store our new info
const extractedData = {};
//We're cycling through our dataNames variable which contains the names of the timings we desire
dataNames.forEach(name => {
//Here, we're assigning the name of the dataName variable, to the difference between start time and the time our event ended.
extractedData[name] = timing[name] - navigationStart;
});//Finally, we return the extracted data to the calling function
return extractedData;
};

That’s all the code she wrote!

Now let’s do some testing. Let’s run the command below.

$ node /root/audit.js https://www.fleeksite.com///By the way, remember this? our url is at index 2, that's how we get the url below
const url = process.argv[2];

Awesome, if all went well (and it should have), you should receive an output like below.

{ responseEnd: 242,domInteractive: 908,domContentLoadedEventEnd: 1037,loadEventEnd: 1294 }

That’s all!

We now have a little tester we can call to monitor our load times (or even if our website is up) and report back to us. There are even more timings available which you can preview by logging your performance timing object to the console.

I hope this was helpful to you, thanks for reading! :)

--

--

Romario Fitzgerald
Romario Fitzgerald

Written by Romario Fitzgerald

I’m a young software developer and entrepreneur who is always looking for ways to grow.

No responses yet