Historically, Lighthouse has analyzed the cold pageload of a page. Starting in 2022 (Lighthouse v10), it can analyze and report on the entire page lifecycle via "user flows".
- … you want to run Lighthouse on your whole webapp, not just the landing page.
- … you want to know if all parts of the web experience are accessible (e.g. a checkout process).
- … you want to know the Cumulative Layout Shift of my SPA page transition
In these cases, you want Lighthouse on a flow, not just a page load.
Lighthouse can now run in three modes: navigations, timespans, and snapshots. Each mode has its own unique use cases, benefits, and limitations. Later, you'll create a flow by combining these three core report types.
- Navigation mode analyzes a single page load. It should feel familiar as all Lighthouse runs prior to v9.6.0 were essentially in this mode. Even in v9.6.0 and onwards, it remains the default.
- Timespan mode analyzes an arbitrary period of time, typically containing user interactions.
- Snapshot mode analyzes the page in a particular state.
In DevTools, navigation is easy: ensure it's the selected mode and then click Analyze page load.
Note: DevTools only generates a report for a standalone navigation. To combine it with other steps for a multi-step user flow report, use the Node API.
import {writeFileSync} from 'fs';
import puppeteer from 'puppeteer';
import {startFlow} from 'lighthouse';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const flow = await startFlow(page);
// Navigate with a URL
await flow.navigate('https://example.com');
// Interaction-initiated navigation via a callback function
await flow.navigate(async () => {
await page.click('a.link');
});
// Navigate with startNavigation/endNavigation
await flow.startNavigation();
await page.click('a.link');
await flow.endNavigation();
await browser.close();
writeFileSync('report.html', await flow.generateReport());
Instead of providing a URL to navigate to, you can provide a callback function or use startNavigation
/endNavigation
, as seen above. This is useful when you want to audit a navigation that's initiated by a scenario like a button click or form submission.
Aside: Lighthouse typically clears out any active Service Worker and Cache Storage for the origin under test. However, in this case, as it doesn't know the URL being analyzed, Lighthouse cannot clear this storage. This generally reflects the real user experience, but if you still wish to clear the Service Workers and Cache Storage you must do it manually.
This callback function must perform an action that will trigger a navigation. Any interactions completed before the callback promise resolves will be captured by the navigation.
The startNavigation
/endNavigation
functions must surround an action that triggers a navigation. Any interactions completed after startNavigation
is invoked and before endNavigation
is invoked will be captured by the navigation.
In DevTools, select "Timespan" as the mode and click Start timespan. Record whatever timerange or interactions is desired and then click End timespan.
import {writeFileSync} from 'fs';
import puppeteer from 'puppeteer';
import {startFlow} from 'lighthouse';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://secret.login');
const flow = await startFlow(page);
await flow.startTimespan();
await page.type('#password', 'L1ghth0useR0cks!');
await page.click('#login');
await page.waitForSelector('#dashboard');
await flow.endTimespan();
await browser.close();
writeFileSync('report.html', await flow.generateReport());
In DevTools, select "Snapshot" as the mode. Set up the page in the state you want to evaluate. Then, click Analyze page state.
import {writeFileSync} from 'fs';
import puppeteer from 'puppeteer';
import {startFlow} from 'lighthouse';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const flow = await startFlow(page);
await page.click('#expand-sidebar');
await flow.snapshot();
await browser.close();
writeFileSync('report.html', await flow.generateReport());
So far we've seen individual Lighthouse modes in action. The true power of flows comes from combining these building blocks into a comprehensive flow to capture the user's entire experience. Analyzing a multi-step user flow is currently only available using the Lighthouse Node API along with Puppeteer.
When mapping a user flow onto the Lighthouse modes, strive for each report to have a narrow focus. This will make debugging much easier when you have issues to fix!
The below example codifies a user flow for an ecommerce site where the user navigates to the homepage, searches for a product, and clicks on the detail link.
import puppeteer from 'puppeteer';
import {startFlow} from 'lighthouse';
import {writeFileSync} from 'fs';
// Setup the browser and Lighthouse.
const browser = await puppeteer.launch();
const page = await browser.newPage();
const flow = await startFlow(page);
// Phase 1 - Navigate to the landing page.
await flow.navigate('https://web.dev/');
// Phase 2 - Interact with the page and submit the search form.
await flow.startTimespan();
await page.click('button[search-open]');
const searchBox = await page.waitForSelector('devsite-search[search-active] input');
await searchBox.type('CLS');
await searchBox.press('Enter');
// Ensure search results have rendered before moving on.
const link = await page.waitForSelector('devsite-content a[href="https://web.dev/articles/cls"]');
await flow.endTimespan();
// Phase 3 - Analyze the new state.
await flow.snapshot();
// Phase 4 - Navigate to the article.
await flow.navigate(async () => {
await link.click();
});
// Get the comprehensive flow report.
writeFileSync('report.html', await flow.generateReport());
// Save results as JSON.
writeFileSync('flow-result.json', JSON.stringify(await flow.createFlowResult(), null, 2));
// Cleanup.
await browser.close();
As this flow has multiple steps, the flow report summarizes everything and allows you to investigate each aspect in more detail.
If you want to test the desktop version of a page with user flows, you can use the desktop config provided in the Lighthouse package, which includes desktop scoring and viewport/performance emulation.
import puppeteer from 'puppeteer';
import {startFlow, desktopConfig} from 'lighthouse';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const flow = await startFlow(page, {
config: desktopConfig,
});
await flow.navigate('https://example.com');
If you want to inherit the viewport settings set up by Puppeteer, you need to disable Lighthouse's viewport emulation in the flags
option.
If Puppeteer is emulating a desktop page make sure to use the desktopConfig
so Lighthouse still scores the results as a desktop page.
import puppeteer from 'puppeteer';
import {startFlow, desktopConfig} from 'lighthouse';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const flow = await startFlow(page, {
// Puppeteer is emulating a desktop environment,
// so we should still use the desktop config.
//
// If Puppeteer is emulating a mobile device then we can remove the next line.
config: desktopConfig,
// `flags` will override the Lighthouse emulation settings
// to prevent Lighthouse from changing the screen dimensions.
flags: {screenEmulation: {disabled: true}},
});
await page.setViewport({width: 1000, height: 500});
await flow.navigate('https://example.com');
- Keep timespan recordings short and focused on a single interaction sequence or page transition.
- Always audit page navigations with navigation mode, avoid auditing hard page navigations with timespan mode.
- Use snapshot recordings when a substantial portion of the page content has changed.
- Always wait for transitions and interactions to finish before ending a timespan. The puppeteer APIs
page.waitForSelector
/page.waitForFunction
/page.waitForResponse
/page.waitForTimeout
are your friends here.