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

  @yaprakaya

  • Google Developer Expert in Web Technologies
  • Living in NL

I am...

I liked...

I like...

Agenda

  • Establishing a performance culture

  • Define performance requirements

  • Available tools

  • Lighthouse

  • Ways to add Lighthouse to your pipeline

Begin with establishing the performance culture

How?

Create awareness about performance benefits.

Real User Data

  • Share real user data

    • Pingdom

    • Page Speed Insights

  • Simulate limited resources

Manual Tests

Share results with your team members.

Automation

Share results with your team members.

Increase the reach

Make it company wide

Following steps

  • Define metrics & budgets

  • Improve the tooling

    • Page with a session
    • Multiple pages

Lighthouse

  • 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.

Chrome Dev Tools

Chrome Dev Tools

Command Line

$ 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"
}

npm package

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!
});

Lighthouse Report

Multiple Urls

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);

Performance budget

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);
});

Want more tests?

// 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},
  ]}}};

Architecture

Gatherers

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;  

Audits

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;

As Mocha Tests?

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();
  });

Custom Config

{
  "passes": [{
    "recordNetwork": true,
    "recordTrace": true,
    "gatherers": [      
      "url",
      "service-worker",
      "offline"]
  }],

  "audits": [
    "first-meaningful-paint",
    "time-to-interactive",
    "service-worker"
  ],

  "aggregations": [{
    /* ... */
    }]
  }]
}

Integration to CI

  • Jenkins (Github, Bitbucket, Gitlab, etc.)
  • Travis + Github
  • Gitlab + Gitlab CI
  • Bitbucket + Bitbucket Pipelines

Github + Travis

  • Jenkins (Github, Bitbucket, Gitlab, etc.)
  • Travis + Github
  • Gitlab + Gitlab CI
  • Bitbucket + Bitbucket Pipelines

GitHub & Travis

Bitbucket & Jenkins

Wrap up

  • Start with small steps and aim for the long run

  • Lighthouse is a powerful building block

References

Thank you...

Yaprak Ayazoglu

 

Node Conference  NL, Amsterdam

7th of June, 2019

https://slides.com/yaprakayazoglu/lighthouse-audits/

lighthouse-audits

By Yaprak Ayazoglu

lighthouse-audits

Lighthouse is an automated tool for improving the quality of web pages. It provides audits for performance, accessibility, progressive webapps, SEO, etc. and provide guidance to how to improve it. This Docker container uses Lighthouse to audit your frontend performance. There are several ways to incorporate Lighthouse. Among those ways, we picked the solution to allow the teams create their own custom tests performance tests.

  • 1,653