• Stars
    star
    1,791
  • Rank 25,940 (Top 0.6 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created over 4 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Automating Web Performance testing with Puppeteer 🎪

Puppeteer WebPerf logo

Automating Web Perf measurement with Puppeteer

🕹 Puppeteer is a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. This repository has recipes for automating Web Performance measurement with Puppeteer.

Table Of Contents

Get a DevTools performance trace for a page load

Puppeteer API: tracing.start()

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // Drag and drop this JSON file to the DevTools Performance panel!
  await page.tracing.start({path: 'profile.json'});
  await page.goto('https://pptr.dev');
  await page.tracing.stop();
  await browser.close();
})();

Source

Screenshot of a DevTools performance profile from loading and rendering a page

Get a DevTools trace with screenshots

Puppeteer API: tracing.start()

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // Drag and drop this JSON file to the DevTools Performance panel!
    await page.tracing.start({ path: 'profile.json', screenshots: true });
    await page.goto('https://pptr.dev');
    await page.tracing.stop();
    await browser.close();
})();

Source

DevTools screenshots in the performance panel

Get a DevTools trace and extract filmstrip screenshots

If you would like to record a performance trace and extract filmstrip screenshots from that trace to a local directory, the below snippet should do the trick. It works by filtering trace events for screenshot entries.

const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.tracing.start({ screenshots: true, path: 'trace.json' });
  await page.goto('https://netflix.com', { timeout: 60000 });
  await page.tracing.stop();

  // Extract data from the trace
  const tracing = JSON.parse(fs.readFileSync('./trace.json', 'utf8'));
  const traceScreenshots = tracing.traceEvents.filter(x => (
      x.cat === 'disabled-by-default-devtools.screenshot' &&
      x.name === 'Screenshot' &&
      typeof x.args !== 'undefined' &&
      typeof x.args.snapshot !== 'undefined'
  ));

  traceScreenshots.forEach(function(snap, index) {
    fs.writeFile(`trace-screenshot-${index}.png`, snap.args.snapshot, 'base64', function(err) {
      if (err) {
        console.log('writeFile error', err);
      }
    });
  });

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

Source

Get a DevTools trace for a user interaction

Puppeteer API: page.click()

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const navigationPromise = page.waitForNavigation();
  await page.goto('https://pptr.dev/#?product=Puppeteer&version=v2.1.1&show=outline');
  await page.setViewport({ width: 1440, height: 714 });
  
  await navigationPromise;
  const selector = 'body > sidebar-component > sidebar-item:nth-child(3) > .pptr-sidebar-item';
  await page.waitForSelector(selector);
  await page.tracing.start({path: 'trace.json', screenshots: true});
  await page.click(selector);
  await page.tracing.stop();
  
  await browser.close();
})();

Source

Get Runtime performance metrics

The page.metrics() returns runtime metrics from the Chrome DevTools Protocol Performance.getMetrics() method, such as layout duration, recalc-style durations and JS event listeners.

Puppeteer API: metrics()

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://pptr.dev');

    const metrics = await page.metrics();
    console.info(metrics);

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

Source

Runtime performance metrics shown in the terminal

Generate a Lighthouse report

💡🏠 Lighthouse is an engine for analyzing web apps and web pages, collecting modern performance metrics and insights on developer best practices. It's available in the Chrome DevTools, PageSpeed Insights, a CLI and as a consumable module.

Generate a Lighthouse report for a URL and output it to a local HTML file. For more details, see the official guide to using Puppeteer with Lighthouse.

Puppeteer API: connect()

const fs = require('fs');
const lighthouse = require('lighthouse');
const puppeteer = require('puppeteer');

const chromeLauncher = require('chrome-launcher');
const reportGenerator = require('lighthouse/lighthouse-core/report/report-generator');
const request = require('request');
const util = require('util');

const options = {
  logLevel: 'info',
  disableDeviceEmulation: true,
  chromeFlags: ['--disable-mobile-emulation']
};

async function lighthouseFromPuppeteer(url, options, config = null) {
  // Launch chrome using chrome-launcher
  const chrome = await chromeLauncher.launch(options);
  options.port = chrome.port;

  // Connect chrome-launcher to puppeteer
  const resp = await util.promisify(request)(`http://localhost:${options.port}/json/version`);
  const { webSocketDebuggerUrl } = JSON.parse(resp.body);
  const browser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });

  // Run Lighthouse
  const { lhr } = await lighthouse(url, options, config);
  await browser.disconnect();
  await chrome.kill();

  const html = reportGenerator.generateReport(lhr, 'html');
  fs.writeFile('report.html', html, function (err) {
    if (err) throw err;
  });
}

lighthouseFromPuppeteer("https://pptr.dev", options);

Source

Lighthouse report generation from Puppeteer

Extract Lighthouse performance metrics

Lighthouse exposes a number of user-centric performance metrics. It's possible to pluck these metrics values out from the JSON response, as demonstrated below.

const fs = require('fs');
const lighthouse = require('lighthouse');
const puppeteer = require('puppeteer');

const chromeLauncher = require('chrome-launcher');
const reportGenerator = require('lighthouse/lighthouse-core/report/report-generator');
const request = require('request');
const util = require('util');

const options = {
  logLevel: 'info',
  disableDeviceEmulation: true,
  chromeFlags: ['--disable-mobile-emulation']
};

async function lighthouseFromPuppeteer(url, options, config = null) {
  // Launch chrome using chrome-launcher
  const chrome = await chromeLauncher.launch(options);
  options.port = chrome.port;

  // Connect chrome-launcher to puppeteer
  const resp = await util.promisify(request)(`http://localhost:${options.port}/json/version`);
  const { webSocketDebuggerUrl } = JSON.parse(resp.body);
  const browser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });

  // Run Lighthouse
  const { lhr } = await lighthouse(url, options, config);
  await browser.disconnect();
  await chrome.kill();

  const json = reportGenerator.generateReport(lhr, 'json');

  const audits = JSON.parse(json).audits; // Lighthouse audits
  const first_contentful_paint = audits['first-contentful-paint'].displayValue;
  const total_blocking_time = audits['total-blocking-time'].displayValue;
  const time_to_interactive = audits['interactive'].displayValue;

  console.log(`\n
     Lighthouse metrics: 
     🎨 First Contentful Paint: ${first_contentful_paint}, 
     ⌛️ Total Blocking Time: ${total_blocking_time},
     👆 Time To Interactive: ${time_to_interactive}`);
}

lighthouseFromPuppeteer("https://bbc.com", options);

Source

Emulate a slow network

If you need to throttle the network connection, use Puppeteer’s page.emulateNetworkConditions API.

🚨 Real network performance can be impacted by latency to towers, traffic patterns and the current radio activity. The Lighthouse guide to network throttling covers in more detail what the differences are between simulated, request-level and packet-level throttling.

You can use Lighthouse with the comcast module for packet-level throttling.

Emulating a Slow 3G network is demonstrated below.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const client = await page.target().createCDPSession();
  await client.send('Network.enable');
  // Simulated network throttling (Slow 3G)
  await page.emulateNetworkConditions(puppeteer.networkConditions['Slow 3G']);
  await browser.close();
})();

Source

You can find details on the presets DevTools supports for Slow and Fast 3G in the official source. If you are looking for the older presets around Regular 4G, WiFi etc, they are captured in network throttling in Puppeteer.

Emulate a slow network and CPU

CPU throttling allows you to simulate how a page performs on slower mobile devices. This can be done using Puppeteer’s page.emulateNetworkConditions API.

🚨 Real device CPU performance is impacted by many factors that are not trivial to emulate via the Chrome DevTools Protocol / Puppeteer. e.g core count, L1/L2 cache, thermal throttling impacting performance, architecture etc. Simulating CPU performance can be a good guideline, but ideally also verify any numbers you see on a real mobile device.

Building on top of Slow 3G network throttling, slow CPU throttling (4x slowdown - close to a median-quality device like the Moto G4), is shown below.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const client = await page.target().createCDPSession();
  await client.send('Network.enable');
  // Simulated network throttling (Slow 3G)
  await page.emulateNetworkConditions(puppeteer.networkConditions['Slow 3G']);
  await page.emulateCPUThrottling(4);
  await browser.close();
})();

Source

Test your site renders with JavaScript disabled

Situations with intermittant connectivity may mean JS is effectively disabled until it can be loaded. Testing a page with JS disabled allows you to simulate a 'worst case' for this.

Puppeteer API: setRequestInterception()

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setRequestInterception(true);

  page.on('request', request => {
    if (request.resourceType() === 'script') {
      request.abort();
    } else {
      request.continue();
    }
  });

  await page.goto('https://reddit.com');
  await page.screenshot({ path: 'pptr-nojs.png' });

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

Source

Reddit rendered with JS disabled

Get Navigation Timing API metrics

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://pptr.dev');
  const performanceTiming = JSON.parse(
    await page.evaluate(() => JSON.stringify(window.performance.timing))
  );
  console.log('performanceTiming', performanceTiming)
  await browser.close();
})();

Source

Navigation Timing API timings in iTerm from Puppeteer

Measure First Paint & First Contentful Paint

Metric: First Contentful Paint - web.dev

First Contentful Paint (FCP) metric measures the time from a page starting to load to when any part of the page's content is rendered on the screen.

The Performance Timeline API supports client-side latency measurements. performance.getEntriesByName returns recorded performance entries based on the provided name (e.g "first-paint") and optionally the performance type.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const navigationPromise = page.waitForNavigation();
  await page.goto('https://pptr.dev');
  
  await navigationPromise;

  const firstPaint = JSON.parse(
    await page.evaluate(() =>
      JSON.stringify(performance.getEntriesByName('first-paint'))
    )
  );

  const firstContentfulPaint = JSON.parse(
    await page.evaluate(() =>
      JSON.stringify(performance.getEntriesByName('first-contentful-paint'))
    )
  );

  console.log(`First paint: ${firstPaint[0].startTime}`);
  console.log(`First paint: ${firstContentfulPaint[0].startTime}`);

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

Source

Measure Largest Contentful Paint (LCP) w/PerformanceObserver

Metric: Largest Contentful Paint - web.dev

The Largest Contentful Paint (LCP) metric reports render time for the largest content element visible in the viewport.

🚨 Lighthouse 6.0 onwards supports measuring LCP and CLS in the lab using the approach in Lighthouse metrics covered earlier. PerformanceObserver is typically used to measure these metrics in the field.

PerformanceObserver allows you to observe performance measurement events and get notified of new performance entries as they are recorded in the browser's performance timeline. When measuring modern metrics like LCP or CLS with PerformanceObserver, you probably want to wait until the page's lifecycle state has changed to hidden. This ensures that you log the most latest entry.

Full Puppeteer snippet

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');

const phone = devices.devicesMap['Nexus 5X'];

function calcLCP() {
  window.largestContentfulPaint = 0;

  const observer = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lastEntry = entries[entries.length - 1];
    window.largestContentfulPaint = lastEntry.renderTime || lastEntry.loadTime;
  });

  observer.observe({ type: 'largest-contentful-paint', buffered: true });

  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      observer.takeRecords();
      observer.disconnect();
      console.log('LCP:', window.largestContentfulPaint);
    }
  });
}


async function getLCP(url) {
  const browser = await puppeteer.launch({
    args: ['--no-sandbox'],
    timeout: 10000
  });

  try {
    const page = await browser.newPage();
    const client = await page.target().createCDPSession();

    await client.send('Network.enable');
    await client.send('ServiceWorker.enable');
    await page.emulateNetworkConditions(puppeteer.networkConditions['Good 3G']);
    await page.emulateCPUThrottling(4);
    await page.emulate(phone);

    await page.evaluateOnNewDocument(calcLCP);
    await page.goto(url, { waitUntil: 'load', timeout: 60000 });

    const lcp = await page.evaluate(() => {
      return window.largestContentfulPaint;
    });
    browser.close();
    return lcp;
  } catch (error) {
    console.log(error);
    browser.close();
  }
}

getLCP("https://pptr.dev").then(lcp => console.log("LCP is: " + lcp));

Source

Largest Contentful Paint

Measure Cumulative Layout Shift (CLS) w/PerformanceObserver

Metric: Cumulative Layout Shift - web.dev

The Cumulative Layout Shift (CLS) metric measures the sum of individual layout shift scores for each unexpected layout shift that occurs between when the page begins loading and when its lifecycle state changes to hidden.

🚨 Lighthouse 6.0 onwards supports measuring CLS and LCP in the lab using the approach in Lighthouse metrics covered earlier. PerformanceObserver is typically used to measure these metrics in the field.

PerformanceObserver allows you to observe performance measurement events and get notified of new performance entries as they are recorded in the browser's performance timeline. When measuring modern metrics like CLS or LCP with PerformanceObserver, you probably want to wait until the page's lifecycle state has changed to hidden. This ensures that you log the most latest entry.

Full Puppeteer snippet.

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
  
const phone = devices.devicesMap['Nexus 5X'];

function calcJank() {
  window.cumulativeLayoutShiftScore = 0;

  const observer = new PerformanceObserver((list) => {
   for (const entry of list.getEntries()) {
     if (!entry.hadRecentInput) {
       console.log("New observer entry for cls: " + entry.value);
       window.cumulativeLayoutShiftScore += entry.value;
     }
   }
  });

  observer.observe({type: 'layout-shift', buffered: true});

  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      observer.takeRecords();
      observer.disconnect();
      console.log('CLS:', window.cumulativeLayoutShiftScore);
    }
  });
}


async function getCLS(url) {
  const browser = await puppeteer.launch({ 
    args: ['--no-sandbox'],
    timeout: 10000
  });

  try {
    const page = await browser.newPage();
    const client = await page.target().createCDPSession();

    await client.send('Network.enable');
    await client.send('ServiceWorker.enable');
    await page.emulateNetworkConditions(puppeteer.networkConditions['Good 3G']);
    await page.emulateCPUThrottling(4);
    await page.emulate(phone);
    // inject a function with the code from 
    // https://web.dev/cls/#measure-cls-in-javascript
    await page.evaluateOnNewDocument(calcJank);  
    await page.goto(url, { waitUntil: 'load', timeout: 60000});

    const cls = await page.evaluate(() => { 
        return window.cumulativeLayoutShiftScore;
    });
    browser.close();
    return cls;
  } catch (error) {
    console.log(error);
    browser.close();
  }
}

getCLS("https://pptr.dev").then(cls => console.log("CLS is: " + cls));

Source

Cumulative Layout Shift

Measure SPA metrics with Next.js

The User Timing API is a general purpose measurement API for time-based metrics. It allows you to arbitrarily mark points in time and then later measure the duration between those marks using performance.mark() and performance.measure.

Outside of the performance metrics made available via the Navigation Timing API, single-page apps (SPA) often also have custom metrics for tracking other key moments. In Next.js, these could correspond to Time-to-Hydrate, SPA route transitions and so on.

Next.js recently added the unstable_onPerformanceData helper for tracking client-side performance metrics using performance.mark and performance.measure. The below Puppeteer script allows us to collect this performance data once you patch your app to log key events to localStorage as in this Glitch example by Houssein Djirdeh.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  let selector = '';
  page.on('load', () => console.log("Loaded: " + page.url()));
  page.on('framenavigated', frame => {
    console.log(`new url: ${frame.url()}`);
  });

  const navigationPromise = page.waitForNavigation({
    waitUntil: 'networkidle2'
  })

  // Navigate to random Next.js page
  await page.goto('https://new-app-3-op9eiblak.now.sh/')

  console.log('\n==== localStorage hydration entry ====\n');
  const hydrationData = await page.evaluate(() => {
    const data = {
      'before-hydrate-mark': localStorage.getItem('beforeRender'),
      'after-hydrate-mark': Number(localStorage.getItem('beforeRender')) + Number(localStorage.getItem('Next.js-hydration')),
      'hydration-duration': localStorage.getItem('Next.js-hydration'),
    };
    return data;
  });

  console.log(hydrationData);

  await page.screenshot({
    path: 'home-page.png',
    fullPage: true
  });

  await navigationPromise;

  // Navigate to the Blog
  selector = '#__next > div > nav > ul > li:nth-child(1) > a';
  await Promise.all([
    await page.waitForSelector(selector),
    await page.click(selector, {
      delay: 300
    }),
    await page.waitFor(4000),
    await navigationPromise
  ]);

  console.log('\n==== localStorage route change performance entries ====\n');
  const routeChangeData = await page.evaluate(() => {
    const data = {
      'link-click-to-render-start-duration': localStorage.getItem('Next.js-route-change-to-render'),
      'render-duration': localStorage.getItem('Next.js-render')
    };
    return data;
  });

  console.log(routeChangeData);

  await page.screenshot({
    path: 'blog-page.png',
    fullPage: true
  });

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

Source

Get DevTools-specific metrics: Frames Per Second

It's possible to open a remote debugging client and turn on DevTools-specific features, such as the frames-per-second (FPS) heads-up-display.

Puppeteer API: createCDPSession()

const puppeteer = require('puppeteer');

(async () => {
  const args = await puppeteer.defaultArgs().filter(flag => flag !== '--enable-automation');
  const browser = await puppeteer.launch({
    headless: false,
    devtools: true,
    ignoreDefaultArgs: true,
    args
  });
  const page = await browser.newPage();
  const devtoolsProtocolClient = await page.target().createCDPSession();
  await devtoolsProtocolClient.send('Overlay.setShowFPSCounter', { show: true });
  await page.goto('https://pptr.dev');
  await page.screenshot({ path: './image.jpg', type: 'jpeg' });
  await page.close();
  await browser.close();
})();

Source

FPS meter from DevTools rendered via Puppeteer

Measure memory leaks

Checking the number of objects retained on the heap can be a good basic start to measuring memory leaks in JavaScript. In Puppeteer, queryObjects() can be used to count all the objects with the same prototype somewhere in the prototype chain.

Puppeteer API: queryObjects()

For a more detailed look at this topic, check out automatically detecting memory-leaks with Puppeteer.

const puppeteer = require('puppeteer');

// Helper by @chrisguttandin
const countObjects = async (page) => {
  const prototypeHandle = await page.evaluateHandle(() => Object.prototype);
  const objectsHandle = await page.queryObjects(prototypeHandle);
  const numberOfObjects = await page.evaluate((instances) => instances.length, objectsHandle);

  await Promise.all([
    prototypeHandle.dispose(),
    objectsHandle.dispose()
  ]);

  return numberOfObjects;
};

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.createIncognitoBrowserContext();

  const numberOfObjects = await countObjects(page);
  console.log(numberOfObjects);

  await page.evaluate(() => {
    class SomeObject {
      constructor () {
        this.numbers = {}
        for (let i = 0; i < 1000; i++) {
          this.numbers[Math.random()] = Math.random()
        }
      }
    }
    const someObject = new SomeObject();
    const onMessage = () => { /* ... */ };
    window.addEventListener('message', onMessage);
  });

  const numberOfObjectsAfter = await countObjects(page);
  console.log(numberOfObjectsAfter);

  // Check if the number of retained objects is expected
  // expect(await countObjects(page)).to.equal(0);

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

Source

Override requests with Request Interception

Request interception (overrides) allows you to modify network requests that are made by a page.

Puppeteer API: setRequestInterception()

Block requests for images

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setRequestInterception(true);
  
  page.on('request', (req) => {
    if (req.resourceType() === 'image'){
      req.abort();
    } else {
      req.continue();
    }
  });

  await page.goto('https://bbc.com');
  await page.screenshot({path: 'no-images.png', fullPage: true});
  await browser.close();
})();

Source

Replace a remote resource with a local one

In the below snippet, we override a remote resource for the Puppeteer site (pptr.dev/style.css) with a local version (assets/style.css). In the version served, you'll see the Puppeteer site is rendered with our green background colors instead.

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');

(async () => {

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // URL to test
  const remoteURL = 'https://pptr.dev/index.html';
  // URL to replace
  const remoteFilePath = 'https://pptr.dev/style.css';
  // Local (override) file to use instead
  const localFilePath = path.join(__dirname, "./assets/style.css");

  await page.setRequestInterception(true);

  page.on('request', interceptedRequest => {
    const url = interceptedRequest.url();
    console.log(`Intercepted ${url}`);

    if (url === remoteFilePath && !url.match(localFilePath)) {
      interceptedRequest.respond({
        body: fs.readFileSync(
          localFilePath
        )
      });
    } else {
      interceptedRequest.continue();
    }
  });

  await page.goto(remoteURL, {
    waitUntil: 'networkidle2'
  });

  await page.screenshot({path: 'override.png', fullPage: true});
  await browser.close();

})();

Source

Puppeteer comparison before and after request interception

While this example doesn't demonstrate it, you could use network overrides to experiment with the before/after for a number of different performance optimizations.

Block third-party domains

You can block specific requests using Puppeteer's request interception feature. This can be helpful for profiling performance with specific domains blocked to see the before/after.

Puppeteer API: setRequestInterception

const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch({
    headless: true
  });
  const page = await browser.newPage();
  const options = {
    waitUntil: 'networkidle2',
    timeout: 30000
  };

  // Before: Normal navigtation
  await page.goto('https://theverge.com', options);
  await page.screenshot({path: 'before.png', fullPage: true});
  const metrics = await page.metrics();
  console.info(metrics);

  // After: Navigation with some domains blocked
  
  // Array of third-party domains to block
  const blockedDomains = [
    'https://pagead2.googlesyndication.com',
    'https://creativecdn.com',
    'https://www.googletagmanager.com',
    'https://cdn.krxd.net',
    'https://adservice.google.com',
    'https://cdn.concert.io',
    'https://z.moatads.com',
    'https://cdn.permutive.com'];
  await page.setRequestInterception(true);
  page.on('request', request => {
    const url = request.url()
    if (blockedDomains.some(d => url.startsWith(d))) {
      request.abort();
    } else {
      request.continue();
    }
  });
  
  await page.goto('https://theverge.com', options);
  await page.screenshot({path: 'after.png', fullPage: true});

  const metricsAfter = await page.metrics();
  console.info(metricsAfter);

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

Source

The Verge before/after domain blocking

Code Coverage for JavaScript and CSS

Puppeteer API: page.coverage.startJSCoverage()

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Gather coverage for JS and CSS files
  await Promise.all([page.coverage.startJSCoverage(), page.coverage.startCSSCoverage()]);

  await page.goto('https://pptr.dev');

  // Stops the coverage gathering
  const [jsCoverage, cssCoverage] = await Promise.all([
    page.coverage.stopJSCoverage(),
    page.coverage.stopCSSCoverage(),
  ]);

  // Calculates # bytes being used based on the coverage
  const calculateUsedBytes = (type, coverage) =>
    coverage.map(({url, ranges, text}) => {
      let usedBytes = 0;

      ranges.forEach((range) => (usedBytes += range.end - range.start - 1));

      return {
        url,
        type,
        usedBytes,
        totalBytes: text.length,
        percentUsed: `${(usedBytes / text.length * 100).toFixed(2)}%`
      };
    });

  console.info([
    ...calculateUsedBytes('js', jsCoverage),
    ...calculateUsedBytes('css', cssCoverage),
  ]);

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

Source

Output preview:

  {
    url: 'https://pptr.dev/index.js',
    type: 'js',
    usedBytes: 59370,
    totalBytes: 141703,
    percentUsed: '41.90%'
  },
  {
    url: 'https://www.googletagmanager.com/gtag/js?id=UA-106086244-2',
    type: 'js',
    usedBytes: 20646,
    totalBytes: 81644,
    percentUsed: '25.29%'
  },
  {
    url: 'https://pptr.dev/style.css',
    type: 'css',
    usedBytes: 1409,
    totalBytes: 14326,
    percentUsed: '9.84%'
  }

Save network requests to a HAR file

💡 A HAR (HTTP Archive) file is a JSON format for tracking performance issues. It keeps track of each resource loaded over the network by the browser including the timing information for each of these resources.

You can use the puppeteer-har package to generate a HAR file as follows:

const puppeteer = require('puppeteer');
const HAR = require('puppeteer-har');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const har = new HAR(page);
  await har.start({ path: 'results.har' });
  await page.goto('https://pptr.dev');
  await har.stop();
  await browser.close();
})();

Source

HAR files can be imported back into Chrome DevTools for analysis or alternatively can be viewed in the HAR Analyzer project.

Generated HAR file being loaded into the Network panel in DevTools

Read more

More Repositories

1

critical

Extract & Inline Critical-path CSS in HTML pages
JavaScript
10,033
star
2

backbone-fundamentals

📖 A creative-commons book on Backbone.js for beginners and advanced users alike
JavaScript
9,294
star
3

essential-js-design-patterns

Repo for my 'Learning JavaScript Design Patterns' book
HTML
4,254
star
4

es6-tools

An aggregation of tooling for using ES6 today
3,954
star
5

basket.js

A script and resource loader for caching & loading files with localStorage
JavaScript
3,362
star
6

es6-equivalents-in-es5

WIP - ES6 Equivalents In ES5
2,531
star
7

a11y

Accessibility audit tooling for the web (beta)
JavaScript
1,710
star
8

tmi

TMI (Too Many Images) - discover your image weight on the web
JavaScript
1,640
star
9

timing.js

Navigation Timing API measurement helpers
JavaScript
1,498
star
10

critical-path-css-tools

Tools to prioritize above-the-fold (critical-path) CSS
1,141
star
11

getUserMedia.js

Shim for getUserMedia(). Uses native implementation for modern browsers and a Flash fallback for everyone else.
JavaScript
908
star
12

critical-path-css-demo

Above-the-fold CSS generation + inlining using Critical & Gulp
ApacheConf
532
star
13

backbone-boilerplates

Backbone.js stack boilerplates demonstrating integration with Express, Ruby, PHP, Grails and more.
JavaScript
488
star
14

chatty

ChattyUI - your private AI chat for running LLMs in the browser
TypeScript
449
star
15

webpack-lighthouse-plugin

A Webpack plugin for Lighthouse
JavaScript
290
star
16

learning-jsdp

Learning JavaScript Design Patterns: 2nd Edition - The Examples
HTML
253
star
17

sublime-fixmyjs

SublimeText package for FixMyJS
Python
250
star
18

predictive-fetching

Improve performance by predictively fetching pages a user is likely to need
238
star
19

storage-on-the-web

🗃 Comparing storage options for the open web in 2016
225
star
20

visibly.js

A cross-browser Page Visibility API shim
JavaScript
221
star
21

sublime-build-systems

Sublime Text build systems
202
star
22

yeoman-examples

A repo of up to date examples using Yeoman
JavaScript
202
star
23

oust

Extract URLs to stylesheets, scripts, links, images or HTML imports from HTML
JavaScript
178
star
24

cssprettifier-bookmarklet

A bookmarklet for prettifying your CSS
JavaScript
174
star
25

polymer-boilerplate

A Polymer.js template for building fast, robust web apps using Web Components
JavaScript
166
star
26

pubsubz

Another Pub/Sub implementation
JavaScript
164
star
27

backbone-mobile-search

A Backbone.js + jQuery Mobile sample app using AMD for separation of modules, Require.js for dependency management + template externalisation and Underscore for templating
JavaScript
154
star
28

prism-js

A Polymer element for syntax highlighting with Prism.js
HTML
149
star
29

starter

A simple, git-clone friendly starting point for personal projects.
JavaScript
145
star
30

memoize.js

A faster JavaScript memoizer
JavaScript
143
star
31

largescale-demo

Scalable JS architecture demo for #jqcon
JavaScript
138
star
32

psi-gulp-sample

Sample Gulp project using PSI
JavaScript
126
star
33

preact-hn

🗞 Preact Hacker News
JavaScript
121
star
34

network-emulation-conditions

Network emulation / throttling conditions (2G, 3G, 4G, Wifi etc) ☎️
JavaScript
108
star
35

bubblesort

Bubble Sort implementation with O(n^2) complexity.
JavaScript
106
star
36

polymer-filters

Polymer filters for formatting values of expressions.
JavaScript
105
star
37

angular1-dribbble-pwa

Angular 1 Dribbble Progressive Web App demo
JavaScript
102
star
38

ember-progressive-webapp

Ember.js Zuperkulblog PWA (built with FastBoot and ember-cli)
JavaScript
97
star
39

memory-mysteries

V8 memory mysteries (sample app)
CSS
84
star
40

smaller-pictures-app

Smaller Pics Progressive Web App
JavaScript
82
star
41

x-instagram

[Deprecated] A Polymer element for querying the Instagram API (Note: not yet updated to Polymer 0.5.x)
JavaScript
76
star
42

x-imager

Responsive images using Imager.js and Polymer
74
star
43

backbonejs-gallery

A Backbone, Underscore and jQuery Templates based image gallery (early early beta)
JavaScript
72
star
44

todomvc-angular-4

Angular 4.x TodoMVC implementation
TypeScript
66
star
45

socketchat

SocketChat - a beginners chat app using SocketStream
CSS
63
star
46

github-watchers-button

An Embeddable GitHub 'Watchers' Button For External Pages
JavaScript
63
star
47

gulp-uncss-task

[Deprecated] Use gulp-uncss instead please.
JavaScript
63
star
48

yt-jukebox

A YouTube Jukebox element built with Polymer & Yeoman
JavaScript
61
star
49

critical-path-angular-demo

Above-the-fold CSS generation + inlining using Critical, Gulp & Angular
JavaScript
60
star
50

native-media-resizing

Draft proposal for browser-level media resizing
59
star
51

catclock

Polymer + Material Timer/Countdown/Countdown app (alpha)
JavaScript
56
star
52

recursive-binarysearch

Recursive Binary Search with O(log N) complexity
JavaScript
56
star
53

selectionsort

Selection sort with O(n^2) time complexity
JavaScript
56
star
54

polymer-grunt-example

Polymer + Grunt
JavaScript
56
star
55

microtemplatez

Another compact micro-templating solution
JavaScript
55
star
56

page-er

A Polymer element for paginating model data
CSS
53
star
57

google-slides

⚡ An offline-enabled Polymer slide-deck
HTML
53
star
58

flickly-wireframe

The jQuery mobile wireframe for Flickly
52
star
59

grunt-uncss-sass-example

An example of using grunt-uncss on a Sass project
JavaScript
52
star
60

sparkle-trail

<sparkle-trail> Polymer element - useful as a pre-loader
CSS
51
star
61

cssdiet

(WIP) - A DevTools extension for multi-page unused CSS auditing
JavaScript
46
star
62

github-client

Angular GitHub client for Firefox OS
JavaScript
44
star
63

a11y-webapp

A11y WebApp built with Polymer (WIP)
JavaScript
44
star
64

backbone-koans-qunit

Backbone Koans for QUnit
JavaScript
44
star
65

video-js

A Polymer element for Video.js
CSS
42
star
66

generator-webapp-uncss

Yeoman generator with grunt-uncss
JavaScript
42
star
67

lottie-animation-demo

Network-aware adaptive loading with Lottie Web
JavaScript
42
star
68

spine.bitly

(Demo app) A Spine.js Bit.ly client for shortening URLs and archiving references to these links offline.
JavaScript
39
star
69

backbone-aura

Backbone Aura
38
star
70

es2015-todomvc-chrome

ES2015 TodoMVC app that works without a transpiler
JavaScript
38
star
71

critical-css-weather-app

Critical-path CSS optimized weather app
JavaScript
37
star
72

polymer-blog

A tutorial app for generator-polymer
JavaScript
33
star
73

generator-boilerplate

A simple Yeoman generator using Git submodules to clone over a boilerplate hosted elsewhere on GitHub
JavaScript
31
star
74

npm-and-polymer-demo

Demo of Polymer + Paper elements working off npm3
HTML
30
star
75

jquery-roundrr

A jQuery plugin for plotting interactive content galleries in a circle form
JavaScript
30
star
76

polymer-localforage

A Polymer element for Mozilla's localForage (async storage via IndexedDB or WebSQL)
HTML
30
star
77

devtools-timeline-model-browser

Browser-friendly helper for parsing DevTools Timeline traces into structured profiling data models
JavaScript
29
star
78

mustache-for-chromeapps

A special build of mustache that works in Chrome Apps under CSP
JavaScript
28
star
79

addyosmani

GitHub README
27
star
80

active-route

Active view routing for Polymer extending <template>
CSS
25
star
81

tmdb-viewer-load-more

Accessibility-friendly version of TMDB Viewer (load-more)
JavaScript
25
star
82

webapp-scaffold

Polymer webapp scaffold element
CSS
25
star
83

typeahead-country

A Polymer element for autocompleting country names
24
star
84

react-interop

React + Polymer + X-Tag interop
JavaScript
24
star
85

vue-cli-todomvc

TodoMVC built using the Vue.js 2.0 CLI 🍰
JavaScript
22
star
86

polymer-browserify-vulcanize

Polymer + Browserify + Vulcanize
JavaScript
22
star
87

es6-starter

A minimal starting point for using ES6 today.
JavaScript
21
star
88

faster-video

A Polymer element for <video> with playback speed controls
JavaScript
21
star
89

js-shapelib

A minimalist JavaScript library for drawing objects around a Circle or Ellipse
JavaScript
19
star
90

element-query

Element queries with Polymer (experimental fork)
CSS
19
star
91

clientside-sample-buildfile

A Client-side ANT Build File Example
19
star
92

parsely

A small utility for parsing URLs of all types.
JavaScript
18
star
93

video-player

A themeable Polymer video element
JavaScript
16
star
94

page-router

Declarative URL routing for Polymer elements.
CSS
16
star
95

polymer-eventemitter

A Polymer event emitter element with support for wildcards, many and once.
JavaScript
15
star
96

lighthouse-reports

Quick module for getting Lighthouse reports in JSON form
JavaScript
15
star
97

generator-es6

An ES6.now project generator for Yeoman.
JavaScript
14
star
98

aura

A scalable, event-driven JavaScript architecture for developing widget-based applications. Works with Backbone.js and other frameworks.
14
star
99

jquery-googleviewer-plugin

A compact Google Viewer plugin
14
star
100

medium-backups

HTML
13
star