lighthouse 11.0.0 → 11.1.0

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 (210) hide show
  1. package/cli/bin.js +1 -1
  2. package/cli/cli-flags.js +1 -1
  3. package/cli/test/smokehouse/core-tests.js +3 -1
  4. package/cli/test/smokehouse/frontends/smokehouse-bin.js +1 -1
  5. package/cli/test/smokehouse/lighthouse-runners/bundle.js +1 -1
  6. package/cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  7. package/cli/test/smokehouse/lighthouse-runners/devtools.js +1 -1
  8. package/core/audits/accessibility/accesskeys.js +1 -1
  9. package/core/audits/accessibility/aria-allowed-attr.js +1 -1
  10. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  11. package/core/audits/accessibility/aria-command-name.js +1 -1
  12. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  13. package/core/audits/accessibility/aria-hidden-body.js +1 -1
  14. package/core/audits/accessibility/aria-hidden-focus.js +1 -1
  15. package/core/audits/accessibility/aria-input-field-name.js +1 -1
  16. package/core/audits/accessibility/aria-meter-name.js +1 -1
  17. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  18. package/core/audits/accessibility/aria-required-attr.js +1 -1
  19. package/core/audits/accessibility/aria-required-children.js +1 -1
  20. package/core/audits/accessibility/aria-required-parent.js +1 -1
  21. package/core/audits/accessibility/aria-roles.js +1 -1
  22. package/core/audits/accessibility/aria-text.js +1 -1
  23. package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
  24. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  25. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  26. package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
  27. package/core/audits/accessibility/aria-valid-attr.js +1 -1
  28. package/core/audits/accessibility/button-name.js +1 -1
  29. package/core/audits/accessibility/bypass.js +1 -1
  30. package/core/audits/accessibility/color-contrast.js +1 -1
  31. package/core/audits/accessibility/definition-list.js +1 -1
  32. package/core/audits/accessibility/dlitem.js +1 -1
  33. package/core/audits/accessibility/document-title.js +1 -1
  34. package/core/audits/accessibility/duplicate-id-active.js +1 -1
  35. package/core/audits/accessibility/duplicate-id-aria.js +2 -1
  36. package/core/audits/accessibility/empty-heading.js +1 -1
  37. package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
  38. package/core/audits/accessibility/frame-title.js +1 -1
  39. package/core/audits/accessibility/heading-order.js +1 -1
  40. package/core/audits/accessibility/html-has-lang.js +1 -1
  41. package/core/audits/accessibility/html-lang-valid.js +1 -1
  42. package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
  43. package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
  44. package/core/audits/accessibility/image-alt.js +1 -1
  45. package/core/audits/accessibility/image-redundant-alt.js +1 -1
  46. package/core/audits/accessibility/input-button-name.js +1 -1
  47. package/core/audits/accessibility/input-image-alt.js +1 -1
  48. package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
  49. package/core/audits/accessibility/label.js +1 -1
  50. package/core/audits/accessibility/landmark-one-main.js +1 -1
  51. package/core/audits/accessibility/link-in-text-block.js +1 -1
  52. package/core/audits/accessibility/link-name.js +1 -1
  53. package/core/audits/accessibility/list.js +1 -1
  54. package/core/audits/accessibility/listitem.js +1 -1
  55. package/core/audits/accessibility/meta-refresh.js +1 -1
  56. package/core/audits/accessibility/meta-viewport.js +1 -1
  57. package/core/audits/accessibility/object-alt.js +1 -1
  58. package/core/audits/accessibility/select-name.js +1 -1
  59. package/core/audits/accessibility/skip-link.js +1 -1
  60. package/core/audits/accessibility/tabindex.js +1 -1
  61. package/core/audits/accessibility/table-duplicate-name.js +1 -1
  62. package/core/audits/accessibility/table-fake-caption.js +1 -1
  63. package/core/audits/accessibility/target-size.js +1 -1
  64. package/core/audits/accessibility/td-has-header.js +1 -1
  65. package/core/audits/accessibility/td-headers-attr.js +1 -1
  66. package/core/audits/accessibility/th-has-data-cells.js +1 -1
  67. package/core/audits/accessibility/valid-lang.js +1 -1
  68. package/core/audits/accessibility/video-caption.js +1 -1
  69. package/core/audits/bf-cache.js +1 -0
  70. package/core/audits/bootup-time.d.ts +6 -0
  71. package/core/audits/bootup-time.js +37 -1
  72. package/core/audits/byte-efficiency/duplicated-javascript.js +1 -0
  73. package/core/audits/byte-efficiency/efficient-animated-content.js +1 -0
  74. package/core/audits/byte-efficiency/legacy-javascript.js +2 -1
  75. package/core/audits/byte-efficiency/modern-image-formats.js +1 -0
  76. package/core/audits/byte-efficiency/offscreen-images.js +1 -0
  77. package/core/audits/byte-efficiency/render-blocking-resources.js +1 -0
  78. package/core/audits/byte-efficiency/total-byte-weight.js +1 -0
  79. package/core/audits/byte-efficiency/unminified-css.js +1 -0
  80. package/core/audits/byte-efficiency/unminified-javascript.js +1 -0
  81. package/core/audits/byte-efficiency/unused-css-rules.js +1 -0
  82. package/core/audits/byte-efficiency/unused-javascript.js +1 -0
  83. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +1 -0
  84. package/core/audits/byte-efficiency/uses-optimized-images.js +1 -0
  85. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +1 -0
  86. package/core/audits/byte-efficiency/uses-responsive-images.js +1 -0
  87. package/core/audits/byte-efficiency/uses-text-compression.js +1 -0
  88. package/core/audits/critical-request-chains.js +1 -0
  89. package/core/audits/dobetterweb/dom-size.d.ts +8 -2
  90. package/core/audits/dobetterweb/dom-size.js +51 -3
  91. package/core/audits/dobetterweb/no-document-write.js +1 -0
  92. package/core/audits/dobetterweb/uses-http2.js +1 -0
  93. package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -0
  94. package/core/audits/font-display.js +1 -0
  95. package/core/audits/image-aspect-ratio.js +10 -1
  96. package/core/audits/installable-manifest.d.ts +0 -6
  97. package/core/audits/installable-manifest.js +1 -17
  98. package/core/audits/largest-contentful-paint-element.js +22 -4
  99. package/core/audits/layout-shift-elements.js +1 -0
  100. package/core/audits/lcp-lazy-loaded.d.ts +3 -2
  101. package/core/audits/lcp-lazy-loaded.js +35 -5
  102. package/core/audits/long-tasks.js +1 -0
  103. package/core/audits/mainthread-work-breakdown.js +1 -0
  104. package/core/audits/non-composited-animations.js +1 -0
  105. package/core/audits/prioritize-lcp-image.js +1 -0
  106. package/core/audits/redirects.js +2 -4
  107. package/core/audits/seo/crawlable-anchors.js +2 -1
  108. package/core/audits/server-response-time.js +1 -0
  109. package/core/audits/third-party-facades.js +14 -5
  110. package/core/audits/third-party-summary.d.ts +4 -2
  111. package/core/audits/third-party-summary.js +25 -11
  112. package/core/audits/unsized-images.js +1 -0
  113. package/core/audits/user-timings.js +1 -0
  114. package/core/audits/uses-rel-preconnect.js +1 -0
  115. package/core/audits/uses-rel-preload.js +1 -0
  116. package/core/audits/viewport.js +1 -0
  117. package/core/audits/work-during-interaction.js +1 -0
  118. package/core/computed/main-resource.d.ts +1 -1
  119. package/core/computed/main-resource.js +10 -4
  120. package/core/computed/page-dependency-graph.js +2 -1
  121. package/core/computed/tbt-impact-tasks.d.ts +8 -13
  122. package/core/computed/tbt-impact-tasks.js +4 -6
  123. package/core/computed/viewport-meta.js +11 -1
  124. package/core/config/config-helpers.js +1 -1
  125. package/core/config/config.js +1 -1
  126. package/core/gather/gatherers/accessibility.js +5 -0
  127. package/core/gather/timespan-runner.d.ts +3 -0
  128. package/core/gather/timespan-runner.js +23 -0
  129. package/core/index.cjs +13 -13
  130. package/core/index.d.cts +15 -2
  131. package/core/lib/asset-saver.d.ts +0 -3
  132. package/core/lib/asset-saver.js +68 -42
  133. package/core/lib/dependency-graph/simulator/network-analyzer.d.ts +7 -1
  134. package/core/lib/dependency-graph/simulator/network-analyzer.js +17 -1
  135. package/core/lib/emulation.js +1 -1
  136. package/core/lib/i18n/i18n.js +2 -2
  137. package/core/runner.js +5 -4
  138. package/dist/report/bundle.esm.js +3 -1
  139. package/dist/report/flow.js +3 -1
  140. package/dist/report/standalone.js +3 -1
  141. package/flow-report/tsconfig.json +0 -2
  142. package/package.json +8 -7
  143. package/readme.md +4 -1
  144. package/report/assets/templates.html +2 -0
  145. package/report/generator/flow-report-assets.js +1 -1
  146. package/report/generator/report-assets.js +1 -1
  147. package/report/generator/tsconfig.json +1 -1
  148. package/report/renderer/components.js +1 -1
  149. package/report/renderer/i18n-formatter.js +1 -1
  150. package/shared/localization/format.js +1 -1
  151. package/shared/localization/locales/ar-XB.json +36 -39
  152. package/shared/localization/locales/ar.json +36 -39
  153. package/shared/localization/locales/bg.json +36 -39
  154. package/shared/localization/locales/ca.json +36 -39
  155. package/shared/localization/locales/cs.json +36 -39
  156. package/shared/localization/locales/da.json +36 -39
  157. package/shared/localization/locales/de.json +36 -39
  158. package/shared/localization/locales/el.json +36 -39
  159. package/shared/localization/locales/en-GB.json +7 -10
  160. package/shared/localization/locales/en-US.json +64 -64
  161. package/shared/localization/locales/en-XA.json +36 -39
  162. package/shared/localization/locales/en-XL.json +64 -64
  163. package/shared/localization/locales/es-419.json +36 -39
  164. package/shared/localization/locales/es.json +36 -39
  165. package/shared/localization/locales/fi.json +36 -39
  166. package/shared/localization/locales/fil.json +36 -39
  167. package/shared/localization/locales/fr.json +36 -39
  168. package/shared/localization/locales/he.json +36 -39
  169. package/shared/localization/locales/hi.json +36 -39
  170. package/shared/localization/locales/hr.json +36 -39
  171. package/shared/localization/locales/hu.json +36 -39
  172. package/shared/localization/locales/id.json +36 -39
  173. package/shared/localization/locales/it.json +36 -39
  174. package/shared/localization/locales/ja.json +36 -39
  175. package/shared/localization/locales/ko.json +36 -39
  176. package/shared/localization/locales/lt.json +36 -39
  177. package/shared/localization/locales/lv.json +36 -39
  178. package/shared/localization/locales/nl.json +36 -39
  179. package/shared/localization/locales/no.json +36 -39
  180. package/shared/localization/locales/pl.json +36 -39
  181. package/shared/localization/locales/pt-PT.json +36 -39
  182. package/shared/localization/locales/pt.json +36 -39
  183. package/shared/localization/locales/ro.json +36 -39
  184. package/shared/localization/locales/ru.json +36 -39
  185. package/shared/localization/locales/sk.json +36 -39
  186. package/shared/localization/locales/sl.json +36 -39
  187. package/shared/localization/locales/sr-Latn.json +36 -39
  188. package/shared/localization/locales/sr.json +36 -39
  189. package/shared/localization/locales/sv.json +36 -39
  190. package/shared/localization/locales/ta.json +36 -39
  191. package/shared/localization/locales/te.json +36 -39
  192. package/shared/localization/locales/th.json +36 -39
  193. package/shared/localization/locales/tr.json +36 -39
  194. package/shared/localization/locales/uk.json +36 -39
  195. package/shared/localization/locales/vi.json +36 -39
  196. package/shared/localization/locales/zh-HK.json +36 -39
  197. package/shared/localization/locales/zh-TW.json +36 -39
  198. package/shared/localization/locales/zh.json +36 -39
  199. package/shared/localization/locales.js +1 -1
  200. package/{root.js → shared/root.js} +2 -1
  201. package/shared/tsconfig.json +0 -1
  202. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
  203. package/third-party/chromium-synchronization/installability-errors-test.js +0 -1
  204. package/third-party/download-content-shell/download-content-shell.js +1 -1
  205. package/tsconfig.json +0 -2
  206. package/types/artifacts.d.ts +2 -1
  207. package/types/audit.d.ts +2 -0
  208. /package/{esm-utils.d.ts → shared/esm-utils.d.ts} +0 -0
  209. /package/{esm-utils.js → shared/esm-utils.js} +0 -0
  210. /package/{root.d.ts → shared/root.d.ts} +0 -0
@@ -1,14 +1,9 @@
1
1
  export { TBTImpactTasksComputed as TBTImpactTasks };
2
- export type TBTImpactTask = LH.Artifacts.TaskNode & {
3
- tbtImpact: number;
4
- selfTbtImpact: number;
5
- };
6
2
  declare const TBTImpactTasksComputed: typeof TBTImpactTasks & {
7
3
  request: (dependencies: import("../index.js").Artifacts.MetricComputationDataInput, context: import("../../types/utility-types.js").default.ImmutableObject<{
8
4
  computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
9
- }>) => Promise<TBTImpactTask[]>;
5
+ }>) => Promise<import("../index.js").Artifacts.TBTImpactTask[]>;
10
6
  };
11
- /** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
12
7
  declare class TBTImpactTasks {
13
8
  /**
14
9
  * @param {LH.Artifacts.TaskNode} task
@@ -28,27 +23,27 @@ declare class TBTImpactTasks {
28
23
  * @param {LH.Artifacts.TaskNode[]} tasks
29
24
  * @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
30
25
  */
31
- static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): TBTImpactTask[];
26
+ static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): import("../index.js").Artifacts.TBTImpactTask[];
32
27
  /**
33
28
  * @param {LH.Artifacts.TaskNode[]} tasks
34
29
  * @param {number} startTimeMs
35
30
  * @param {number} endTimeMs
36
- * @return {TBTImpactTask[]}
31
+ * @return {LH.Artifacts.TBTImpactTask[]}
37
32
  */
38
- static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
33
+ static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
39
34
  /**
40
35
  * @param {LH.Artifacts.TaskNode[]} tasks
41
36
  * @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
42
37
  * @param {number} startTimeMs
43
38
  * @param {number} endTimeMs
44
- * @return {TBTImpactTask[]}
39
+ * @return {LH.Artifacts.TBTImpactTask[]}
45
40
  */
46
- static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
41
+ static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
47
42
  /**
48
43
  * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
49
44
  * @param {LH.Artifacts.ComputedContext} context
50
- * @return {Promise<TBTImpactTask[]>}
45
+ * @return {Promise<LH.Artifacts.TBTImpactTask[]>}
51
46
  */
52
- static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<TBTImpactTask[]>;
47
+ static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.TBTImpactTask[]>;
53
48
  }
54
49
  //# sourceMappingURL=tbt-impact-tasks.d.ts.map
@@ -12,8 +12,6 @@ import {TotalBlockingTime} from './metrics/total-blocking-time.js';
12
12
  import {ProcessedTrace} from './processed-trace.js';
13
13
  import {calculateTbtImpactForEvent} from './metrics/tbt-utils.js';
14
14
 
15
- /** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
16
-
17
15
  class TBTImpactTasks {
18
16
  /**
19
17
  * @param {LH.Artifacts.TaskNode} task
@@ -66,7 +64,7 @@ class TBTImpactTasks {
66
64
  * @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
67
65
  */
68
66
  static createImpactTasks(tasks, taskToImpact) {
69
- /** @type {TBTImpactTask[]} */
67
+ /** @type {LH.Artifacts.TBTImpactTask[]} */
70
68
  const tbtImpactTasks = [];
71
69
 
72
70
  for (const task of tasks) {
@@ -92,7 +90,7 @@ class TBTImpactTasks {
92
90
  * @param {LH.Artifacts.TaskNode[]} tasks
93
91
  * @param {number} startTimeMs
94
92
  * @param {number} endTimeMs
95
- * @return {TBTImpactTask[]}
93
+ * @return {LH.Artifacts.TBTImpactTask[]}
96
94
  */
97
95
  static computeImpactsFromObservedTasks(tasks, startTimeMs, endTimeMs) {
98
96
  /** @type {Map<LH.Artifacts.TaskNode, number>} */
@@ -125,7 +123,7 @@ class TBTImpactTasks {
125
123
  * @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
126
124
  * @param {number} startTimeMs
127
125
  * @param {number} endTimeMs
128
- * @return {TBTImpactTask[]}
126
+ * @return {LH.Artifacts.TBTImpactTask[]}
129
127
  */
130
128
  static computeImpactsFromLantern(tasks, tbtNodeTimings, startTimeMs, endTimeMs) {
131
129
  /** @type {Map<LH.Artifacts.TaskNode, number>} */
@@ -193,7 +191,7 @@ class TBTImpactTasks {
193
191
  /**
194
192
  * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
195
193
  * @param {LH.Artifacts.ComputedContext} context
196
- * @return {Promise<TBTImpactTask[]>}
194
+ * @return {Promise<LH.Artifacts.TBTImpactTask[]>}
197
195
  */
198
196
  static async compute_(metricComputationData, context) {
199
197
  const tbtResult = await TotalBlockingTime.request(metricComputationData, context);
@@ -35,7 +35,17 @@ class ViewportMeta {
35
35
  }
36
36
 
37
37
  const viewportProps = parsedProps.validProperties;
38
- const isMobileOptimized = Boolean(viewportProps.width || viewportProps['initial-scale']);
38
+ const initialScale = Number(viewportProps['initial-scale']);
39
+
40
+ if (!isNaN(initialScale) && initialScale < 1) {
41
+ return {
42
+ hasViewportTag: true,
43
+ isMobileOptimized: false,
44
+ parserWarnings: warnings,
45
+ };
46
+ }
47
+
48
+ const isMobileOptimized = Boolean(viewportProps.width || initialScale);
39
49
 
40
50
  return {
41
51
  hasViewportTag: true,
@@ -16,7 +16,7 @@ import ConfigPlugin from './config-plugin.js';
16
16
  import {Runner} from '../runner.js';
17
17
  import * as i18n from '../lib/i18n/i18n.js';
18
18
  import * as validation from './validation.js';
19
- import {getModuleDirectory} from '../../esm-utils.js';
19
+ import {getModuleDirectory} from '../../shared/esm-utils.js';
20
20
 
21
21
  const require = createRequire(import.meta.url);
22
22
 
@@ -28,7 +28,7 @@ import {
28
28
  mergeConfigFragment,
29
29
  mergeConfigFragmentArrayByKey,
30
30
  } from './config-helpers.js';
31
- import {getModuleDirectory} from '../../esm-utils.js';
31
+ import {getModuleDirectory} from '../../shared/esm-utils.js';
32
32
  import * as format from '../../shared/localization/format.js';
33
33
 
34
34
  const defaultConfigPath = path.join(
@@ -42,12 +42,17 @@ async function runA11yChecks() {
42
42
  'accesskeys': {enabled: true},
43
43
  'area-alt': {enabled: false},
44
44
  'aria-allowed-role': {enabled: true},
45
+ 'aria-braille-equivalent': {enabled: false},
46
+ 'aria-conditional-attr': {enabled: false},
47
+ 'aria-deprecated-role': {enabled: false},
45
48
  'aria-dialog-name': {enabled: true},
49
+ 'aria-prohibited-attr': {enabled: false},
46
50
  'aria-roledescription': {enabled: false},
47
51
  'aria-treeitem-name': {enabled: true},
48
52
  'aria-text': {enabled: true},
49
53
  'audio-caption': {enabled: false},
50
54
  'blink': {enabled: false},
55
+ 'duplicate-id-active': {enabled: true},
51
56
  'duplicate-id': {enabled: false},
52
57
  'empty-heading': {enabled: true},
53
58
  'frame-focusable-content': {enabled: false},
@@ -9,4 +9,7 @@ export function startTimespanGather(page: LH.Puppeteer.Page, options?: {
9
9
  } | undefined): Promise<{
10
10
  endTimespanGather(): Promise<LH.Gatherer.GatherResult>;
11
11
  }>;
12
+ export namespace UIStrings {
13
+ const warningNavigationDetected: string;
14
+ }
12
15
  //# sourceMappingURL=timespan-runner.d.ts.map
@@ -12,6 +12,16 @@ import {getEmptyArtifactState, collectPhaseArtifacts, awaitArtifacts} from './ru
12
12
  import {enableAsyncStacks, prepareTargetForTimespanMode} from './driver/prepare.js';
13
13
  import {initializeConfig} from '../config/config.js';
14
14
  import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
15
+ import * as i18n from '../lib/i18n/i18n.js';
16
+
17
+ /* eslint-disable max-len */
18
+ const UIStrings = {
19
+ /** A warning that indicates page navigations should be audited using navigation mode, as opposed to timespan mode. "navigation mode" refers to a Lighthouse mode that analyzes a page navigation. "timespan mode" refers to a Lighthouse mode that analyzes user interactions over an arbitrary period of time. */
20
+ warningNavigationDetected: 'A page navigation was detected during the run. Using timespan mode to audit page navigations is not recommended. Use navigation mode to audit page navigations for better third-party attribution and main thread detection.',
21
+ };
22
+ /* eslint-enable max-len */
23
+
24
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
15
25
 
16
26
  /**
17
27
  * @param {LH.Puppeteer.Page} page
@@ -45,6 +55,13 @@ async function startTimespanGather(page, options = {}) {
45
55
 
46
56
  await prepareTargetForTimespanMode(driver, resolvedConfig.settings);
47
57
 
58
+ let pageNavigationDetected = false;
59
+ function onFrameNavigated() {
60
+ pageNavigationDetected = true;
61
+ }
62
+
63
+ driver.defaultSession.on('Page.frameNavigated', onFrameNavigated);
64
+
48
65
  const disableAsyncStacks = await enableAsyncStacks(driver.defaultSession);
49
66
 
50
67
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseOptions});
@@ -67,6 +84,11 @@ async function startTimespanGather(page, options = {}) {
67
84
  // We should disable our `Page.frameNavigated` handlers before that.
68
85
  await disableAsyncStacks();
69
86
 
87
+ driver.defaultSession.off('Page.frameNavigated', onFrameNavigated);
88
+ if (pageNavigationDetected) {
89
+ baseArtifacts.LighthouseRunWarnings.push(str_(UIStrings.warningNavigationDetected));
90
+ }
91
+
70
92
  await collectPhaseArtifacts({phase: 'getArtifact', ...phaseOptions});
71
93
  await driver.disconnect();
72
94
 
@@ -82,4 +104,5 @@ async function startTimespanGather(page, options = {}) {
82
104
 
83
105
  export {
84
106
  startTimespanGather,
107
+ UIStrings,
85
108
  };
package/core/index.cjs CHANGED
@@ -5,38 +5,38 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- /** @type {import('./index.js')['default']} */
8
+ /**
9
+ * @typedef ExportType
10
+ * @property {import('./index.js')['startFlow']} startFlow
11
+ * @property {import('./index.js')['navigation']} navigation
12
+ * @property {import('./index.js')['startTimespan']} startTimespan
13
+ * @property {import('./index.js')['snapshot']} snapshot
14
+ */
15
+
16
+ /** @type {import('./index.js')['default'] & ExportType} */
9
17
  const lighthouse = async function lighthouse(...args) {
10
18
  const {default: lighthouse} = await import('./index.js');
11
19
  return lighthouse(...args);
12
20
  };
13
21
 
14
- /** @type {import('./index.js')['startFlow']} */
15
- const startFlow = async function startFlow(...args) {
22
+ lighthouse.startFlow = async function startFlow(...args) {
16
23
  const {startFlow} = await import('./index.js');
17
24
  return startFlow(...args);
18
25
  };
19
26
 
20
- /** @type {import('./index.js')['navigation']} */
21
- const navigation = async function navigation(...args) {
27
+ lighthouse.navigation = async function navigation(...args) {
22
28
  const {navigation} = await import('./index.js');
23
29
  return navigation(...args);
24
30
  };
25
31
 
26
- /** @type {import('./index.js')['startTimespan']} */
27
- const startTimespan = async function startTimespan(...args) {
32
+ lighthouse.startTimespan = async function startTimespan(...args) {
28
33
  const {startTimespan} = await import('./index.js');
29
34
  return startTimespan(...args);
30
35
  };
31
36
 
32
- /** @type {import('./index.js')['snapshot']} */
33
- const snapshot = async function snapshot(...args) {
37
+ lighthouse.snapshot = async function snapshot(...args) {
34
38
  const {snapshot} = await import('./index.js');
35
39
  return snapshot(...args);
36
40
  };
37
41
 
38
42
  module.exports = lighthouse;
39
- module.exports.startFlow = startFlow;
40
- module.exports.navigation = navigation;
41
- module.exports.startTimespan = startTimespan;
42
- module.exports.snapshot = snapshot;
package/core/index.d.cts CHANGED
@@ -1,4 +1,17 @@
1
1
  export = lighthouse;
2
- /** @type {import('./index.js')['default']} */
3
- declare const lighthouse: typeof import("./index.js")['default'];
2
+ /**
3
+ * @typedef ExportType
4
+ * @property {import('./index.js')['startFlow']} startFlow
5
+ * @property {import('./index.js')['navigation']} navigation
6
+ * @property {import('./index.js')['startTimespan']} startTimespan
7
+ * @property {import('./index.js')['snapshot']} snapshot
8
+ */
9
+ /** @type {import('./index.js')['default'] & ExportType} */
10
+ declare const lighthouse: typeof import("./index.js")['default'] & ExportType;
11
+ type ExportType = {
12
+ startFlow: typeof import("./index.js")['startFlow'];
13
+ navigation: typeof import("./index.js")['navigation'];
14
+ startTimespan: typeof import("./index.js")['startTimespan'];
15
+ snapshot: typeof import("./index.js")['snapshot'];
16
+ };
4
17
  //# sourceMappingURL=index.d.cts.map
@@ -1,5 +1,4 @@
1
1
  export type PreparedAssets = {
2
- passName: string;
3
2
  traceData: LH.Trace;
4
3
  devtoolsLog: import("../index.js").DevtoolsLog;
5
4
  };
@@ -35,7 +34,6 @@ export function saveFlowArtifacts(flowArtifacts: LH.UserFlow.FlowArtifacts, base
35
34
  export function saveLhr(lhr: LH.Result, basePath: string): void;
36
35
  /**
37
36
  * @typedef {object} PreparedAssets
38
- * @property {string} passName
39
37
  * @property {LH.Trace} traceData
40
38
  * @property {LH.DevtoolsLog} devtoolsLog
41
39
  */
@@ -60,7 +58,6 @@ export function loadFlowArtifacts(basePath: string): LH.UserFlow.FlowArtifacts;
60
58
  */
61
59
  export function saveAssets(artifacts: LH.Artifacts, audits: LH.Result['audits'], pathWithBasename: string): Promise<void>;
62
60
  /**
63
- * Filter traces and extract screenshots to prepare for saving.
64
61
  * @param {LH.Artifacts} artifacts
65
62
  * @param {LH.Result['audits']} [audits]
66
63
  * @return {Promise<Array<PreparedAssets>>}
@@ -17,17 +17,18 @@ import {MetricTraceEvents} from './traces/metric-trace-events.js';
17
17
  import {NetworkAnalysis} from '../computed/network-analysis.js';
18
18
  import {LoadSimulator} from '../computed/load-simulator.js';
19
19
  import {LighthouseError} from '../lib/lh-error.js';
20
- import {LH_ROOT} from '../../root.js';
20
+ import {LH_ROOT} from '../../shared/root.js';
21
21
 
22
22
  const optionsFilename = 'options.json';
23
23
  const artifactsFilename = 'artifacts.json';
24
24
  const traceSuffix = '.trace.json';
25
25
  const devtoolsLogSuffix = '.devtoolslog.json';
26
+ const defaultPrefix = 'defaultPass';
27
+ const errorPrefix = 'pageLoadError-defaultPass';
26
28
  const stepDirectoryRegex = /^step(\d+)$/;
27
29
 
28
30
  /**
29
31
  * @typedef {object} PreparedAssets
30
- * @property {string} passName
31
32
  * @property {LH.Trace} traceData
32
33
  * @property {LH.DevtoolsLog} devtoolsLog
33
34
  */
@@ -53,32 +54,30 @@ function loadArtifacts(basePath) {
53
54
 
54
55
  const filenames = fs.readdirSync(basePath);
55
56
 
56
- // load devtoolsLogs
57
- artifacts.devtoolsLogs = {};
58
57
  filenames.filter(f => f.endsWith(devtoolsLogSuffix)).forEach(filename => {
59
- const passName = filename.replace(devtoolsLogSuffix, '');
58
+ if (!artifacts.devtoolsLogs) artifacts.devtoolsLogs = {};
59
+ const prefix = filename.replace(devtoolsLogSuffix, '');
60
60
  const devtoolsLog = JSON.parse(fs.readFileSync(path.join(basePath, filename), 'utf8'));
61
- artifacts.devtoolsLogs[passName] = devtoolsLog;
62
- if (passName === 'defaultPass') {
61
+ artifacts.devtoolsLogs[prefix] = devtoolsLog;
62
+ if (prefix === defaultPrefix) {
63
63
  artifacts.DevtoolsLog = devtoolsLog;
64
64
  }
65
- if (passName === 'pageLoadError-defaultPass') {
65
+ if (prefix === errorPrefix) {
66
66
  artifacts.DevtoolsLogError = devtoolsLog;
67
67
  }
68
68
  });
69
69
 
70
- // load traces
71
- artifacts.traces = {};
72
70
  filenames.filter(f => f.endsWith(traceSuffix)).forEach(filename => {
71
+ if (!artifacts.traces) artifacts.traces = {};
73
72
  const file = fs.readFileSync(path.join(basePath, filename), {encoding: 'utf-8'});
74
73
  const trace = JSON.parse(file);
75
- const passName = filename.replace(traceSuffix, '');
76
- artifacts.traces[passName] = Array.isArray(trace) ? {traceEvents: trace} : trace;
77
- if (passName === 'defaultPass') {
78
- artifacts.Trace = artifacts.traces[passName];
74
+ const prefix = filename.replace(traceSuffix, '');
75
+ artifacts.traces[prefix] = Array.isArray(trace) ? {traceEvents: trace} : trace;
76
+ if (prefix === defaultPrefix) {
77
+ artifacts.Trace = artifacts.traces[prefix];
79
78
  }
80
- if (passName === 'pageLoadError-defaultPass') {
81
- artifacts.TraceError = artifacts.traces[passName];
79
+ if (prefix === errorPrefix) {
80
+ artifacts.TraceError = artifacts.traces[prefix];
82
81
  }
83
82
  });
84
83
 
@@ -220,23 +219,34 @@ async function saveArtifacts(artifacts, basePath) {
220
219
  }
221
220
  }
222
221
 
223
- // `DevtoolsLog` and `Trace` will always be the 'defaultPass' version.
222
+ // `devtoolsLogs` and `traces` are duplicate compat artifacts.
224
223
  // We don't need to save them twice, so extract them here.
225
- // eslint-disable-next-line no-unused-vars
226
- const {traces, devtoolsLogs, DevtoolsLog, Trace, ...restArtifacts} = artifacts;
224
+ const {
225
+ // eslint-disable-next-line no-unused-vars
226
+ traces,
227
+ // eslint-disable-next-line no-unused-vars
228
+ devtoolsLogs,
229
+ DevtoolsLog,
230
+ Trace,
231
+ DevtoolsLogError,
232
+ TraceError,
233
+ ...restArtifacts
234
+ } = artifacts;
235
+
236
+ if (Trace) {
237
+ await saveTrace(Trace, `${basePath}/${defaultPrefix}${traceSuffix}`);
238
+ }
227
239
 
228
- // save traces
229
- if (traces) {
230
- for (const [passName, trace] of Object.entries(traces)) {
231
- await saveTrace(trace, `${basePath}/${passName}${traceSuffix}`);
232
- }
240
+ if (TraceError) {
241
+ await saveTrace(TraceError, `${basePath}/${errorPrefix}${traceSuffix}`);
233
242
  }
234
243
 
235
- // save devtools log
236
- if (devtoolsLogs) {
237
- for (const [passName, devtoolsLog] of Object.entries(devtoolsLogs)) {
238
- await saveDevtoolsLog(devtoolsLog, `${basePath}/${passName}${devtoolsLogSuffix}`);
239
- }
244
+ if (DevtoolsLog) {
245
+ await saveDevtoolsLog(DevtoolsLog, `${basePath}/${defaultPrefix}${devtoolsLogSuffix}`);
246
+ }
247
+
248
+ if (DevtoolsLogError) {
249
+ await saveDevtoolsLog(DevtoolsLogError, `${basePath}/${errorPrefix}${devtoolsLogSuffix}`);
240
250
  }
241
251
 
242
252
  // save everything else, using a replacer to serialize LighthouseErrors in the artifacts.
@@ -256,33 +266,49 @@ function saveLhr(lhr, basePath) {
256
266
  }
257
267
 
258
268
  /**
259
- * Filter traces and extract screenshots to prepare for saving.
269
+ * Filter trace and extract screenshots to prepare for saving.
270
+ * @param {LH.Trace} trace
271
+ * @param {LH.Result['audits']} [audits]
272
+ * @return {LH.Trace}
273
+ */
274
+ function prepareTraceAsset(trace, audits) {
275
+ if (!trace) return trace;
276
+
277
+ const traceData = Object.assign({}, trace);
278
+ if (audits) {
279
+ const evts = new MetricTraceEvents(traceData.traceEvents, audits).generateFakeEvents();
280
+ traceData.traceEvents = traceData.traceEvents.concat(evts);
281
+ }
282
+ return traceData;
283
+ }
284
+
285
+ /**
260
286
  * @param {LH.Artifacts} artifacts
261
287
  * @param {LH.Result['audits']} [audits]
262
288
  * @return {Promise<Array<PreparedAssets>>}
263
289
  */
264
290
  async function prepareAssets(artifacts, audits) {
265
- const passNames = Object.keys(artifacts.traces);
266
291
  /** @type {Array<PreparedAssets>} */
267
292
  const assets = [];
268
293
 
269
- for (const passName of passNames) {
270
- const trace = artifacts.traces[passName];
271
- const devtoolsLog = artifacts.devtoolsLogs[passName];
272
-
273
- const traceData = Object.assign({}, trace);
274
- if (audits) {
275
- const evts = new MetricTraceEvents(traceData.traceEvents, audits).generateFakeEvents();
276
- traceData.traceEvents = traceData.traceEvents.concat(evts);
277
- }
278
-
294
+ const devtoolsLog = artifacts.DevtoolsLog;
295
+ const traceData = prepareTraceAsset(artifacts.Trace, audits);
296
+ if (traceData || devtoolsLog) {
279
297
  assets.push({
280
- passName,
281
298
  traceData,
282
299
  devtoolsLog,
283
300
  });
284
301
  }
285
302
 
303
+ const devtoolsLogError = artifacts.DevtoolsLogError;
304
+ const traceErrorData = prepareTraceAsset(artifacts.TraceError, audits);
305
+ if (devtoolsLogError || traceErrorData) {
306
+ assets.push({
307
+ traceData: traceErrorData,
308
+ devtoolsLog: devtoolsLogError,
309
+ });
310
+ }
311
+
286
312
  return assets;
287
313
  }
288
314
 
@@ -174,9 +174,15 @@ export class NetworkAnalyzer {
174
174
  * @return {LH.Artifacts.NetworkRequest|undefined}
175
175
  */
176
176
  static findResourceForUrl(records: Array<LH.Artifacts.NetworkRequest>, resourceUrl: string): LH.Artifacts.NetworkRequest | undefined;
177
+ /**
178
+ * @param {Array<LH.Artifacts.NetworkRequest>} records
179
+ * @param {string} resourceUrl
180
+ * @return {LH.Artifacts.NetworkRequest|undefined}
181
+ */
182
+ static findLastDocumentForUrl(records: Array<LH.Artifacts.NetworkRequest>, resourceUrl: string): LH.Artifacts.NetworkRequest | undefined;
177
183
  /**
178
184
  * Resolves redirect chain given a main document.
179
- * See: {@link NetworkAnalyzer.findResourceForUrl}) for how to retrieve main document.
185
+ * See: {@link NetworkAnalyzer.findLastDocumentForUrl}) for how to retrieve main document.
180
186
  *
181
187
  * @param {LH.Artifacts.NetworkRequest} request
182
188
  * @return {LH.Artifacts.NetworkRequest}
@@ -506,9 +506,25 @@ class NetworkAnalyzer {
506
506
  );
507
507
  }
508
508
 
509
+ /**
510
+ * @param {Array<LH.Artifacts.NetworkRequest>} records
511
+ * @param {string} resourceUrl
512
+ * @return {LH.Artifacts.NetworkRequest|undefined}
513
+ */
514
+ static findLastDocumentForUrl(records, resourceUrl) {
515
+ // equalWithExcludedFragments is expensive, so check that the resourceUrl starts with the request url first
516
+ const matchingRequests = records.filter(request =>
517
+ request.resourceType === 'Document' &&
518
+ // Note: `request.url` should never have a fragment, else this optimization gives wrong results.
519
+ resourceUrl.startsWith(request.url) &&
520
+ UrlUtils.equalWithExcludedFragments(request.url, resourceUrl)
521
+ );
522
+ return matchingRequests[matchingRequests.length - 1];
523
+ }
524
+
509
525
  /**
510
526
  * Resolves redirect chain given a main document.
511
- * See: {@link NetworkAnalyzer.findResourceForUrl}) for how to retrieve main document.
527
+ * See: {@link NetworkAnalyzer.findLastDocumentForUrl}) for how to retrieve main document.
512
528
  *
513
529
  * @param {LH.Artifacts.NetworkRequest} request
514
530
  * @return {LH.Artifacts.NetworkRequest}
@@ -4,7 +4,7 @@
4
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
5
  */
6
6
 
7
- import {lighthouseVersion} from '../../root.js';
7
+ import {lighthouseVersion} from '../../shared/root.js';
8
8
 
9
9
  const NO_THROTTLING_METRICS = {
10
10
  latency: 0,
@@ -13,9 +13,9 @@ import lookupClosestLocale from 'lookup-closest-locale';
13
13
  import log from 'lighthouse-logger';
14
14
 
15
15
  import {getAvailableLocales} from '../../../shared/localization/format.js';
16
- import {LH_ROOT} from '../../../root.js';
16
+ import {LH_ROOT} from '../../../shared/root.js';
17
17
  import {isIcuMessage, formatMessage, DEFAULT_LOCALE} from '../../../shared/localization/format.js';
18
- import {getModulePath} from '../../../esm-utils.js';
18
+ import {getModulePath} from '../../../shared/esm-utils.js';
19
19
 
20
20
  const UIStrings = {
21
21
  /** Used to show the duration in milliseconds that something lasted. The `{timeInMs}` placeholder will be replaced with the time duration, shown in milliseconds (e.g. 63 ms) */
package/core/runner.js CHANGED
@@ -19,8 +19,8 @@ import * as assetSaver from './lib/asset-saver.js';
19
19
  import {Sentry} from './lib/sentry.js';
20
20
  import {ReportGenerator} from '../report/generator/report-generator.js';
21
21
  import {LighthouseError} from './lib/lh-error.js';
22
- import {lighthouseVersion} from '../root.js';
23
- import {getModuleDirectory} from '../esm-utils.js';
22
+ import {lighthouseVersion} from '../shared/root.js';
23
+ import {getModuleDirectory} from '../shared/esm-utils.js';
24
24
  import {EntityClassification} from './computed/entity-classification.js';
25
25
  import UrlUtils from './lib/url-utils.js';
26
26
 
@@ -375,9 +375,10 @@ class Runner {
375
375
 
376
376
  // If trace/devtoolsLog required, check that DEFAULT_PASS trace/devtoolsLog exists.
377
377
  // NOTE: for now, not a pass-specific check of traces or devtoolsLogs.
378
- const noRequiredTrace = artifactName === 'traces' && !artifacts.traces[Audit.DEFAULT_PASS];
378
+ const noRequiredTrace = artifactName === 'traces' &&
379
+ !artifacts.traces?.[Audit.DEFAULT_PASS];
379
380
  const noRequiredDevtoolsLog = artifactName === 'devtoolsLogs' &&
380
- !artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
381
+ !artifacts.devtoolsLogs?.[Audit.DEFAULT_PASS];
381
382
 
382
383
  if (noArtifact || noRequiredTrace || noRequiredDevtoolsLog) {
383
384
  log.warn('Runner',