lighthouse 9.5.0-dev.20230110 → 9.5.0-dev.20230112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/cli/bin.js +6 -6
  2. package/cli/run.js +1 -1
  3. package/cli/test/smokehouse/config/exclusions.js +5 -0
  4. package/cli/test/smokehouse/core-tests.js +14 -14
  5. package/cli/test/smokehouse/lighthouse-runners/bundle.js +9 -9
  6. package/cli/test/smokehouse/lighthouse-runners/cli.js +7 -7
  7. package/cli/test/smokehouse/lighthouse-runners/devtools.js +3 -3
  8. package/cli/test/smokehouse/readme.md +1 -1
  9. package/cli/test/smokehouse/smokehouse.js +13 -13
  10. package/core/audits/bf-cache.js +120 -0
  11. package/core/audits/installable-manifest.js +1 -1
  12. package/core/audits/seo/is-crawlable.js +114 -42
  13. package/core/config/config-helpers.js +11 -11
  14. package/core/config/config-plugin.js +2 -2
  15. package/core/config/config.js +15 -15
  16. package/core/config/default-config.js +8 -3
  17. package/core/config/desktop-config.js +1 -1
  18. package/core/config/experimental-config.js +1 -1
  19. package/core/config/full-config.js +1 -1
  20. package/core/config/lr-desktop-config.js +1 -1
  21. package/core/config/lr-mobile-config.js +1 -1
  22. package/core/config/perf-config.js +1 -1
  23. package/core/config/validation.js +4 -4
  24. package/core/gather/driver/execution-context.js +2 -2
  25. package/core/gather/driver/network-monitor.js +2 -2
  26. package/core/gather/driver/wait-for-condition.js +9 -2
  27. package/core/gather/gatherers/bf-cache-failures.js +21 -3
  28. package/core/gather/navigation-runner.js +1 -1
  29. package/core/gather/snapshot-runner.js +1 -1
  30. package/core/gather/timespan-runner.js +1 -1
  31. package/core/index.js +10 -10
  32. package/core/legacy/config/config.js +23 -23
  33. package/core/legacy/config/legacy-default-config.js +1 -1
  34. package/core/lib/{bfcache-strings.js → bf-cache-strings.js} +1 -0
  35. package/core/user-flow.js +9 -9
  36. package/core/util.cjs +13 -3
  37. package/dist/report/bundle.esm.js +13 -3
  38. package/dist/report/flow.js +1 -1
  39. package/dist/report/standalone.js +1 -1
  40. package/package.json +1 -1
  41. package/report/renderer/util.js +13 -3
  42. package/report/test/generator/report-generator-test.js +1 -1
  43. package/report/test/renderer/util-test.js +2 -0
  44. package/shared/localization/locales/en-US.json +141 -114
  45. package/shared/localization/locales/en-XL.json +141 -114
  46. package/tsconfig.json +1 -0
  47. package/types/config.d.ts +18 -18
  48. package/types/smokehouse.d.ts +2 -2
  49. package/types/user-flow.d.ts +1 -1
package/cli/bin.js CHANGED
@@ -63,20 +63,20 @@ async function begin() {
63
63
 
64
64
  const urlUnderTest = cliFlags._[0];
65
65
 
66
- /** @type {LH.Config.Json|undefined} */
67
- let configJson;
66
+ /** @type {LH.Config|undefined} */
67
+ let config;
68
68
  if (cliFlags.configPath) {
69
69
  // Resolve the config file path relative to where cli was called.
70
70
  cliFlags.configPath = path.resolve(process.cwd(), cliFlags.configPath);
71
71
 
72
72
  if (cliFlags.configPath.endsWith('.json')) {
73
- configJson = JSON.parse(fs.readFileSync(cliFlags.configPath, 'utf-8'));
73
+ config = JSON.parse(fs.readFileSync(cliFlags.configPath, 'utf-8'));
74
74
  } else {
75
75
  const configModuleUrl = url.pathToFileURL(cliFlags.configPath).href;
76
- configJson = (await import(configModuleUrl)).default;
76
+ config = (await import(configModuleUrl)).default;
77
77
  }
78
78
  } else if (cliFlags.preset) {
79
- configJson = (await import(`../core/config/${cliFlags.preset}-config.js`)).default;
79
+ config = (await import(`../core/config/${cliFlags.preset}-config.js`)).default;
80
80
  }
81
81
 
82
82
  if (cliFlags.budgetPath) {
@@ -132,7 +132,7 @@ async function begin() {
132
132
  });
133
133
  }
134
134
 
135
- return runLighthouse(urlUnderTest, cliFlags, configJson);
135
+ return runLighthouse(urlUnderTest, cliFlags, config);
136
136
  }
137
137
 
138
138
  export {
package/cli/run.js CHANGED
@@ -209,7 +209,7 @@ async function potentiallyKillChrome(launchedChrome) {
209
209
  /**
210
210
  * @param {string} url
211
211
  * @param {LH.CliFlags} flags
212
- * @param {LH.Config.Json|undefined} config
212
+ * @param {LH.Config|undefined} config
213
213
  * @return {Promise<LH.RunnerResult|undefined>}
214
214
  */
215
215
  async function runLighthouse(url, flags, config) {
@@ -28,4 +28,9 @@ const exclusions = {
28
28
  ],
29
29
  };
30
30
 
31
+ // https://github.com/GoogleChrome/lighthouse/issues/14271
32
+ for (const array of Object.values(exclusions)) {
33
+ array.push('lantern-idle-callback-short');
34
+ }
35
+
31
36
  export default exclusions;
@@ -14,6 +14,8 @@ import errorsExpiredSsl from './test-definitions/errors-expired-ssl.js';
14
14
  import errorsIframeExpiredSsl from './test-definitions/errors-iframe-expired-ssl.js';
15
15
  import errorsInfiniteLoop from './test-definitions/errors-infinite-loop.js';
16
16
  import formsAutoComplete from './test-definitions/forms-autocomplete.js';
17
+ import fpsMax from './test-definitions/fps-max.js';
18
+ import fpsScaled from './test-definitions/fps-scaled.js';
17
19
  import issuesMixedContent from './test-definitions/issues-mixed-content.js';
18
20
  import lanternFetch from './test-definitions/lantern-fetch.js';
19
21
  import lanternIdleCallbackLong from './test-definitions/lantern-idle-callback-long.js';
@@ -51,10 +53,10 @@ import pwaSvgomg from './test-definitions/pwa-svgomg.js';
51
53
  import redirectsClientPaintServer from './test-definitions/redirects-client-paint-server.js';
52
54
  import redirectsHistoryPushState from './test-definitions/redirects-history-push-state.js';
53
55
  import redirectsMultipleServer from './test-definitions/redirects-multiple-server.js';
54
- import redirectScripts from './test-definitions/redirects-scripts.js';
56
+ import redirectsScripts from './test-definitions/redirects-scripts.js';
57
+ import redirectsSelf from './test-definitions/redirects-self.js';
55
58
  import redirectsSingleClient from './test-definitions/redirects-single-client.js';
56
59
  import redirectsSingleServer from './test-definitions/redirects-single-server.js';
57
- import redirectsSelf from './test-definitions/redirects-self.js';
58
60
  import screenshot from './test-definitions/screenshot.js';
59
61
  import seoFailing from './test-definitions/seo-failing.js';
60
62
  import seoPassing from './test-definitions/seo-passing.js';
@@ -62,8 +64,6 @@ import seoStatus403 from './test-definitions/seo-status-403.js';
62
64
  import seoTapTargets from './test-definitions/seo-tap-targets.js';
63
65
  import sourceMaps from './test-definitions/source-maps.js';
64
66
  import timing from './test-definitions/timing.js';
65
- import fpsScaled from './test-definitions/fps-scaled.js';
66
- import fpsMax from './test-definitions/fps-max.js';
67
67
 
68
68
  /** @type {ReadonlyArray<Smokehouse.TestDfn>} */
69
69
  const smokeTests = [
@@ -77,19 +77,21 @@ const smokeTests = [
77
77
  errorsIframeExpiredSsl,
78
78
  errorsInfiniteLoop,
79
79
  formsAutoComplete,
80
+ fpsMax,
81
+ fpsScaled,
80
82
  issuesMixedContent,
83
+ lanternFetch,
84
+ lanternIdleCallbackLong,
85
+ lanternIdleCallbackShort,
81
86
  lanternOnline,
82
87
  lanternSetTimeout,
83
- lanternFetch,
84
88
  lanternXhr,
85
- lanternIdleCallbackShort,
86
- lanternIdleCallbackLong,
87
89
  legacyJavascript,
88
90
  metricsDebugger,
89
91
  metricsDelayedFcp,
90
92
  metricsDelayedLcp,
91
- metricsTrickyTtiLateFcp,
92
93
  metricsTrickyTti,
94
+ metricsTrickyTtiLateFcp,
93
95
  offlineOnlineOnly,
94
96
  offlineReady,
95
97
  offlineSwBroken,
@@ -107,17 +109,17 @@ const smokeTests = [
107
109
  perfTraceElements,
108
110
  pubads,
109
111
  pwaAirhorner,
110
- pwaChromestatus,
111
- pwaSvgomg,
112
112
  pwaCaltrain,
113
+ pwaChromestatus,
113
114
  pwaRocks,
115
+ pwaSvgomg,
114
116
  redirectsClientPaintServer,
115
117
  redirectsHistoryPushState,
116
118
  redirectsMultipleServer,
117
- redirectScripts,
119
+ redirectsScripts,
120
+ redirectsSelf,
118
121
  redirectsSingleClient,
119
122
  redirectsSingleServer,
120
- redirectsSelf,
121
123
  screenshot,
122
124
  seoFailing,
123
125
  seoPassing,
@@ -125,8 +127,6 @@ const smokeTests = [
125
127
  seoTapTargets,
126
128
  sourceMaps,
127
129
  timing,
128
- fpsScaled,
129
- fpsMax,
130
130
  ];
131
131
 
132
132
  export default smokeTests;
@@ -26,9 +26,9 @@ import {loadArtifacts, saveArtifacts} from '../../../../core/lib/asset-saver.js'
26
26
  // This runs only in the worker. The rest runs on the main thread.
27
27
  if (!isMainThread && parentPort) {
28
28
  (async () => {
29
- const {url, configJson, testRunnerOptions} = workerData;
29
+ const {url, config, testRunnerOptions} = workerData;
30
30
  try {
31
- const result = await runBundledLighthouse(url, configJson, testRunnerOptions);
31
+ const result = await runBundledLighthouse(url, config, testRunnerOptions);
32
32
  // Save to assets directory because LighthouseError won't survive postMessage.
33
33
  const assetsDir = fs.mkdtempSync(os.tmpdir() + '/smoke-bundle-assets-');
34
34
  await saveArtifacts(result.artifacts, assetsDir);
@@ -46,11 +46,11 @@ if (!isMainThread && parentPort) {
46
46
 
47
47
  /**
48
48
  * @param {string} url
49
- * @param {LH.Config.Json|undefined} configJson
49
+ * @param {LH.Config|undefined} config
50
50
  * @param {{isDebug?: boolean, useLegacyNavigation?: boolean}} testRunnerOptions
51
51
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts}>}
52
52
  */
53
- async function runBundledLighthouse(url, configJson, testRunnerOptions) {
53
+ async function runBundledLighthouse(url, config, testRunnerOptions) {
54
54
  if (isMainThread || !parentPort) {
55
55
  throw new Error('must be called in worker');
56
56
  }
@@ -87,12 +87,12 @@ async function runBundledLighthouse(url, configJson, testRunnerOptions) {
87
87
  if (testRunnerOptions.useLegacyNavigation) {
88
88
  const connection = new CriConnection(port);
89
89
  runnerResult =
90
- await legacyNavigation(url, {port, logLevel}, configJson, connection);
90
+ await legacyNavigation(url, {port, logLevel}, config, connection);
91
91
  } else {
92
92
  // Puppeteer is not included in the bundle, we must create the page here.
93
93
  const browser = await puppeteer.connect({browserURL: `http://localhost:${port}`});
94
94
  const page = await browser.newPage();
95
- runnerResult = await lighthouse(url, {port, logLevel}, configJson, page);
95
+ runnerResult = await lighthouse(url, {port, logLevel}, config, page);
96
96
  }
97
97
  if (!runnerResult) throw new Error('No runnerResult');
98
98
 
@@ -109,17 +109,17 @@ async function runBundledLighthouse(url, configJson, testRunnerOptions) {
109
109
  /**
110
110
  * Launch Chrome and do a full Lighthouse run via the Lighthouse DevTools bundle.
111
111
  * @param {string} url
112
- * @param {LH.Config.Json=} configJson
112
+ * @param {LH.Config=} config
113
113
  * @param {{isDebug?: boolean, useLegacyNavigation?: boolean}=} testRunnerOptions
114
114
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
115
115
  */
116
- async function runLighthouse(url, configJson, testRunnerOptions = {}) {
116
+ async function runLighthouse(url, config, testRunnerOptions = {}) {
117
117
  /** @type {string[]} */
118
118
  const logs = [];
119
119
  const worker = new Worker(new URL(import.meta.url), {
120
120
  stdout: true,
121
121
  stderr: true,
122
- workerData: {url, configJson, testRunnerOptions},
122
+ workerData: {url, config, testRunnerOptions},
123
123
  });
124
124
  worker.stdout.setEncoding('utf8');
125
125
  worker.stderr.setEncoding('utf8');
@@ -27,16 +27,16 @@ const execFileAsync = promisify(execFile);
27
27
  /**
28
28
  * Launch Chrome and do a full Lighthouse run via the Lighthouse CLI.
29
29
  * @param {string} url
30
- * @param {LH.Config.Json=} configJson
30
+ * @param {LH.Config=} config
31
31
  * @param {{isDebug?: boolean, useFraggleRock?: boolean}=} testRunnerOptions
32
32
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
33
33
  */
34
- async function runLighthouse(url, configJson, testRunnerOptions = {}) {
34
+ async function runLighthouse(url, config, testRunnerOptions = {}) {
35
35
  const {isDebug} = testRunnerOptions;
36
36
  const tmpDir = `${LH_ROOT}/.tmp/smokehouse`;
37
37
  await fs.mkdir(tmpDir, {recursive: true});
38
38
  const tmpPath = await fs.mkdtemp(`${tmpDir}/smokehouse-`);
39
- return internalRun(url, tmpPath, configJson, testRunnerOptions)
39
+ return internalRun(url, tmpPath, config, testRunnerOptions)
40
40
  // Wait for internalRun() before removing scratch directory.
41
41
  .finally(() => !isDebug && fs.rm(tmpPath, {recursive: true, force: true}));
42
42
  }
@@ -45,11 +45,11 @@ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
45
45
  * Internal runner.
46
46
  * @param {string} url
47
47
  * @param {string} tmpPath
48
- * @param {LH.Config.Json=} configJson
48
+ * @param {LH.Config=} config
49
49
  * @param {{isDebug?: boolean, useLegacyNavigation?: boolean}=} options
50
50
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
51
51
  */
52
- async function internalRun(url, tmpPath, configJson, options) {
52
+ async function internalRun(url, tmpPath, config, options) {
53
53
  const {isDebug = false, useLegacyNavigation = false} = options || {};
54
54
  const localConsole = new LocalConsole();
55
55
 
@@ -72,9 +72,9 @@ async function internalRun(url, tmpPath, configJson, options) {
72
72
  }
73
73
 
74
74
  // Config can be optionally provided.
75
- if (configJson) {
75
+ if (config) {
76
76
  const configPath = `${tmpPath}/config.json`;
77
- await fs.writeFile(configPath, JSON.stringify(configJson));
77
+ await fs.writeFile(configPath, JSON.stringify(config));
78
78
  args.push(`--config-path=${configPath}`);
79
79
  }
80
80
 
@@ -41,16 +41,16 @@ async function setup() {
41
41
  * unless DEVTOOLS_PATH is set.
42
42
  * CHROME_PATH determines which Chrome is used–otherwise the default is puppeteer's chrome binary.
43
43
  * @param {string} url
44
- * @param {LH.Config.Json=} configJson
44
+ * @param {LH.Config=} config
45
45
  * @param {{isDebug?: boolean, useLegacyNavigation?: boolean}=} testRunnerOptions
46
46
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
47
47
  */
48
- async function runLighthouse(url, configJson, testRunnerOptions = {}) {
48
+ async function runLighthouse(url, config, testRunnerOptions = {}) {
49
49
  const chromeFlags = [
50
50
  `--custom-devtools-frontend=file://${devtoolsDir}/out/LighthouseIntegration/gen/front_end`,
51
51
  ];
52
52
  const {lhr, artifacts, logs} = await testUrlFromDevtools(url, {
53
- config: configJson,
53
+ config,
54
54
  chromeFlags,
55
55
  useLegacyNavigation: testRunnerOptions.useLegacyNavigation,
56
56
  });
@@ -14,7 +14,7 @@ See [`SmokehouseOptions`](https://github.com/GoogleChrome/lighthouse/blob/main/c
14
14
  | -------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
15
15
  | `id` | `string` | The string identifier of the test. |
16
16
  | `expectations` | `{lhr: Object, artifacts: Object}` | See below. |
17
- | `config` | `LH.Config.Json` (optional) | An optional Lighthouse config. If not specified, the default config is used. |
17
+ | `config` | `LH.Config` (optional) | An optional Lighthouse config. If not specified, the default config is used. |
18
18
  | `runSerially` | `boolean` (optional) | An optional flag. If set to true, the test won't be run in parallel to other tests. Useful if the test is performance sensitive. |
19
19
 
20
20
  ### Expectations
@@ -134,21 +134,21 @@ function purpleify(str) {
134
134
  }
135
135
 
136
136
  /**
137
- * @param {LH.Config.Json=} configJson
138
- * @return {LH.Config.Json|undefined}
137
+ * @param {LH.Config=} config
138
+ * @return {LH.Config|undefined}
139
139
  */
140
- function convertToLegacyConfig(configJson) {
141
- if (!configJson) return configJson;
140
+ function convertToLegacyConfig(config) {
141
+ if (!config) return config;
142
142
 
143
143
  return {
144
- ...configJson,
144
+ ...config,
145
145
  passes: [{
146
146
  passName: 'defaultPass',
147
- pauseAfterFcpMs: configJson.settings?.pauseAfterFcpMs,
148
- pauseAfterLoadMs: configJson.settings?.pauseAfterLoadMs,
149
- networkQuietThresholdMs: configJson.settings?.networkQuietThresholdMs,
150
- cpuQuietThresholdMs: configJson.settings?.cpuQuietThresholdMs,
151
- blankPage: configJson.settings?.blankPage,
147
+ pauseAfterFcpMs: config.settings?.pauseAfterFcpMs,
148
+ pauseAfterLoadMs: config.settings?.pauseAfterLoadMs,
149
+ networkQuietThresholdMs: config.settings?.networkQuietThresholdMs,
150
+ cpuQuietThresholdMs: config.settings?.cpuQuietThresholdMs,
151
+ blankPage: config.settings?.blankPage,
152
152
  }],
153
153
  };
154
154
  }
@@ -184,15 +184,15 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
184
184
  bufferedConsole.log(` Retrying run (${i} out of ${retries} retries)…`);
185
185
  }
186
186
 
187
- let configJson = smokeTestDefn.config;
187
+ let config = smokeTestDefn.config;
188
188
  if (useLegacyNavigation) {
189
- configJson = convertToLegacyConfig(configJson);
189
+ config = convertToLegacyConfig(config);
190
190
  }
191
191
 
192
192
  // Run Lighthouse.
193
193
  try {
194
194
  result = {
195
- ...await lighthouseRunner(requestedUrl, configJson, {isDebug, useLegacyNavigation}),
195
+ ...await lighthouseRunner(requestedUrl, config, {isDebug, useLegacyNavigation}),
196
196
  networkRequests: takeNetworkRequestUrls ? takeNetworkRequestUrls() : undefined,
197
197
  };
198
198
 
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+
7
+ import {Audit} from './audit.js';
8
+ import * as i18n from '../lib/i18n/i18n.js';
9
+ import {NotRestoredReasonDescription} from '../lib/bf-cache-strings.js';
10
+
11
+ /* eslint-disable max-len */
12
+ const UIStrings = {
13
+ /** Title of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. This title is shown to users if the back/forward cache was used, or if the there was no attempt to restore the page from the back/forward cache. */
14
+ title: `Page didn't prevent back/forward cache restoration`,
15
+ /** Title of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. This title is shown to users if the page attempted to restore from the back/forward cache but the back/forward cache was not used. */
16
+ failureTitle: 'Page prevented back/forward cache restoration',
17
+ /** Description of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. */
18
+ description: 'Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://web.dev/bfcache/)',
19
+ /** Failure type for an error that the user should be able to address themselves. Shown in a table column with other failure types. */
20
+ actionableFailureType: 'Actionable',
21
+ /** Failure type for an error that the user cannot address in the page's code. Shown in a table column with other failure types. */
22
+ notActionableFailureType: 'Not actionable',
23
+ /** Failure type for an error caused by missing browser support. Shown in a table column with other failure types. */
24
+ supportPendingFailureType: 'Pending browser support',
25
+ /** Label for a column in a data table; entries in the column will be a string explaining why a failure occurred. */
26
+ failureReasonColumn: 'Failure reason',
27
+ /** Label for a column in a data table; entries in the column will be a string representing the type of failure preventing the back/forward cache from being used. */
28
+ failureTypeColumn: 'Failure type',
29
+ /**
30
+ * @description [ICU Syntax] Label for an audit identifying the number of back/forward cache failure reasons found in the page.
31
+ */
32
+ displayValue: `{itemCount, plural,
33
+ =1 {1 failure reason}
34
+ other {# failure reasons}
35
+ }`,
36
+ };
37
+ /* eslint-enable max-len */
38
+
39
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
40
+
41
+ /** @type {LH.Crdp.Page.BackForwardCacheNotRestoredReasonType[]} */
42
+ const ORDERED_FAILURE_TYPES = ['PageSupportNeeded', 'SupportPending', 'Circumstantial'];
43
+
44
+ /** @type {Record<LH.Crdp.Page.BackForwardCacheNotRestoredReasonType, string | LH.IcuMessage>} */
45
+ const FAILURE_TYPE_TO_STRING = {
46
+ PageSupportNeeded: str_(UIStrings.actionableFailureType),
47
+ Circumstantial: str_(UIStrings.notActionableFailureType),
48
+ SupportPending: str_(UIStrings.supportPendingFailureType),
49
+ };
50
+
51
+ class BFCache extends Audit {
52
+ /**
53
+ * @return {LH.Audit.Meta}
54
+ */
55
+ static get meta() {
56
+ return {
57
+ id: 'bf-cache',
58
+ title: str_(UIStrings.title),
59
+ failureTitle: str_(UIStrings.failureTitle),
60
+ description: str_(UIStrings.description),
61
+ supportedModes: ['navigation', 'timespan'],
62
+ requiredArtifacts: ['BFCacheFailures'],
63
+ };
64
+ }
65
+
66
+ /**
67
+ * @param {LH.Artifacts} artifacts
68
+ * @return {Promise<LH.Audit.Product>}
69
+ */
70
+ static async audit(artifacts) {
71
+ const failures = artifacts.BFCacheFailures;
72
+ if (!failures.length) return {score: 1};
73
+
74
+ // TODO: Analyze more than one bf cache failure.
75
+ const {notRestoredReasonsTree} = failures[0];
76
+
77
+ /** @type {LH.Audit.Details.TableItem[]} */
78
+ const results = [];
79
+
80
+ for (const failureType of ORDERED_FAILURE_TYPES) {
81
+ const reasonsMap = notRestoredReasonsTree[failureType];
82
+
83
+ for (const [reason, frameUrls] of Object.entries(reasonsMap)) {
84
+ results.push({
85
+ reason: NotRestoredReasonDescription[reason]?.name ?? reason,
86
+ failureType: FAILURE_TYPE_TO_STRING[failureType],
87
+ subItems: {
88
+ type: 'subitems',
89
+ items: frameUrls.map(frameUrl => ({frameUrl})),
90
+ },
91
+ // Include hidden protocol reason code for debugging.
92
+ protocolReason: reason,
93
+ });
94
+ }
95
+ }
96
+
97
+ /** @type {LH.Audit.Details.Table['headings']} */
98
+ const headings = [
99
+ /* eslint-disable max-len */
100
+ {key: 'reason', valueType: 'text', subItemsHeading: {key: 'frameUrl', valueType: 'url'}, label: str_(UIStrings.failureReasonColumn)},
101
+ {key: 'failureType', valueType: 'text', label: str_(UIStrings.failureTypeColumn)},
102
+ /* eslint-enable max-len */
103
+ ];
104
+
105
+ const details = Audit.makeTableDetails(headings, results);
106
+
107
+ const displayValue = results.length ?
108
+ str_(UIStrings.displayValue, {itemCount: results.length}) :
109
+ undefined;
110
+
111
+ return {
112
+ score: results.length ? 0 : 1,
113
+ displayValue,
114
+ details,
115
+ };
116
+ }
117
+ }
118
+
119
+ export default BFCache;
120
+ export {UIStrings};
@@ -16,7 +16,7 @@ const UIStrings = {
16
16
  'failureTitle': 'Web app manifest or service worker do not meet the installability requirements',
17
17
  /** Description of a Lighthouse audit that tells the user why installability is important for webapps. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
18
18
  'description': `Service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. With proper service worker and manifest implementations, browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more about manifest installability requirements](https://developer.chrome.com/docs/lighthouse/pwa/installable-manifest/).`,
19
- /** Description Table column header for the observed value of the Installability failure reason statistic. */
19
+ /** Label for a column in a data table; entries in the column will be a string explaining why a failure occurred. */
20
20
  'columnValue': 'Failure reason',
21
21
  /**
22
22
  * @description [ICU Syntax] Label for an audit identifying the number of installability errors found in the page.