logo logo

Custom Audits using Lighthouse

Custom Audits using Lighthouse

In the previous blog we have seen the importance of Web Performance testing and also understood how to capture metrics using Lighthouse. In this chapter, we shall learn how to add custom audits to your lighthouse metric.

Let’s revisit the Lighthouse architecture built around the Chrome Debugging Protocol which is a set of low-level APIs to interact with a Chrome instance. It interfaces a Chrome instance through the Driver. The Gatherers collect data from the page using the Driver. The output of a Gatherer is an Artifact, a collection of grouped metrics. An Artifact then is used by an Audit to test for a metric. The Audits assert and assign a score to a specific metric. The output of an Audit is used to generate the Lighthouse report that we are familiar with. 

I have created a sample HTML to perform a custom audit:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test Project Custom Audit</title>
  </head>

  <body>
    <img
      onload="window.performance.mark('testProjectImg')"
      alt="Test Project"
      src="https://testproject.io/wp-content/uploads/2019/11/hero-image.svg"
    />
      window.performance.mark('testProjectImg');

      window.testProjectMetric = window.performance.getEntriesByType(
        'mark'
      )[0].startTime;
    </script>
  </body>
</html>

We need two components to create our custom audit:

  1. Gather – This will collect the needed data from the webpage.
  2. Audit – This will run the test and report if it passes or fails.

Gatherer

A gatherer is used by Lighthouse to get any data from the webpage that is currently needed to perform the Lighthouse audit through gatherer:

'use strict';
const { Gatherer } = require('lighthouse');

class TestProjectHeroImage extends Gatherer {
  afterPass(options) {
    const driver = options.driver;
    return driver.evaluateAsync('window.testProjectMetric');
  }
}

module.exports = TestProjectHeroImage;

The gatherer has three different hooks:

  1. beforePass – called before the navigation to given URL.
  2. pass – called after the page is loaded and the trace is being recorded.
  3. afterPass – called after the page is loaded, all the other pass have been executed and a trace is available.

 Refer to this GitHub repo to learn more about the driver.

The afterPass lifecycle hook is expected to return an Artifact. We use the same artifact and pass it on to Audit.js

Audit

An audit defines a score. It takes the artifact that was created by the Gatherer and calculated the required metrics. Similar to gatherer, we extend the base class to Audit and implement the necessary methods:

'use strict';

const Audit = require('lighthouse').Audit;

const MAX_LOAD_TIME = 100;

class LoadAudit extends Audit {
  static get meta() {
    return {
      id: 'testProject-heroimage-audit',
      title: 'Hero Image of TestProject',
      failureTitle: 'Hero image is slow to load',
      scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
      description: 'Loaded TestProject Hero Image....',
      requiredArtifacts: ['TestProjectHeroImage'],
    };
  }

  static audit(artifacts) {
    const loadMetrics = artifacts.TestProjectHeroImage;

    const belowThreshold = loadMetrics <= MAX_LOAD_TIME;

    return {
      rawValue: loadMetrics,
      score: Number(belowThreshold),
    };
  }
}
module.exports = LoadAudit;

The class audit defines two methods:

  1. meta – Inside meta function we define the id, title,scoreDisplayMode, failureTitle and also make sure the Artifacts needed are configured correctly in case it’s the Gatherer – ‘TestProjectHeroImage’
  2. audit – takes as input the Artifacts from Gatherers and returns a metric.

Now the configuration that would get passed to the Lighthouse would look as follows:

const config = {
    extends: 'lighthouse:default',
    passes: [
      {
        passName: 'defaultPass',
        gatherers: ['hero-gatherer'],
      },
    ],

    audits: ['hero-audit'],

    categories: {
      mysite: {
        title: 'Test Project Hero Image',
        description: 'Hero Imaged Loaded!!',
        auditRefs: [{ id: 'testProject-heroimage-audit', weight: 10 }],
      },
    },
  };

Now, we are going to pass the configuration to the Lighthouse options. We will consider the example which we created in the previous chapter and the entire code would look like this:

const {
  openBrowser,
  goto,
  currentURL,
  closeBrowser,
  client,
} = require('taiko');
import lighthouse from 'lighthouse';
const ReportGenerator = require('lighthouse/lighthouse-core/report/report-generator');
const fs = require('fs');
(async () => {
  const config = {
    extends: 'lighthouse:default',
    passes: [
      {
        passName: 'defaultPass',
        gatherers: ['search-gatherer'],
      },
    ],

    audits: ['search-audit'],

    categories: {
      mysite: {
        title: 'Test Project Hero Image',
        description: 'Hero Imaged Loaded!!',
        auditRefs: [{ id: 'testProject-heroimage-audit', weight: 10 }],
      },
    },
  };

  try {
    await openBrowser({ headless: true });
    await goto('http://127.0.0.1:5500/lh.html');
    let url = await currentURL();
    let port = await client()
      .webSocketUrl.split('/devtools/')[0]
      .replace('ws://', '')
      .split(':')[1];
    let lhr = await lighthouse(
      url,
      {
        port,
        output: 'html',
        logLevel: 'error',
      },
      config
    );
    console.log(lhr.lhr)
    const report = ReportGenerator.generateReport(lhr.lhr, 'html');
    fs.writeFile('audit.html', report, (err) => {
      if (err) throw err;
    });
  } catch (error) {
    console.error(error);
  } finally {
    await closeBrowser();
  }
})();

Executing the above code would create an audit.html which will have our custom audit, as seen below:

Custom Audits using Lighthouse

Custom Audits using Lighthouse

Let’s see what happens when the hero image from TestProject takes longer than 3s as we have mentioned the MAX_LOAD_TIME=3000ms 

TestProject Hero image Lighthouse Custom Audit

Hope this chapter helped you in understanding how to get started with adding custom audits to your Lighthouse reports.

Please find the full code snippets here.

Referrece:

https://github.com/GoogleChrome/lighthouse/tree/master/docs/recipes/custom-audit

https://github.com/AymenLoukil/Google-lighthouse-custom-audit

Happy Testing 😉

About the author

Sai Krishna

Work at ThoughtWorks as Lead Consultant with 8 yrs of experience. Over the course of my career, I have worked on testing different mobile applications and building automation frameworks. Active contributor to Appium and also a member of Appium organization.

I love to contribute to open source technologies and passionate about new ways of thinking.

Leave a Reply

FacebookLinkedInTwitterEmail