Performance Culture
Yaprak Ayazoglu
Cyber Sessions ING
11th of March, 2021
Establishing a performance culture
Define performance requirements
Available tools
Lighthouse
Ways to add Lighthouse to your pipeline
No complaints about performance
Why are you doing this?
We don't have time for this.
QBR Planning
Good ideas but maybe later
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/