Skip to main content
Historically, Lighthouse analyzed the cold page load of a page. Starting with Lighthouse v10, it can analyze and report on the entire page lifecycle via user flows. You might want flows if:
  • You want to run Lighthouse on your whole web app, not just the landing page.
  • You want to verify that all parts of an experience are accessible (e.g. a checkout process).
  • You want to measure Cumulative Layout Shift across an SPA page transition.

The three modes

Lighthouse runs in three modes. Each has distinct use cases and limitations. A flow is built by combining them.
Chrome DevTools supports individual Navigation, Timespan, and Snapshot runs, but does not support combining multiple steps into a single multi-step user flow report. To create a multi-step flow, use the Node API as shown below.

Creating a flow

A flow is created by calling startFlow(page) with a Puppeteer Page object. You then call flow methods (navigate, startTimespan/endTimespan, snapshot) in sequence to build up the steps.
import {startFlow} from 'lighthouse';
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
const flow = await startFlow(page);
After all steps are recorded, generate the report:
// HTML report
writeFileSync('report.html', await flow.generateReport());

// JSON result
writeFileSync('flow-result.json', JSON.stringify(await flow.createFlowResult(), null, 2));

Complete flow example

The example below models a user flow for an e-commerce site: navigate to the homepage, search for a product, snapshot the results, and navigate to the detail page.
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();
The resulting report summarizes all steps and lets you drill into each phase individually.

Desktop user flow

Use the built-in desktopConfig to score and emulate a desktop environment:
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');

Inheriting Puppeteer’s viewport settings

If Puppeteer is already configured with a specific viewport, disable Lighthouse’s own screen emulation to avoid overriding it:
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 use the desktop config to keep desktop scoring.
  // If Puppeteer is emulating a mobile device, remove this line.
  config: desktopConfig,
  // Prevent Lighthouse from changing screen dimensions.
  flags: {screenEmulation: {disabled: true}},
});

await page.setViewport({width: 1000, height: 500});

await flow.navigate('https://example.com');

Tips and tricks

Record each timespan around a single interaction sequence or page transition. Shorter recordings are easier to debug and produce more actionable results.
If the user action causes a full page navigation (URL change, form POST, etc.), use flow.navigate() or startNavigation/endNavigation rather than capturing it inside a timespan.
Reach for snapshot mode when a substantial portion of the page content has changed — for example, after a multi-step wizard advances to the final confirmation screen.
Always wait for page transitions and async operations to settle before calling flow.endTimespan(). Puppeteer helpers like page.waitForSelector, page.waitForFunction, page.waitForResponse, and page.waitForTimeout are useful here.

Build docs developers (and LLMs) love