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/

Made with Slides.com