lighthouse 12.6.1-dev.20250610 → 12.6.1-dev.20250611

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.
@@ -164,7 +164,7 @@ class Audit {
164
164
  if (results.length === 0) {
165
165
  return {
166
166
  type: 'table',
167
- headings: [],
167
+ headings,
168
168
  items: [],
169
169
  summary,
170
170
  };
@@ -174,7 +174,7 @@ class Audit {
174
174
 
175
175
  return {
176
176
  type: 'table',
177
- headings: headings,
177
+ headings,
178
178
  items: results,
179
179
  summary,
180
180
  sortedBy,
@@ -68,10 +68,11 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
68
68
  parsedTrace,
69
69
  insights,
70
70
  });
71
- if (!details || (details.type === 'table' && details.headings.length === 0)) {
71
+ if (!details || (details.type === 'table' && details.items.length === 0)) {
72
72
  return {
73
73
  scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
74
74
  score: null,
75
+ details,
75
76
  };
76
77
  }
77
78
 
@@ -18,6 +18,16 @@ const UIStrings = {
18
18
  columnFailingLink: 'Uncrawlable Link',
19
19
  };
20
20
 
21
+ const hrefAssociatedAttributes = [
22
+ 'target',
23
+ 'download',
24
+ 'ping',
25
+ 'rel',
26
+ 'hreflang',
27
+ 'type',
28
+ 'referrerpolicy',
29
+ ];
30
+
21
31
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
22
32
 
23
33
  class CrawlableAnchors extends Audit {
@@ -45,10 +55,14 @@ class CrawlableAnchors extends Audit {
45
55
  role = '',
46
56
  id,
47
57
  href,
58
+ attributeNames = [],
59
+ listeners = [],
60
+ ancestorListeners = [],
48
61
  }) => {
49
62
  rawHref = rawHref.replace( /\s/g, '');
50
63
  name = name.trim();
51
64
  role = role.trim();
65
+ const hasListener = Boolean(listeners.length || ancestorListeners.length);
52
66
 
53
67
  if (role.length > 0) return;
54
68
  // Ignore mailto links even if they use one of the failing patterns. See https://github.com/GoogleChrome/lighthouse/issues/11443#issuecomment-694898412
@@ -62,6 +76,19 @@ class CrawlableAnchors extends Audit {
62
76
  if (rawHref.startsWith('file:')) return true;
63
77
  if (name.length > 0) return;
64
78
 
79
+ // If the a element has no href attribute, then the element represents a
80
+ // placeholder for where a link might otherwise have been placed, if it had
81
+ // been relevant, consisting of just the element's contents. The target,
82
+ // download, ping, rel, hreflang, type, and referrerpolicy attributes must be
83
+ // omitted if the href attribute is not present.
84
+ // See https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element
85
+ if (
86
+ !attributeNames.includes('href') &&
87
+ hrefAssociatedAttributes.every(attribute => !attributeNames.includes(attribute))
88
+ ) {
89
+ return hasListener;
90
+ }
91
+
65
92
  if (href === '') return true;
66
93
  if (javaScriptVoidRegExp.test(rawHref)) return true;
67
94
 
@@ -263,6 +263,12 @@ function filterConfigByGatherMode(resolvedConfig, mode) {
263
263
  */
264
264
  function filterConfigByExplicitFilters(resolvedConfig, filters) {
265
265
  const {onlyAudits, onlyCategories, skipAudits} = filters;
266
+ if (onlyAudits && !onlyAudits.length) {
267
+ throw new Error(`onlyAudits cannot be an empty array.`);
268
+ }
269
+ if (onlyCategories && !onlyCategories.length) {
270
+ throw new Error(`onlyCategories cannot be an empty array.`);
271
+ }
266
272
 
267
273
  warnOnUnknownOnlyCategories(resolvedConfig.categories, onlyCategories);
268
274
 
@@ -94,6 +94,7 @@ function collectAnchorElements() {
94
94
  rel: node.rel,
95
95
  target: node.target,
96
96
  id: node.getAttribute('id') || '',
97
+ attributeNames: node.getAttributeNames(),
97
98
  // @ts-expect-error - getNodeDetails put into scope via stringification
98
99
  node: getNodeDetails(node),
99
100
  };
@@ -109,6 +110,7 @@ function collectAnchorElements() {
109
110
  rel: '',
110
111
  target: node.target.baseVal || '',
111
112
  id: node.getAttribute('id') || '',
113
+ attributeNames: node.getAttributeNames(),
112
114
  // @ts-expect-error - getNodeDetails put into scope via stringification
113
115
  node: getNodeDetails(node),
114
116
  };
@@ -160,9 +162,27 @@ class AnchorElements extends BaseGatherer {
160
162
  const anchorsWithEventListeners = anchors.map(async anchor => {
161
163
  const listeners = await getEventListeners(session, anchor.node.devtoolsNodePath);
162
164
 
165
+ /** @type {Set<{type: string}>} */
166
+ const ancestorListeners = new Set();
167
+ const splitPath = anchor.node.devtoolsNodePath.split(',');
168
+ const ancestorListenerPromises = [];
169
+ while (splitPath.length >= 2) {
170
+ splitPath.length -= 2;
171
+ const path = splitPath.join(',');
172
+ const promise = getEventListeners(session, path).then(listeners => {
173
+ for (const listener of listeners) {
174
+ ancestorListeners.add(listener);
175
+ }
176
+ }).catch(() => {});
177
+ ancestorListenerPromises.push(promise);
178
+ }
179
+
180
+ await Promise.all(ancestorListenerPromises);
181
+
163
182
  return {
164
183
  ...anchor,
165
184
  listeners,
185
+ ancestorListeners: Array.from(ancestorListeners),
166
186
  };
167
187
  });
168
188
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.6.1-dev.20250610",
4
+ "version": "12.6.1-dev.20250611",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -111,7 +111,7 @@
111
111
  "@testing-library/preact-hooks": "^1.1.0",
112
112
  "@types/archiver": "^2.1.2",
113
113
  "@types/chrome": "^0.0.154",
114
- "@types/configstore": "^4.0.0",
114
+ "@types/configstore": "^6.0.2",
115
115
  "@types/cpy": "^5.1.0",
116
116
  "@types/debug": "^4.1.7",
117
117
  "@types/eslint": "^8.2.1",
@@ -189,7 +189,7 @@
189
189
  "@sentry/node": "^7.0.0",
190
190
  "axe-core": "^4.10.3",
191
191
  "chrome-launcher": "^1.2.0",
192
- "configstore": "^5.0.1",
192
+ "configstore": "^7.0.0",
193
193
  "csp_evaluator": "1.1.5",
194
194
  "devtools-protocol": "0.0.1467305",
195
195
  "enquirer": "^2.3.6",
package/readme.md CHANGED
@@ -52,7 +52,7 @@ The Chrome extension was available prior to Lighthouse being available in Chrome
52
52
 
53
53
  The Node CLI provides the most flexibility in how Lighthouse runs can be configured and reported. Users who want more advanced usage, or want to run Lighthouse in an automated fashion should use the Node CLI.
54
54
 
55
- > **Note**
55
+ > [!NOTE]
56
56
  > Lighthouse requires Node 18.20 or later.
57
57
 
58
58
  **Installation**:
@@ -227,7 +227,8 @@ top of any Lighthouse HTML report and open the report in the
227
227
  In the Viewer, reports can be shared by clicking the share icon in the top
228
228
  right corner and signing in to GitHub.
229
229
 
230
- > **Note**: shared reports are stashed as a secret Gist in GitHub, under your account.
230
+ > [!NOTE]
231
+ > shared reports are stashed as a secret Gist in GitHub, under your account.
231
232
 
232
233
  ## Docs & Recipes
233
234
 
@@ -363,9 +363,13 @@ declare module Artifacts {
363
363
  node: NodeDetails
364
364
  onclick: string
365
365
  id: string
366
+ attributeNames: Array<string>
366
367
  listeners?: Array<{
367
368
  type: Crdp.DOMDebugger.EventListener['type']
368
369
  }>
370
+ ancestorListeners?: Array<{
371
+ type: Crdp.DOMDebugger.EventListener['type']
372
+ }>
369
373
  }
370
374
 
371
375
  type BFCacheReasonMap = {