From 8b927538819e1742b0bf40f4e048b6ffc318f50c Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:06:40 -0400 Subject: [PATCH 1/9] docs: update puppeteer auth example for 10.0 (#14195) --- docs/recipes/auth/README.md | 22 +++++--------- docs/recipes/auth/example-lh-auth.js | 29 ++++++++----------- .../integration-test/example-lh-auth.test.js | 10 +++---- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/docs/recipes/auth/README.md b/docs/recipes/auth/README.md index 346bd85c32fc..c011146e4bd8 100644 --- a/docs/recipes/auth/README.md +++ b/docs/recipes/auth/README.md @@ -35,28 +35,25 @@ What does this do? Read on.... ## Process -Puppeteer - a browser automation tool - can be used to programatically setup a session. +Puppeteer - a browser automation tool - can be used to programmatically setup a session. 1. Launch a new browser. 1. Navigate to the login page. 1. Fill and submit the login form. 1. Run Lighthouse using the same browser. -First, launch Chrome: +First, launch Chrome and create a new page: ```js -// This port will be used by Lighthouse later. The specific port is arbitrary. -const PORT = 8041; const browser = await puppeteer.launch({ - args: [`--remote-debugging-port=${PORT}`], // Optional, if you want to see the tests in action. headless: false, slowMo: 50, }); +const page = await browser.newPage(); ``` Navigate to the login form: ```js -const page = await browser.newPage(); await page.goto('http://localhost:10632'); ``` @@ -89,18 +86,13 @@ await Promise.all([ At this point, the session that Puppeteer is managing is now logged in. -Close the page used to log in: -```js -await page.close(); -// The page has been closed, but the browser still has the relevant session. -``` - -Now run Lighthouse, using the same port as before: +Now run Lighthouse, using the same page as before: ```js // The local server is running on port 10632. const url = 'http://localhost:10632/dashboard'; -// Direct Lighthouse to use the same port. -const result = await lighthouse(url, {port: PORT, disableStorageReset: true}); +// Direct Lighthouse to use the same page. +// Disable storage reset so login session is preserved. +const result = await lighthouse(url, {disableStorageReset: true}, undefined, page); const lhr = result.lhr; // Direct Puppeteer to close the browser - we're done with it. diff --git a/docs/recipes/auth/example-lh-auth.js b/docs/recipes/auth/example-lh-auth.js index 9aaa30cb59cd..a7cae9208e34 100644 --- a/docs/recipes/auth/example-lh-auth.js +++ b/docs/recipes/auth/example-lh-auth.js @@ -13,15 +13,11 @@ import puppeteer from 'puppeteer'; import lighthouse from 'lighthouse'; import esMain from 'es-main'; -// This port will be used by Lighthouse later. The specific port is arbitrary. -const PORT = 8041; - /** - * @param {import('puppeteer').Browser} browser + * @param {puppeteer.Page} page * @param {string} origin */ -async function login(browser, origin) { - const page = await browser.newPage(); +async function login(page, origin) { await page.goto(origin); await page.waitForSelector('input[type="email"]', {visible: true}); @@ -34,36 +30,35 @@ async function login(browser, origin) { page.$eval('.login-form', form => form.submit()), page.waitForNavigation(), ]); - - await page.close(); } /** - * @param {puppeteer.Browser} browser + * @param {puppeteer.Page} page * @param {string} origin */ -async function logout(browser, origin) { - const page = await browser.newPage(); +async function logout(page, origin) { await page.goto(`${origin}/logout`); - await page.close(); } async function main() { // Direct Puppeteer to open Chrome with a specific debugging port. const browser = await puppeteer.launch({ - args: [`--remote-debugging-port=${PORT}`], // Optional, if you want to see the tests in action. headless: false, slowMo: 50, }); + const page = await browser.newPage(); // Setup the browser session to be logged into our site. - await login(browser, 'http://localhost:10632'); + await login(page, 'http://localhost:10632'); // The local server is running on port 10632. const url = 'http://localhost:10632/dashboard'; - // Direct Lighthouse to use the same port. - const result = await lighthouse(url, {port: PORT, disableStorageReset: true}); + + // Direct Lighthouse to use the same Puppeteer page. + // Disable storage reset so login session is preserved. + const result = await lighthouse(url, {disableStorageReset: true}, undefined, page); + // Direct Puppeteer to close the browser as we're done with it. await browser.close(); @@ -72,7 +67,7 @@ async function main() { } if (esMain(import.meta)) { - main(); + await main(); } export { diff --git a/docs/recipes/integration-test/example-lh-auth.test.js b/docs/recipes/integration-test/example-lh-auth.test.js index 2142c1d2feba..9c67c32ed011 100644 --- a/docs/recipes/integration-test/example-lh-auth.test.js +++ b/docs/recipes/integration-test/example-lh-auth.test.js @@ -97,8 +97,8 @@ describe('my site', () => { }); afterEach(async () => { + await logout(page, ORIGIN); await page.close(); - await logout(browser, ORIGIN); }); describe('/ logged out', () => { @@ -119,14 +119,14 @@ describe('my site', () => { describe('/ logged in', () => { it('lighthouse', async () => { - await login(browser, ORIGIN); + await login(page, ORIGIN); await page.goto(ORIGIN); const lhr = await runLighthouse(page.url()); expect(lhr).toHaveLighthouseScoreGreaterThanOrEqual('seo', 0.9); }); it('login form should not exist', async () => { - await login(browser, ORIGIN); + await login(page, ORIGIN); await page.goto(ORIGIN); const emailInput = await page.$('input[type="email"]'); const passwordInput = await page.$('input[type="password"]'); @@ -144,14 +144,14 @@ describe('my site', () => { describe('/dashboard logged in', () => { it('lighthouse', async () => { - await login(browser, ORIGIN); + await login(page, ORIGIN); await page.goto(`${ORIGIN}/dashboard`); const lhr = await runLighthouse(page.url()); expect(lhr).toHaveLighthouseScoreGreaterThanOrEqual('seo', 0.9); }); it('has secrets', async () => { - await login(browser, ORIGIN); + await login(page, ORIGIN); await page.goto(`${ORIGIN}/dashboard`); expect(await page.content()).toContain('secrets'); }); From 67c8b1ac0a3e9ec6c51d5f966b3583b4c064c8f4 Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Thu, 25 Aug 2022 12:05:53 -0700 Subject: [PATCH 2/9] docs(puppeteer): update to reflect FR changes (#14319) --- docs/puppeteer.md | 50 +++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/docs/puppeteer.md b/docs/puppeteer.md index 2e3358604daa..d6e36c673faa 100644 --- a/docs/puppeteer.md +++ b/docs/puppeteer.md @@ -14,11 +14,9 @@ The example below shows how to inject CSS into the page before Lighthouse audits A similar approach can be taken for injecting JavaScript. ```js -const puppeteer = require('puppeteer'); -const lighthouse = require('lighthouse'); -const {URL} = require('url'); +import puppeteer from 'puppeteer'; +import lighthouse from 'lighthouse'; -(async() => { const url = 'https://chromestatus.com/features'; // Use Puppeteer to launch headful Chrome and don't use its default 800x600 viewport. @@ -26,10 +24,10 @@ const browser = await puppeteer.launch({ headless: false, defaultViewport: null, }); +const page = await browser.newPage(); // Wait for Lighthouse to open url, then inject our stylesheet. browser.on('targetchanged', async target => { - const page = await target.page(); if (page && page.url() === url) { await page.addStyleTag({content: '* {color: red}'}); } @@ -37,16 +35,11 @@ browser.on('targetchanged', async target => { // Lighthouse will open the URL. // Puppeteer will observe `targetchanged` and inject our stylesheet. -const {lhr} = await lighthouse(url, { - port: (new URL(browser.wsEndpoint())).port, - output: 'json', - logLevel: 'info', -}); +const {lhr} = await lighthouse(url, undefined, undefined, page); console.log(`Lighthouse scores: ${Object.values(lhr.categories).map(c => c.score).join(', ')}`); await browser.close(); -})(); ``` ### Option 2: Launch Chrome with Lighthouse/chrome-launcher and handoff to Puppeteer @@ -55,41 +48,26 @@ When using Lighthouse programmatically, you'll often use chrome-launcher to laun Puppeteer can reconnect to this existing browser instance like so: ```js -const chromeLauncher = require('chrome-launcher'); -const puppeteer = require('puppeteer'); -const lighthouse = require('lighthouse'); -const request = require('request'); -const util = require('util'); - -(async() => { +import chromeLauncher from 'chrome-launcher'; +import puppeteer from 'puppeteer'; +import lighthouse from 'lighthouse'; +import fetch from 'node-fetch'; -const URL = 'https://chromestatus.com/features'; - -const opts = { - //chromeFlags: ['--headless'], - logLevel: 'info', - output: 'json' -}; +const url = 'https://chromestatus.com/features'; // Launch chrome using chrome-launcher. -const chrome = await chromeLauncher.launch(opts); -opts.port = chrome.port; +const chrome = await chromeLauncher.launch(); // Connect to it using puppeteer.connect(). -const resp = await util.promisify(request)(`http://localhost:${opts.port}/json/version`); -const {webSocketDebuggerUrl} = JSON.parse(resp.body); +const resp = await fetch(`http://localhost:${chrome.port}/json/version`); +const {webSocketDebuggerUrl} = await resp.json(); const browser = await puppeteer.connect({browserWSEndpoint: webSocketDebuggerUrl}); +const page = await browser.newPage(); // Run Lighthouse. -const {lhr} = await lighthouse(URL, opts, null); +const {lhr} = await lighthouse(url, undefined, undefined, page); console.log(`Lighthouse scores: ${Object.values(lhr.categories).map(c => c.score).join(', ')}`); await browser.disconnect(); await chrome.kill(); - -})(); ``` - --------------- - -**Note**: https://github.com/GoogleChrome/lighthouse/issues/3837 tracks the overall discussion for making Lighthouse work in concert with Puppeteer. Some things, like A/B testing the perf of UI changes, are tricky or not yet possible. From 32c0604007d1e7912975c7a43277710f96e2cf6a Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Fri, 26 Aug 2022 11:22:34 -0700 Subject: [PATCH 3/9] docs(config): update to reflect changes in FR (#14324) --- core/config/default-config.js | 128 +++++++++++----------------------- docs/configuration.md | 115 +++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 121 deletions(-) diff --git a/core/config/default-config.js b/core/config/default-config.js index 709632bb417b..29088f2f1332 100644 --- a/core/config/default-config.js +++ b/core/config/default-config.js @@ -122,105 +122,57 @@ const UIStrings = { const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); -// Ensure all artifact IDs match the typedefs. -/** @type {Record} */ -const artifacts = { - DevtoolsLog: '', - Trace: '', - Accessibility: '', - AnchorElements: '', - BFCacheFailures: '', - CacheContents: '', - ConsoleMessages: '', - CSSUsage: '', - Doctype: '', - DOMStats: '', - EmbeddedContent: '', - FontSize: '', - Inputs: '', - FullPageScreenshot: '', - GlobalListeners: '', - IFrameElements: '', - ImageElements: '', - InstallabilityErrors: '', - InspectorIssues: '', - JsUsage: '', - LinkElements: '', - MainDocumentContent: '', - MetaElements: '', - NetworkUserAgent: '', - OptimizedImages: '', - ResponseCompression: '', - RobotsTxt: '', - ServiceWorker: '', - ScriptElements: '', - Scripts: '', - SourceMaps: '', - Stacks: '', - TagsBlockingFirstPaint: '', - TapTargets: '', - TraceElements: '', - ViewportDimensions: '', - WebAppManifest: '', - devtoolsLogs: '', - traces: '', -}; - -for (const key of Object.keys(artifacts)) { - artifacts[/** @type {keyof typeof artifacts} */ (key)] = key; -} - /** @type {LH.Config} */ const defaultConfig = { settings: constants.defaultSettings, artifacts: [ // Artifacts which can be depended on come first. - {id: artifacts.DevtoolsLog, gatherer: 'devtools-log'}, - {id: artifacts.Trace, gatherer: 'trace'}, + {id: 'DevtoolsLog', gatherer: 'devtools-log'}, + {id: 'Trace', gatherer: 'trace'}, - {id: artifacts.Accessibility, gatherer: 'accessibility'}, - {id: artifacts.AnchorElements, gatherer: 'anchor-elements'}, - {id: artifacts.CacheContents, gatherer: 'cache-contents'}, - {id: artifacts.ConsoleMessages, gatherer: 'console-messages'}, - {id: artifacts.CSSUsage, gatherer: 'css-usage'}, - {id: artifacts.Doctype, gatherer: 'dobetterweb/doctype'}, - {id: artifacts.DOMStats, gatherer: 'dobetterweb/domstats'}, - {id: artifacts.EmbeddedContent, gatherer: 'seo/embedded-content'}, - {id: artifacts.FontSize, gatherer: 'seo/font-size'}, - {id: artifacts.Inputs, gatherer: 'inputs'}, - {id: artifacts.GlobalListeners, gatherer: 'global-listeners'}, - {id: artifacts.IFrameElements, gatherer: 'iframe-elements'}, - {id: artifacts.ImageElements, gatherer: 'image-elements'}, - {id: artifacts.InstallabilityErrors, gatherer: 'installability-errors'}, - {id: artifacts.InspectorIssues, gatherer: 'inspector-issues'}, - {id: artifacts.JsUsage, gatherer: 'js-usage'}, - {id: artifacts.LinkElements, gatherer: 'link-elements'}, - {id: artifacts.MainDocumentContent, gatherer: 'main-document-content'}, - {id: artifacts.MetaElements, gatherer: 'meta-elements'}, - {id: artifacts.NetworkUserAgent, gatherer: 'network-user-agent'}, - {id: artifacts.OptimizedImages, gatherer: 'dobetterweb/optimized-images'}, - {id: artifacts.ResponseCompression, gatherer: 'dobetterweb/response-compression'}, - {id: artifacts.RobotsTxt, gatherer: 'seo/robots-txt'}, - {id: artifacts.ServiceWorker, gatherer: 'service-worker'}, - {id: artifacts.ScriptElements, gatherer: 'script-elements'}, - {id: artifacts.Scripts, gatherer: 'scripts'}, - {id: artifacts.SourceMaps, gatherer: 'source-maps'}, - {id: artifacts.Stacks, gatherer: 'stacks'}, - {id: artifacts.TagsBlockingFirstPaint, gatherer: 'dobetterweb/tags-blocking-first-paint'}, - {id: artifacts.TapTargets, gatherer: 'seo/tap-targets'}, - {id: artifacts.TraceElements, gatherer: 'trace-elements'}, - {id: artifacts.ViewportDimensions, gatherer: 'viewport-dimensions'}, - {id: artifacts.WebAppManifest, gatherer: 'web-app-manifest'}, + {id: 'Accessibility', gatherer: 'accessibility'}, + {id: 'AnchorElements', gatherer: 'anchor-elements'}, + {id: 'CacheContents', gatherer: 'cache-contents'}, + {id: 'ConsoleMessages', gatherer: 'console-messages'}, + {id: 'CSSUsage', gatherer: 'css-usage'}, + {id: 'Doctype', gatherer: 'dobetterweb/doctype'}, + {id: 'DOMStats', gatherer: 'dobetterweb/domstats'}, + {id: 'EmbeddedContent', gatherer: 'seo/embedded-content'}, + {id: 'FontSize', gatherer: 'seo/font-size'}, + {id: 'Inputs', gatherer: 'inputs'}, + {id: 'GlobalListeners', gatherer: 'global-listeners'}, + {id: 'IFrameElements', gatherer: 'iframe-elements'}, + {id: 'ImageElements', gatherer: 'image-elements'}, + {id: 'InstallabilityErrors', gatherer: 'installability-errors'}, + {id: 'InspectorIssues', gatherer: 'inspector-issues'}, + {id: 'JsUsage', gatherer: 'js-usage'}, + {id: 'LinkElements', gatherer: 'link-elements'}, + {id: 'MainDocumentContent', gatherer: 'main-document-content'}, + {id: 'MetaElements', gatherer: 'meta-elements'}, + {id: 'NetworkUserAgent', gatherer: 'network-user-agent'}, + {id: 'OptimizedImages', gatherer: 'dobetterweb/optimized-images'}, + {id: 'ResponseCompression', gatherer: 'dobetterweb/response-compression'}, + {id: 'RobotsTxt', gatherer: 'seo/robots-txt'}, + {id: 'ServiceWorker', gatherer: 'service-worker'}, + {id: 'ScriptElements', gatherer: 'script-elements'}, + {id: 'Scripts', gatherer: 'scripts'}, + {id: 'SourceMaps', gatherer: 'source-maps'}, + {id: 'Stacks', gatherer: 'stacks'}, + {id: 'TagsBlockingFirstPaint', gatherer: 'dobetterweb/tags-blocking-first-paint'}, + {id: 'TapTargets', gatherer: 'seo/tap-targets'}, + {id: 'TraceElements', gatherer: 'trace-elements'}, + {id: 'ViewportDimensions', gatherer: 'viewport-dimensions'}, + {id: 'WebAppManifest', gatherer: 'web-app-manifest'}, // Artifact copies are renamed for compatibility with legacy artifacts. - {id: artifacts.devtoolsLogs, gatherer: 'devtools-log-compat'}, - {id: artifacts.traces, gatherer: 'trace-compat'}, + {id: 'devtoolsLogs', gatherer: 'devtools-log-compat'}, + {id: 'traces', gatherer: 'trace-compat'}, // FullPageScreenshot comes at the end so all other node analysis is captured. - {id: artifacts.FullPageScreenshot, gatherer: 'full-page-screenshot'}, + {id: 'FullPageScreenshot', gatherer: 'full-page-screenshot'}, - // BFCacheErrors comes at the very end because it can perform a page navigation. - {id: artifacts.BFCacheFailures, gatherer: 'bf-cache-failures'}, + // BFCacheFailures comes at the very end because it can perform a page navigation. + {id: 'BFCacheFailures', gatherer: 'bf-cache-failures'}, ], audits: [ 'is-on-https', diff --git a/docs/configuration.md b/docs/configuration.md index 8a4de7018da3..a55609a83bda 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,3 +1,8 @@ + +> **WARNING**: This config format is for configs in version 10.0 and beyond. Please read the [legacy config](#legacy-configs) section if you are using the old config format. + # Lighthouse Configuration The Lighthouse config object is the primary method of customizing Lighthouse to suit your use case. Using a custom config, you can limit the audits to run, add additional loads of the page under special conditions, add your own custom checks, tweak the scoring, and more. @@ -10,7 +15,7 @@ You can specify a custom config file when using Lighthouse through the CLI or co **custom-config.js file** ```js -module.exports = { +export default { extends: 'lighthouse:default', settings: { onlyAudits: [ @@ -29,9 +34,9 @@ lighthouse --config-path=path/to/custom-config.js https://example.com **Use config file via Node** ```js -const lighthouse = require('lighthouse'); -const config = require('./path/to/custom-config.js'); -lighthouse('https://example.com/', {port: 9222}, config); +import lighthouse from 'lighthouse'; +import config from './path/to/custom-config.js'; +await lighthouse('https://example.com/', {port: 9222}, config); ``` ## Properties @@ -80,44 +85,23 @@ For full list see [our config settings typedef](https://github.com/GoogleChrome/ | onlyAudits | `string[]` | Includes only the specified audits in the final report. Additive with `onlyCategories` and reduces the time to audit a page. | | skipAudits | `string[]` | Excludes the specified audits from the final report. Takes priority over `onlyCategories`, not usable in conjuction with `onlyAudits`, and reduces the time to audit a page. | -### `passes: Object[]` - -The passes property controls how to load the requested URL and what information to gather about the page while loading. Each entry in the passes array represents one load of the page (e.g. 4 entries in `passes` will load the page 4 times), so be judicious about adding multiple entries here to avoid extending run times. +### `artifacts: Object[]` -Each `passes` entry defines basic settings such as how long to wait for the page to load and whether to record a trace file. Additionally a list of **gatherers** to use is defined per pass. Gatherers can read information from the page to generate artifacts which are later used by audits to provide you with a Lighthouse report. For more information on implementing a custom gatherer and the role they play in building a Lighthouse report, refer to the [recipes](https://github.com/GoogleChrome/lighthouse/blob/main/docs/recipes/custom-audit). Also note that `artifacts.devtoolsLogs` will be automatically populated for every pass. Gatherers also have access to this data within the `afterPass` as `traceData.devtoolsLog` (However, most will find the higher-level `traceData.networkRecords` more useful). +The list of artifacts to collect on a single Lighthouse run. This property is required and on extension will be concatenated with the existing set of artifacts. -For list of default pass values, see [our config constants](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/constants.js). - -#### Example ```js { - passes: [ - { - passName: 'fastPass', - gatherers: ['fast-gatherer'], - }, - { - passName: 'slowPass', - recordTrace: true, - useThrottling: true, - networkQuietThresholdMs: 5000, - gatherers: ['slow-gatherer'], - } + artifacts: [ + {id: 'Accessibility', gatherer: 'accessibility'}, + {id: 'AnchorElements', gatherer: 'anchor-elements'}, ] } ``` -#### Options | Name | Type | Description | | -- | -- | -- | -| passName | `string` | A unique identifier for the pass used in audits and during config extension. | -| recordTrace | `boolean` | Records a [trace](https://github.com/GoogleChrome/lighthouse/blob/main/docs/architecture.md#understanding-a-trace) of the pass when enabled. Available to gatherers during `afterPass` as `traceData.trace` and to audits in `artifacts.traces`. | -| useThrottling | `boolean` | Enables throttling of the pass when enabled. | -| pauseAfterLoadMs | `number` | The number of milliseconds to wait after the load event before the pass can continue. Used to ensure the page has had time for post-load JavaScript to execute before ending a trace. (Default: 0) | -| networkQuietThresholdMs | `number` | The number of milliseconds since the last network request to wait before the page should be considered to have reached 'network quiet'. Used to ensure the page has had time for the full waterfall of network requests to complete before ending a trace. (Default: 5000) | -| pauseAfterNetworkQuietMs | `number` | The number of milliseconds to wait after 'network quiet' before the pass can continue. Used to ensure the page has had time for post-network-quiet JavaScript to execute before ending a trace. (Default: 0) | -| blockedUrlPatterns | `string[]` | URLs of requests to block while loading the page. Basic wildcard support using `*`. | -| gatherers | `string[]` | The list of gatherers to run on this pass. This property is required and on extension will be concatenated with the existing set of gatherers. | +| id | `string` | Unique identifier for this artifact. This is how the artifact is referenced in audits. | +| gatherer | `string` | Gatherer used to produce this artifact. Does not need to be unique within the `artifacts` list. | ### `audits: string[]` @@ -133,7 +117,6 @@ The audits property controls which audits to run and include with your Lighthous } ``` - ### `categories: Object|undefined` The categories property controls how to score and organize the audit results in the report. Each category defined in the config will have an entry in the `categories` property of Lighthouse's output. The category output contains the child audit results along with an overall score for the category. @@ -212,3 +195,69 @@ The best examples are the ones Lighthouse uses itself! There are several referen * [core/config/perf-config.js](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/perf-config.js) * [docs/recipes/custom-audit/custom-config.js](https://github.com/GoogleChrome/lighthouse/blob/main/docs/recipes/custom-audit/custom-config.js) * [pwmetrics](https://github.com/paulirish/pwmetrics/blob/v4.1.1/lib/perf-config.ts) + +## Legacy Configs + +Older versions of Lighthouse (pre-10.0) use a slightly different config format. The biggest difference is that the new configs do not include `passes`. If you want to load a page multiple times, we recommend creating a [user flow](https://github.com/GoogleChrome/lighthouse/blob/main/docs/user-flows.md). + +- v9 configuration docs: https://github.com/GoogleChrome/lighthouse/blob/branch-9/docs/configuration.md + +### `passes: Object[]` + +The passes property controls how to load the requested URL and what information to gather about the page while loading. Each entry in the passes array represents one load of the page (e.g. 4 entries in `passes` will load the page 4 times), so be judicious about adding multiple entries here to avoid extending run times. + +Each `passes` entry defines basic settings such as how long to wait for the page to load and whether to record a trace file. Additionally a list of **gatherers** to use is defined per pass. Gatherers can read information from the page to generate artifacts which are later used by audits to provide you with a Lighthouse report. For more information on implementing a custom gatherer and the role they play in building a Lighthouse report, refer to the [recipes](https://github.com/GoogleChrome/lighthouse/blob/main/docs/recipes/custom-audit). Also note that `artifacts.devtoolsLogs` will be automatically populated for every pass. Gatherers also have access to this data within the `afterPass` as `traceData.devtoolsLog` (However, most will find the higher-level `traceData.networkRecords` more useful). + +For list of default pass values, see [our config constants](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/constants.js). + +#### Example +```js +{ + passes: [ + { + passName: 'fastPass', + gatherers: ['fast-gatherer'], + }, + { + passName: 'slowPass', + recordTrace: true, + useThrottling: true, + networkQuietThresholdMs: 5000, + gatherers: ['slow-gatherer'], + } + ] +} +``` + +#### Options +| Name | Type | Description | +| -- | -- | -- | +| passName | `string` | A unique identifier for the pass used in audits and during config extension. | +| recordTrace | `boolean` | Records a [trace](https://github.com/GoogleChrome/lighthouse/blob/main/docs/architecture.md#understanding-a-trace) of the pass when enabled. Available to gatherers during `afterPass` as `traceData.trace` and to audits in `artifacts.traces`. | +| useThrottling | `boolean` | Enables throttling of the pass when enabled. | +| pauseAfterLoadMs | `number` | The number of milliseconds to wait after the load event before the pass can continue. Used to ensure the page has had time for post-load JavaScript to execute before ending a trace. (Default: 0) | +| networkQuietThresholdMs | `number` | The number of milliseconds since the last network request to wait before the page should be considered to have reached 'network quiet'. Used to ensure the page has had time for the full waterfall of network requests to complete before ending a trace. (Default: 5000) | +| pauseAfterNetworkQuietMs | `number` | The number of milliseconds to wait after 'network quiet' before the pass can continue. Used to ensure the page has had time for post-network-quiet JavaScript to execute before ending a trace. (Default: 0) | +| blockedUrlPatterns | `string[]` | URLs of requests to block while loading the page. Basic wildcard support using `*`. | +| gatherers | `string[]` | The list of gatherers to run on this pass. This property is required and on extension will be concatenated with the existing set of gatherers. | + +### Migrating to 10.0 format + +1. Combine the gatherer lists in [`config.passes`](#passes-object) into [`config.artifacts`](#artifacts-object), giving each artifact a unique ID. +1. Remove [`config.passes`](#passes-object) property. Pass properties such as `pauseAfterLoadMs` are defined on `config.settings` in 10.0 configs. + +### Using legacy configs in 10.0 + +The old config format can still be used in 10.0 but it's behind a separate path in the CLI and Node API. + +**Use config file via CLI** +```sh +lighthouse --legacy-navigation --config-path=path/to/custom-config.js https://example.com +``` + +**Use config file via Node** +```js +import {legacyNavigation} from 'lighthouse'; +import config from './path/to/custom-config.js'; +await legacyNavigation('https://example.com/', {port: 9222}, config); +``` From 8b4b44f088ec037a6909952a93d548580bcf0dca Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Tue, 6 Sep 2022 14:13:24 -0700 Subject: [PATCH 4/9] docs(plugins): update to reflect changes in 10.0 (#14322) --- docs/configuration.md | 1 + docs/plugins.md | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a55609a83bda..cbedfb1dcced 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -145,6 +145,7 @@ The categories property controls how to score and organize the audit results in | -- | -- | -- | | title | `string` | The display name of the category. | | description | `string` | The displayed description of the category. | +| supportedModes | `string[]` (optional, [user flows](https://github.com/GoogleChrome/lighthouse/blob/master/docs/user-flows.md)) | The modes supported by the category. Category will support all modes if this is not provided. | | auditRefs | `Object[]` | The audits to include in the category. | | auditRefs[$i].id | `string` | The ID of the audit to include. | | auditRefs[$i].weight | `number` | The weight of the audit in the scoring of the category. | diff --git a/docs/plugins.md b/docs/plugins.md index 327688e0723e..a07f80856720 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -75,7 +75,7 @@ This file contains the configuration for your plugin. It can be called anything **Example `plugin.js`** ```js -module.exports = { +export default { // Additional audits to run on information Lighthouse gathered. audits: [{path: 'lighthouse-plugin-cats/audits/has-cat-images.js'}], @@ -99,7 +99,7 @@ These files contain the logic that will generate results for the Lighthouse repo **Example `audits/has-cat-images.js`** ```js -const Audit = require('lighthouse').Audit; +import {Audit} = from 'lighthouse'; class CatAudit extends Audit { static get meta() { @@ -129,7 +129,7 @@ class CatAudit extends Audit { } } -module.exports = CatAudit; +export default CatAudit; ``` #### Run the plugin locally in development @@ -164,6 +164,7 @@ Defines the display strings of the plugin's category and configures audit scorin - `description: string` _OPTIONAL_ - A more detailed description of the category's purpose. - `manualDescription: string` _OPTIONAL_ - A more detailed description of all of the manual audits in a plugin. Only use this if you've added manual audits. - `auditRefs: Array<{id: string, weight: number, group?: string}>` **REQUIRED** - The list of audits to include in the plugin category along with their overall weight in the score of the plugin category. Each audit ref may optionally reference a group ID from `groups`. +- `supportedModes: string[]` _OPTIONAL_ - Which Lighthouse [modes](https://github.com/GoogleChrome/lighthouse/blob/master/docs/user-flows.md) this plugin supports. Category will support all modes if this is not provided. #### `groups` @@ -239,7 +240,7 @@ You might have noticed that a simple array of network requests is missing from t See below for an example of an audit that processes network requests. ```js -const {Audit, NetworkRecords} = require('lighthouse'); +import {Audit, NetworkRecords} from 'lighthouse'; class HeaderPoliceAudit { static get meta() { @@ -271,7 +272,7 @@ class HeaderPoliceAudit { } } -module.exports = HeaderPoliceAudit; +export default HeaderPoliceAudit; ``` ## Best Practices From bfc9dcb0bc425570d9373d7ef5ac4c8fcf396837 Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Wed, 7 Sep 2022 11:20:19 -0700 Subject: [PATCH 5/9] docs(recipes): update custom-gatherer-puppeteer to use FR (#13940) --- .../custom-config.js | 18 ++++++++----- .../custom-gatherer.js | 25 ++++++------------- .../recipes/custom-gatherer-puppeteer/test.sh | 2 +- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/docs/recipes/custom-gatherer-puppeteer/custom-config.js b/docs/recipes/custom-gatherer-puppeteer/custom-config.js index b778eb92a433..94a74ee36a1e 100644 --- a/docs/recipes/custom-gatherer-puppeteer/custom-config.js +++ b/docs/recipes/custom-gatherer-puppeteer/custom-config.js @@ -5,12 +5,18 @@ */ export default { - passes: [{ - passName: 'defaultPass', - gatherers: [ - 'custom-gatherer', - ], - }], + artifacts: [ + {id: 'CustomGatherer', gatherer: 'custom-gatherer'}, + ], + + navigations: [ + { + id: 'default', + artifacts: [ + 'CustomGatherer', + ], + }, + ], audits: [ 'custom-audit', diff --git a/docs/recipes/custom-gatherer-puppeteer/custom-gatherer.js b/docs/recipes/custom-gatherer-puppeteer/custom-gatherer.js index 4aaf06df3d32..6230686aedb7 100644 --- a/docs/recipes/custom-gatherer-puppeteer/custom-gatherer.js +++ b/docs/recipes/custom-gatherer-puppeteer/custom-gatherer.js @@ -7,24 +7,15 @@ /* global document */ import {Gatherer} from 'lighthouse'; -import puppeteer from 'puppeteer'; - -async function connect(driver) { - const browser = await puppeteer.connect({ - browserWSEndpoint: await driver.wsEndpoint(), - defaultViewport: null, - }); - const {targetInfo} = await driver.sendCommand('Target.getTargetInfo'); - const puppeteerTarget = (await browser.targets()) - .find(target => target._targetId === targetInfo.targetId); - const page = await puppeteerTarget.page(); - return {browser, page, executionContext: driver.executionContext}; -} class CustomGatherer extends Gatherer { - async afterPass(options) { - const {driver} = options; - const {page, executionContext} = await connect(driver); + meta = { + supportedModes: ['navigation', 'timespan', 'snapshot'], + }; + + async getArtifact(context) { + const {driver, page} = context; + const {executionContext} = driver; // Inject an input field for our debugging pleasure. function makeInput() { @@ -41,8 +32,6 @@ class CustomGatherer extends Gatherer { const value = await executionContext.evaluateAsync(`document.querySelector('input').value`); if (value !== '123') throw new Error('huh?'); - // No need to close the browser or page. Puppeteer doesn't own either of them. - return {value}; } } diff --git a/docs/recipes/custom-gatherer-puppeteer/test.sh b/docs/recipes/custom-gatherer-puppeteer/test.sh index e0cf24f82af0..d33a601385f2 100644 --- a/docs/recipes/custom-gatherer-puppeteer/test.sh +++ b/docs/recipes/custom-gatherer-puppeteer/test.sh @@ -3,6 +3,6 @@ # Make sure we're in this `docs/recipes/customer-gatherer-puppeteer` directory cd "$(dirname "$0")" -node node_modules/.bin/lighthouse --legacy-navigation --config-path=custom-config.js https://www.example.com --output=json | +node node_modules/.bin/lighthouse --config-path=custom-config.js https://www.example.com --output=json | jq '.audits["custom-audit"].score' | grep -q 1 From e91cacb0b06523336d24594ecd18ad425a553d4f Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:59:33 -0700 Subject: [PATCH 6/9] docs: update docs/readme.md for 10.0 (#14457) --- docs/readme.md | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 58e4c7d11085..868a418be950 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -7,26 +7,24 @@ internals, see [Lighthouse Architecture](architecture.md). The example below shows how to run Lighthouse programmatically as a Node module. It assumes you've installed Lighthouse as a dependency (`yarn add --dev lighthouse`). -```javascript -const fs = require('fs'); -const lighthouse = require('lighthouse'); -const chromeLauncher = require('chrome-launcher'); +```js +import fs from 'fs'; +import lighthouse from 'lighthouse'; +import chromeLauncher from 'chrome-launcher'; -(async () => { - const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']}); - const options = {logLevel: 'info', output: 'html', onlyCategories: ['performance'], port: chrome.port}; - const runnerResult = await lighthouse('https://example.com', options); +const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']}); +const options = {logLevel: 'info', output: 'html', onlyCategories: ['performance'], port: chrome.port}; +const runnerResult = await lighthouse('https://example.com', options); - // `.report` is the HTML report as a string - const reportHtml = runnerResult.report; - fs.writeFileSync('lhreport.html', reportHtml); +// `.report` is the HTML report as a string +const reportHtml = runnerResult.report; +fs.writeFileSync('lhreport.html', reportHtml); - // `.lhr` is the Lighthouse Result as a JS object - console.log('Report is done for', runnerResult.lhr.finalDisplayedUrl); - console.log('Performance score was', runnerResult.lhr.categories.performance.score * 100); +// `.lhr` is the Lighthouse Result as a JS object +console.log('Report is done for', runnerResult.lhr.finalDisplayedUrl); +console.log('Performance score was', runnerResult.lhr.categories.performance.score * 100); - await chrome.kill(); -})(); +await chrome.kill(); ``` ### Performance-only Lighthouse run @@ -35,9 +33,8 @@ Many modules consuming Lighthouse are only interested in the performance numbers You can limit the audits you run to a particular category or set of audits. ```js -// ... const flags = {onlyCategories: ['performance']}; -launchChromeAndRunLighthouse(url, flags).then( // ... +await lighthouse(url, flags); ``` You can also craft your own config (e.g. [experimental-config.js](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/experimental-config.js)) for custom runs. Also see the [basic custom audit recipe](https://github.com/GoogleChrome/lighthouse/tree/main/docs/recipes/custom-audit). @@ -68,8 +65,7 @@ the `logLevel` flag when calling `lighthouse`. ```javascript const flags = {logLevel: 'info'}; - -launchChromeAndRunLighthouse('https://example.com', flags).then(...); +await lighthouse('https://example.com', flags); ``` ## Configuration @@ -147,7 +143,6 @@ As an example, here's a trace-only run that reports on user timings and critical "user-timings", "critical-request-chains" ], - "categories": { "performance": { "name": "Performance Metrics", From 93bab901b483c5affe6062a471e8ca840b1d87ac Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Tue, 15 Nov 2022 13:38:24 -0800 Subject: [PATCH 7/9] docs(user-flows): use new api location (#14533) --- docs/user-flows.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-flows.md b/docs/user-flows.md index 4440bd5fc825..e0b2b0d8cf76 100644 --- a/docs/user-flows.md +++ b/docs/user-flows.md @@ -40,7 +40,7 @@ In DevTools, navigation is easy: ensure it's the selected mode and then click _A ```js import {writeFileSync} from 'fs'; import puppeteer from 'puppeteer'; -import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; +import {startFlow} from 'lighthouse'; (async function() { const browser = await puppeteer.launch(); @@ -83,7 +83,7 @@ In DevTools, select "Timespan" as the mode and click _Start timespan_. Record wh ```js import {writeFileSync} from 'fs'; import puppeteer from 'puppeteer'; -import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; +import {startFlow} from 'lighthouse'; (async function() { const browser = await puppeteer.launch(); @@ -118,7 +118,7 @@ In DevTools, select "Snapshot" as the mode. Set up the page in the state you wan ```js import {writeFileSync} from 'fs'; import puppeteer from 'puppeteer'; -import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; +import {startFlow} from 'lighthouse'; (async function() { const browser = await puppeteer.launch(); @@ -157,7 +157,7 @@ The below example codifies a user flow for an ecommerce site where the user navi import {writeFileSync} from 'fs'; import puppeteer from 'puppeteer'; import * as pptrTestingLibrary from 'pptr-testing-library'; -import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; +import {startFlow} from 'lighthouse'; const {getDocument, queries} = pptrTestingLibrary; From c456c2ca1164540eff6aeff6c0e33431e4041009 Mon Sep 17 00:00:00 2001 From: Adam Raine Date: Mon, 12 Dec 2022 15:57:32 -0800 Subject: [PATCH 8/9] docs: reintroduce changes to flows for 10.0 (#14710) --- docs/user-flows.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/user-flows.md b/docs/user-flows.md index e0b2b0d8cf76..6d83a5b7a836 100644 --- a/docs/user-flows.md +++ b/docs/user-flows.md @@ -1,6 +1,6 @@ # User Flows in Lighthouse -Historically, Lighthouse has analyzed the cold pageload of a page. Starting in 2022, it can analyze and report on the entire page lifecycle via "user flows". +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 might be interested in flows if… @@ -55,6 +55,11 @@ import {startFlow} from 'lighthouse'; 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()); })(); @@ -64,12 +69,14 @@ import {startFlow} from 'lighthouse'; ##### Triggering a navigation via user interactions -Instead of providing a URL to navigate to, you can provide a callback function, 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. +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. + ### Timespan From e6e8f5acd530aefbefb75a9c7b47768ac50c47cb Mon Sep 17 00:00:00 2001 From: Adam Raine <6752989+adamraine@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:55:37 -0800 Subject: [PATCH 9/9] docs: update custom gatherer recipe for 10.0 (#14765) --- docs/recipes/custom-audit/custom-config.js | 21 ++++----- docs/recipes/custom-audit/memory-audit.js | 43 ++++++++++++++++++ docs/recipes/custom-audit/memory-gatherer.js | 32 +++++++++++++ docs/recipes/custom-audit/package.json | 2 +- docs/recipes/custom-audit/searchable-audit.js | 45 ------------------- .../custom-audit/searchable-gatherer.js | 31 ------------- .../custom-config.js | 9 ---- 7 files changed, 85 insertions(+), 98 deletions(-) create mode 100644 docs/recipes/custom-audit/memory-audit.js create mode 100644 docs/recipes/custom-audit/memory-gatherer.js delete mode 100644 docs/recipes/custom-audit/searchable-audit.js delete mode 100644 docs/recipes/custom-audit/searchable-gatherer.js diff --git a/docs/recipes/custom-audit/custom-config.js b/docs/recipes/custom-audit/custom-config.js index 1457d10edaae..4abb4948a580 100644 --- a/docs/recipes/custom-audit/custom-config.js +++ b/docs/recipes/custom-audit/custom-config.js @@ -8,27 +8,24 @@ export default { // 1. Run your custom tests along with all the default Lighthouse tests. extends: 'lighthouse:default', - // 2. Add gatherer to the default Lighthouse load ('pass') of the page. - passes: [{ - passName: 'defaultPass', - gatherers: [ - 'searchable-gatherer', - ], - }], + // 2. Register new artifact with custom gatherer. + artifacts: [ + {id: 'MemoryProfile', gatherer: 'memory-gatherer'}, + ], // 3. Add custom audit to the list of audits 'lighthouse:default' will run. audits: [ - 'searchable-audit', + 'memory-audit', ], - // 4. Create a new 'My site metrics' section in the default report for our results. + // 4. Create a new 'My site audits' section in the default report for our results. categories: { mysite: { - title: 'My site metrics', - description: 'Metrics for our super awesome site', + title: 'My site audits', + description: 'Audits for our super awesome site', auditRefs: [ // When we add more custom audits, `weight` controls how they're averaged together. - {id: 'searchable-audit', weight: 1}, + {id: 'memory-audit', weight: 1}, ], }, }, diff --git a/docs/recipes/custom-audit/memory-audit.js b/docs/recipes/custom-audit/memory-audit.js new file mode 100644 index 000000000000..dd27e867006e --- /dev/null +++ b/docs/recipes/custom-audit/memory-audit.js @@ -0,0 +1,43 @@ +/** + * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +import {Audit} from 'lighthouse'; + +const MAX_MEMORY_USAGE = 1_000_000; + +/** + * @fileoverview Tests that the memory usage is below a certain threshold. + */ + +class MemoryUsage extends Audit { + static get meta() { + return { + id: 'memory-audit', + title: 'Did not find any large memory usage', + failureTitle: 'Found large memory usage', + description: 'Detects if any memory sample was larger than 1 MB', + + // The name of the custom gatherer class that provides input to this audit. + requiredArtifacts: ['MemoryProfile'], + }; + } + + static audit(artifacts) { + let largestMemoryUsage = 0; + for (const sample of artifacts.MemoryProfile.samples) { + if (sample.total > largestMemoryUsage) { + largestMemoryUsage = sample.total; + } + } + + return { + numericValue: largestMemoryUsage, + score: largestMemoryUsage > MAX_MEMORY_USAGE ? 0 : 1, + }; + } +} + +export default MemoryUsage; diff --git a/docs/recipes/custom-audit/memory-gatherer.js b/docs/recipes/custom-audit/memory-gatherer.js new file mode 100644 index 000000000000..1ba478588135 --- /dev/null +++ b/docs/recipes/custom-audit/memory-gatherer.js @@ -0,0 +1,32 @@ +/** + * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +import {Gatherer} from 'lighthouse'; + +class MemoryProfile extends Gatherer { + meta = { + supportedModes: ['navigation', 'timespan'], + }; + + async startInstrumentation(context) { + const session = context.driver.defaultSession; + await session.sendCommand('Memory.startSampling'); + } + + async stopInstrumentation(context) { + const session = context.driver.defaultSession; + await session.sendCommand('Memory.stopSampling'); + } + + async getArtifact(context) { + const session = context.driver.defaultSession; + const {profile} = await session.sendCommand('Memory.getSamplingProfile'); + + return profile; + } +} + +export default MemoryProfile; diff --git a/docs/recipes/custom-audit/package.json b/docs/recipes/custom-audit/package.json index 0e8af2e02951..c95c0cb9bb26 100644 --- a/docs/recipes/custom-audit/package.json +++ b/docs/recipes/custom-audit/package.json @@ -4,6 +4,6 @@ "type": "module", "scripts": {}, "devDependencies": { - "lighthouse": "^9.5.0" + "lighthouse": "file:../../../dist/lighthouse.tgz" } } diff --git a/docs/recipes/custom-audit/searchable-audit.js b/docs/recipes/custom-audit/searchable-audit.js deleted file mode 100644 index 739a0bbbe640..000000000000 --- a/docs/recipes/custom-audit/searchable-audit.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ - -import {Audit} from 'lighthouse'; - -const MAX_SEARCHABLE_TIME = 4000; - -/** - * @fileoverview Tests that `window.myLoadMetrics.searchableTime` was below the - * test threshold value. - */ - -class LoadAudit extends Audit { - static get meta() { - return { - id: 'searchable-audit', - title: 'Search box initialized and ready', - failureTitle: 'Search box slow to initialize', - description: 'Used to measure time to when the search' + - ' box is initialized and ready to search.', - - // The name of the custom gatherer class that provides input to this audit. - requiredArtifacts: ['TimeToSearchable'], - }; - } - - static audit(artifacts) { - const loadMetrics = artifacts.TimeToSearchable; - - // Audit will pass when the search box loaded in less time than our threshold. - // This score will be binary, so will get a red ✘ or green ✓ in the report. - const belowThreshold = loadMetrics.searchableTime <= MAX_SEARCHABLE_TIME; - - return { - numericValue: loadMetrics.searchableTime, - // Cast true/false to 1/0 - score: Number(belowThreshold), - }; - } -} - -export default LoadAudit; diff --git a/docs/recipes/custom-audit/searchable-gatherer.js b/docs/recipes/custom-audit/searchable-gatherer.js deleted file mode 100644 index 36d694cf5542..000000000000 --- a/docs/recipes/custom-audit/searchable-gatherer.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ - -import {Gatherer} from 'lighthouse'; - -/** - * @fileoverview Extracts `window.myLoadMetrics` from the test page. - */ - -class TimeToSearchable extends Gatherer { - afterPass(options) { - const driver = options.driver; - - return driver.executionContext.evaluateAsync('window.myLoadMetrics') - // Ensure returned value is what we expect. - .then(loadMetrics => { - if (!loadMetrics || loadMetrics.searchableTime === undefined) { - // Throw if page didn't provide the metrics we expect. This isn't - // fatal -- the Lighthouse run will continue, but any audits that - // depend on this gatherer will show this error string in the report. - throw new Error('Unable to find load metrics in page'); - } - return loadMetrics; - }); - } -} - -export default TimeToSearchable; diff --git a/docs/recipes/custom-gatherer-puppeteer/custom-config.js b/docs/recipes/custom-gatherer-puppeteer/custom-config.js index 94a74ee36a1e..72f4637649ec 100644 --- a/docs/recipes/custom-gatherer-puppeteer/custom-config.js +++ b/docs/recipes/custom-gatherer-puppeteer/custom-config.js @@ -9,15 +9,6 @@ export default { {id: 'CustomGatherer', gatherer: 'custom-gatherer'}, ], - navigations: [ - { - id: 'default', - artifacts: [ - 'CustomGatherer', - ], - }, - ], - audits: [ 'custom-audit', ],