Programmatically audit with Lighthouse and performance budgets

jamescryer

James

Posted on March 2, 2021

Programmatically audit with Lighthouse and performance budgets

Lighthouse is a fantastic tool for auditing websites. Not only can it be run in Chrome Devtools and on the CLI, but also programmatically! This is especially useful in CI pipelines where we can use Lighthouse to check metrics such as Core Web Vitals on local or ephemeral environments, in order to warn the engineer(s) that quality attributes have degraded before going to production.

const PORT = 8041;

function createAuditor(budget) {
  const flags = {
    port: PORT,
    disableStorageReset: true,
  };
  const optionsArray = [];
  const mobileOptions = {
    extends: 'lighthouse:default',
    settings: {
      budgets: budget && [budget],
    },
  };
  optionsArray.push(mobileOptions);

  return async function audit(url) {
    const lighthouse = require('lighthouse');
    const puppeteer = require('puppeteer');
    const browser = await puppeteer.launch({
      args: [`--remote-debugging-port=${PORT}`],
      headless: true,
    });
    const results = [];

    for (const options of optionsArray) {
      const runnerResult = await lighthouse(url, flags, options);
      const budgetReport = getBudgetReport(runnerResult.report);
      const { categories, finalUrl, configSettings } = runnerResult.lhr;

      results.push({
        url: finalUrl,
        formFactor: configSettings.formFactor,
        accessibility: categories.accessibility.score * 100,
        bestPractices: categories['best-practices'].score * 100,
        performance: categories.performance.score * 100,
        pwa: categories.pwa.score * 100,
        seo: categories.seo.score * 100,
        budgetReport,
      });
    }

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

function getBudgetReport(result) {
  const report = JSON.parse(result);
  const getOverBudget = (item) => item.countOverBudget || item.sizeOverBudget || item.overBudget;
  const perfBudget = report.audits['performance-budget'];
  const timingBudget = report.audits['timing-budget'];
  const budgetReport = [];

  if (perfBudget && perfBudget.details) {
    const perf = perfBudget.details.items.filter(getOverBudget);
    budgetReport.push(...perf);
  }

  if (timingBudget && timingBudget.details) {
    const timings = timingBudget.details.items.filter(getOverBudget);
    budgetReport.push(...timings);
  }

  return budgetReport;
}
Enter fullscreen mode Exit fullscreen mode

The example implementation uses Puppeteer, as this would allow us to interact and navigate before beginning the audit. The audit method returns the results including metrics that have not met the budget requirements, allowing us to fail a build and/or report metrics.

Below is an example of the Lighthouse performance budget.

{
    "resourceCounts": [
        {
            "resourceType": "script",
            "budget": 15
        }
    ],
    "resourceSizes": [
        {
            "resourceType": "script",
            "budget": 180
        }
    ],
    "timings": [
        {
            "metric": "interactive",
            "budget": 4500
        },
        {
            "metric": "first-contentful-paint",
            "budget": 1300
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

I've written about sending these metrics to Datadog in a following post.

💖 💪 🙅 🚩
jamescryer
James

Posted on March 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related