Chrome Dev tools Lighthouse + puppeteer

Lighthouse functions

  • Classic navigations
    • Page loads with cold cache
  • New
    • Page loads with a warm cache
    • Pages with an activated Service Worker
    • Accounting for potential user interactions

From recording to meassure UI-UX

Preparation - recording

  1. Get a script user flow (e.g. via Chrome Dev Tools recorder).
  2. Test it works
    1
    2
    3
    4
    5
    6
    # create node module, non interative
    npm init -y
    # install dependencies
    npm install puppeteer lighthouse
    # test
    node demo_user_flow_recording.js

Modify the recording file to use Lighthouse

  • Modify from just replay…

    1
    2
    3
    4
    5
    const puppeteer = require('puppeteer');

    (async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
  • … into this to meassure UI/UX

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    const puppeteer = require('puppeteer');
    const { startFlow } = require('lighthouse/lighthouse-core/fraggle-rock/api.js');
    const fs = require("fs");

    (async () => {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();

    const flow = await startFlow(page, {
    name: 'Go to homepage',
    configContext: {
    settingsOverrides: {
    screenEmulation: {
    mobile: false,
    width: 1350,
    height: 940,
    deviceScaleFactor: 1,
    disabled: false,
    },
    formFactor: "desktop",
    },
    },
    });

    // update viewport
    await targetPage.setViewport({ "width": 940, "height": 1350 })

    // add some steps
    {
    // capture data
    await flow.navigate("https://gitlab.com/")

    const targetPage = page;
    const promises = [];
    promises.push(targetPage.waitForNavigation());
    await targetPage.goto('https://gitlab.com/');
    await Promise.all(promises);
    }

    // generate report
    const report = flow.generateReport();
    fs.writeFileSync('report.html', report);
    await browser.close();

Mode examples

  1. Use puppeteer to open the browser.
  2. Start a Lighthouse user flow.
  3. Navigate to the target URL.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import fs from 'fs';
import open from 'open';
import puppeteer from 'puppeteer';
import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js';

async function captureReport() {
const browser = await puppeteer.launch({headless: false});
// open a browser
const page = await browser.newPage();

// start a Lighthouse user flow
const flow = await startFlow(page, {name: 'Single Navigation'});
// navigate to the target URL
await flow.navigate('https://web.dev/performance-scoring/');

await browser.close();

const report = flow.generateReport();
fs.writeFileSync('flow.report.html', report);
open('flow.report.html', {wait: false});
}

captureReport();

Capturing a warm load

  1. Use puppeteer to open the browser.
  2. Start a Lighthouse user flow.
  3. Navigate to the target URL.
  4. Do a second navigation, disabling the clearing of cache and storage that Lighthouse does by default in navigations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
async function captureReport() {
const browser = await puppeteer.launch({headless: false});
// open the browser
const page = await browser.newPage();

const testUrl = 'https://web.dev/performance-scoring/';
// start user flow
const flow = await startFlow(page, {name: 'Cold and warm navigations'});
// 1st navigation
await flow.navigate(testUrl, {
stepName: 'Cold navigation'
});
// 2nd navigation, no cache clean up
await flow.navigate(testUrl, {
stepName: 'Warm navigation',
configContext: {
settingsOverrides: {disableStorageReset: true},
},
});

await browser.close();

const report = flow.generateReport();
fs.writeFileSync('flow.report.html', report);
open('flow.report.html', {wait: false});
}

captureReport();

Snapshots

  • Audit a single point in time: set up a page and test it in its exact state: (e.g. with a drop-down open or a form partially filled in).
  • Many of the performance metrics are currently defined as beginning with a page load and so are not applicable in a snapshot, but the accessibility audits and many of the performance best practices can still yield important checks.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function captureReport() {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();

const flow = await startFlow(page, {name: 'Squoosh snapshots'});

await page.goto('https://squoosh.app/', {waitUntil: 'networkidle0'});

// Wait for first demo-image button, then open it.
const demoImageSelector = 'ul[class*="demos"] button';
await page.waitForSelector(demoImageSelector);
await flow.snapshot({stepName: 'Page loaded'});
await page.click(demoImageSelector);

// Wait for advanced settings button in UI, then open them.
const advancedSettingsSelector = 'form label[class*="option-reveal"]';
await page.waitForSelector(advancedSettingsSelector);
await flow.snapshot({stepName: 'Demo loaded'});
await page.click(advancedSettingsSelector);

await flow.snapshot({stepName: 'Advanced settings opened'});

browser.close();

const report = flow.generateReport();
fs.writeFileSync('flow.report.html', report);
open('flow.report.html', {wait: false});
}

captureReport();

Timespans

  • Runs Lighthouse audits over some period of time, which may or may not include a navigation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
async function captureReport() {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
// Get a session handle to be able to send protocol commands to the page.
const session = await page.target().createCDPSession();

const testUrl = 'https://pie-charmed-treatment.glitch.me/';
const flow = await startFlow(page, {name: 'CLS during navigation and on scroll'});

// Regular Lighthouse navigation.
await flow.navigate(testUrl, {stepName: 'Navigate only'});

// Navigate and scroll timespan.
await flow.startTimespan({stepName: 'Navigate and scroll'});
await page.goto(testUrl, {waitUntil: 'networkidle0'});
// We need the ability to scroll like a user.
/*
There's not a direct puppeteer function for this, but we can use the DevTools Protocol
and issue a Input.synthesizeScrollGesture event, which has convenient parameters like
repetitions and delay to somewhat simulate a more natural scrolling gesture.
// https://chromedevtools.github.io/devtools-protocol/tot/Input/
// check #method-synthesizeScrollGesture
*/
await session.send('Input.synthesizeScrollGesture', {
x: 100,
y: 0,
yDistance: -2500,
speed: 1000,
repeatCount: 2,
repeatDelayMs: 250,
});
await flow.endTimespan();

await browser.close();

const report = flow.generateReport();
fs.writeFileSync('flow.report.html', report);
open('flow.report.html', {wait: false});
}

captureReport();