Ways of Frontend performance auditing with Lighthouse
Yaprak Ayazoglu
Node Conference NL, Amsterdam
7th of June, 2019
https://slides.com/yaprakayazoglu/lighthouse-audits/
Freelance Frontend Engineer
Yaprak Ayazoglu
Establishing a performance culture
Define performance requirements
Available tools
Lighthouse
Ways to add Lighthouse to your pipeline
Pingdom
Page Speed Insights
Lighthouse is an open-source, automated tool for improving the quality of web pages.
You can run it against any web page, public or requiring authentication.
$ npm install -g lighthouse
$ lighthouse https://example.com/
# outputs an html file
$ lighthouse https://example.com/ --output=json --output-path report.json
# outputs a json file
"scripts": {
"lighthouse": "lighthouse --output-path=./report.html \
--chrome-flags='--headless' https://example.com"
}
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
function launchChromeAndRunLighthouse(url, opts, config = null) {
return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
opts.port = chrome.port;
return lighthouse(url, opts, config).then(results => {
return chrome.kill().then(() => results.lhr)
});
});
}
const opts = {
chromeFlags: ['--show-paint-rects']
};
// Usage:
launchChromeAndRunLighthouse('https://example.com', opts).then(results => {
// Use results!
});
const ReportGeneratorV2 =
require('./node_modules/lighthouse/lighthouse-core/report/report-generator');
const pug = require('pug');
const urlsToTest = [
'https://example.com',
];
const reports = urlsToTest.map(async (url, index) => {
try {
const results = await runLighthouse(url, opts);
const reportInHtml = ReportGeneratorV2.generateReportHtml(results);
fs.writeFileSync(outputPath, reportInHtml);
return results;
} catch (e) {
// Exit
}
});
const rootReport = pug.renderFile('rootReportTemplate.pug', {
reports: reports,
});
fs.writeFileSync(outputPath, rootReport);
const categoriesByBudgets = require('./thresholds/performance-budgets');
isPassed(results) {
const categoryByResults = results.categories;
return Object.keys(categoriesByBudgets).every((category) => {
const threshold = categoriesByBudgets[category];
const categoryResult = categoryByResults[category];
return categoryResult.score >= threshold;
});
}
runLighthouseTests(urls, opts, customConfig).then(results => {
const fileName = 'report.html';
const outputPath = `./${REPORTS_DIR}/${fileName}`;
const reportInHtml = ReportGeneratorV2.generateReportHtml(results);
fs.outputFile(outputPath, reportInHtml);
const passed = isPassed(results);
if (!passed) {
process.exit(1);
}
})
.catch(error => {
throw new Error('Lighthouse error: ' + error);
process.exit(1);
});
// custom-config.js
module.exports = {
extends: 'lighthouse:default',
// Add gatherer
passes: [{
passName: 'defaultPass',
gatherers: [
'custom-gatherer',
],
}],
// 3. Add custom audit
audits: [
'custom-audit',
],
// 4. Create a new 'Custom metrics' category
categories: {
'custom-metrics': {
title: 'Custom Tests',
description: 'Custom tests',
auditRefs: [
{id: 'custom-audit', weight: 1},
]}}};
const { Gatherer } = require('lighthouse');
class CustomGatherer extends Gatherer {
beforePass(options) {/* ... *//}
pass(options) {/* ... *//}
afterPass(options) {
const driver = options.driver;
return driver
.evaluateAsync('window.myPerformanceMetrics')
}
}
module.exports = CustomGatherer;
const Audit = require('lighthouse').Audit;
class CustomAudit extends Audit {
static get meta() {
return {
id: 'custom-audit',
title: 'CustomAudit',
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
failureTitle: 'Custom test failed',
description: 'Custom tests in detail',
requiredArtifacts: ['CustomGatherer'],
};
}
static audit(artifacts) {
const measure = artifacts.CustomGatherer;
return {
rawValue: measure,
score: measure,
displayValue: `Custom test result is ${measure}`
};
}
}
module.exports = CustomAudit;
describe('Performance tests', function() {
let result = null;
before('Run Lighthouse base test', (done) => {
lighthouse(urlToTest, lighthouseOptions, customAuditConfig)
.then((res) => {
result = subsetOfMetrics.prepareData(res);
done();
});
});
it('should have time to interactive < MAX_TTI', (done) => {
let tti = result.preparedResults.find(r => {
return r.name === 'tti';
});
assert.isBelow(tti.value, MAX_TTI);
done();
});
{
"passes": [{
"recordNetwork": true,
"recordTrace": true,
"gatherers": [
"url",
"service-worker",
"offline"]
}],
"audits": [
"first-meaningful-paint",
"time-to-interactive",
"service-worker"
],
"aggregations": [{
/* ... */
}]
}]
}
Yaprak Ayazoglu
Node Conference NL, Amsterdam
7th of June, 2019
https://slides.com/yaprakayazoglu/lighthouse-audits/