Programmatically audit with Lighthouse and performance budgets
Tags: javascript, lighthouse, performance
Published: 2021-03-02 16:09:13

[Lighthouse](https://github.com/GoogleChrome/lighthouse) is a fantastic tool for auditing websites. Not only can it be run in [Chrome Devtools](https://developers.google.com/web/tools/chrome-devtools) and on the [CLI](https://github.com/GoogleChrome/lighthouse#using-the-node-cli), but also [programmatically](https://github.com/GoogleChrome/lighthouse/blob/master/docs/readme.md#using-programmatically)! This is especially useful in CI pipelines where we can use Lighthouse to check metrics such as [Core Web Vitals](https://web.dev/vitals/) on local or ephemeral environments, in order to warn the engineer(s) that quality attributes have degraded before going to production.

```javascript
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;
}
```

The example implementation uses [Puppeteer](https://pptr.dev/), 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](https://github.com/GoogleChrome/lighthouse/blob/master/docs/performance-budgets.md).
```json
{
	"resourceCounts": [
		{
			"resourceType": "script",
			"budget": 15
		}
	],
	"resourceSizes": [
		{
			"resourceType": "script",
			"budget": 180
		}
	],
	"timings": [
		{
			"metric": "interactive",
			"budget": 4500
		},
		{
			"metric": "first-contentful-paint",
			"budget": 1300
		}
	]
}
```

I've written about [sending these metrics to Datadog](https://dev.to/jamescryer/send-metrics-to-datadog-from-netlify-builds-3kmm) in a following post.