lighthouse 9.5.0-dev.20220423 → 9.5.0-dev.20220426

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.
@@ -177,7 +177,7 @@ function getFlags(manualArgv, options = {}) {
177
177
  },
178
178
  'enable-error-reporting': {
179
179
  type: 'boolean',
180
- describe: 'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://git.io/vFFTO',
180
+ describe: 'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://github.com/GoogleChrome/lighthouse/blob/master/docs/error-reporting.md',
181
181
  },
182
182
  'gather-mode': {
183
183
  alias: 'G',
@@ -66,7 +66,6 @@ class Deprecations extends Audit {
66
66
  deprecations = artifacts.InspectorIssues.deprecationIssue
67
67
  // TODO: translate these strings.
68
68
  // see https://github.com/GoogleChrome/lighthouse/issues/13895
69
- // @ts-expect-error: .type hasn't released to npm yet
70
69
  .filter(deprecation => !deprecation.type || deprecation.type === 'Untranslated')
71
70
  .map(deprecation => {
72
71
  const {scriptId, url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
@@ -19,12 +19,14 @@ const UIStrings = {
19
19
  '[Learn more](https://web.dev/doctype/).',
20
20
  /** Explanatory message stating that the document has no doctype. */
21
21
  explanationNoDoctype: 'Document must contain a doctype',
22
+ /** Explanatory message stating that the document has wrong doctype */
23
+ explanationWrongDoctype: 'Document contains a doctype that triggers quirks-mode',
22
24
  /** Explanatory message stating that the publicId field is not empty. */
23
25
  explanationPublicId: 'Expected publicId to be an empty string',
24
26
  /** Explanatory message stating that the systemId field is not empty. */
25
27
  explanationSystemId: 'Expected systemId to be an empty string',
26
28
  /** Explanatory message stating that the doctype is set, but is not "html" and is therefore invalid. */
27
- explanationBadDoctype: 'Doctype name must be the lowercase string `html`',
29
+ explanationBadDoctype: 'Doctype name must be the string `html`',
28
30
  };
29
31
 
30
32
  const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
@@ -56,9 +58,10 @@ class Doctype extends Audit {
56
58
  }
57
59
 
58
60
  // only set constants once we know there is a doctype
59
- const doctypeName = artifacts.Doctype.name.trim();
61
+ const doctypeName = artifacts.Doctype.name;
60
62
  const doctypePublicId = artifacts.Doctype.publicId;
61
63
  const doctypeSystemId = artifacts.Doctype.systemId;
64
+ const compatMode = artifacts.Doctype.documentCompatMode;
62
65
 
63
66
  if (doctypePublicId !== '') {
64
67
  return {
@@ -74,17 +77,25 @@ class Doctype extends Audit {
74
77
  };
75
78
  }
76
79
 
77
- /* Note that the value for name is case sensitive,
78
- and must be the string `html`. For details see:
79
- https://html.spec.whatwg.org/multipage/parsing.html#the-initial-insertion-mode */
80
- if (doctypeName === 'html') {
80
+ /* Note that the casing of this property is normalized to be lowercase.
81
+ see: https://html.spec.whatwg.org/#doctype-name-state */
82
+ if (doctypeName !== 'html') {
81
83
  return {
82
- score: 1,
84
+ score: 0,
85
+ explanation: str_(UIStrings.explanationBadDoctype),
83
86
  };
84
- } else {
87
+ }
88
+
89
+ // Catch-all for any quirks-mode situations the above checks didn't get.
90
+ // https://github.com/GoogleChrome/lighthouse/issues/10030
91
+ if (compatMode === 'BackCompat') {
85
92
  return {
86
93
  score: 0,
87
- explanation: str_(UIStrings.explanationBadDoctype),
94
+ explanation: str_(UIStrings.explanationWrongDoctype),
95
+ };
96
+ } else {
97
+ return {
98
+ score: 1,
88
99
  };
89
100
  }
90
101
  }
@@ -12,9 +12,6 @@ const axeLibSource = require('../../lib/axe.js').source;
12
12
  const pageFunctions = require('../../lib/page-functions.js');
13
13
 
14
14
  /**
15
- * This is run in the page, not Lighthouse itself.
16
- * axe.run returns a promise which fulfills with a results object
17
- * containing any violations.
18
15
  * @return {Promise<LH.Artifacts.Accessibility>}
19
16
  */
20
17
  /* c8 ignore start */
@@ -38,8 +35,11 @@ async function runA11yChecks() {
38
35
  'wcag2aa',
39
36
  ],
40
37
  },
38
+ // resultTypes doesn't limit the output of the axeResults object. Instead, if it's defined,
39
+ // some expensive element identification is done only for the respective types. https://github.com/dequelabs/axe-core/blob/f62f0cf18f7b69b247b0b6362cf1ae71ffbf3a1b/lib/core/reporters/helpers/process-aggregate.js#L61-L97
41
40
  resultTypes: ['violations', 'inapplicable'],
42
41
  rules: {
42
+ // Consider http://go/prcpg for expert review of the aXe rules.
43
43
  'tabindex': {enabled: true},
44
44
  'accesskeys': {enabled: true},
45
45
  'heading-order': {enabled: true},
@@ -60,6 +60,12 @@ async function runA11yChecks() {
60
60
  // https://github.com/dequelabs/axe-core/issues/2958
61
61
  'nested-interactive': {enabled: false},
62
62
  'frame-focusable-content': {enabled: false},
63
+ 'aria-roledescription': {enabled: false},
64
+ 'scrollable-region-focusable': {enabled: false},
65
+ // TODO(paulirish): create audits and enable these 3.
66
+ 'input-button-name': {enabled: false},
67
+ 'role-img-alt': {enabled: false},
68
+ 'select-name': {enabled: false},
63
69
  },
64
70
  });
65
71
 
@@ -70,7 +76,8 @@ async function runA11yChecks() {
70
76
  return {
71
77
  violations: axeResults.violations.map(createAxeRuleResultArtifact),
72
78
  incomplete: axeResults.incomplete.map(createAxeRuleResultArtifact),
73
- notApplicable: axeResults.inapplicable.map(result => ({id: result.id})),
79
+ notApplicable: axeResults.inapplicable.map(result => ({id: result.id})), // FYI: inapplicable => notApplicable!
80
+ passes: axeResults.passes.map(result => ({id: result.id})),
74
81
  version: axeResults.testEngine.version,
75
82
  };
76
83
  }
@@ -150,6 +157,11 @@ class Accessibility extends FRGatherer {
150
157
  supportedModes: ['snapshot', 'navigation'],
151
158
  };
152
159
 
160
+ static pageFns = {
161
+ runA11yChecks,
162
+ createAxeRuleResultArtifact,
163
+ };
164
+
153
165
  /**
154
166
  * @param {LH.Gatherer.FRTransitionalContext} passContext
155
167
  * @return {Promise<LH.Artifacts.Accessibility>}
@@ -12,7 +12,8 @@ const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
12
12
  /**
13
13
  * Get and return `name`, `publicId`, `systemId` from
14
14
  * `document.doctype`
15
- * @return {{name: string, publicId: string, systemId: string} | null}
15
+ * and `compatMode` from `document` to check `quirks-mode`
16
+ * @return {{name: string, publicId: string, systemId: string, documentCompatMode: string} | null}
16
17
  */
17
18
  function getDoctype() {
18
19
  // An example of this is warnerbros.com/archive/spacejam/movie/jam.htm
@@ -20,8 +21,9 @@ function getDoctype() {
20
21
  return null;
21
22
  }
22
23
 
24
+ const documentCompatMode = document.compatMode;
23
25
  const {name, publicId, systemId} = document.doctype;
24
- return {name, publicId, systemId};
26
+ return {name, publicId, systemId, documentCompatMode};
25
27
  }
26
28
 
27
29
  class Doctype extends FRGatherer {
@@ -612,39 +612,59 @@ class TraceProcessor {
612
612
  // Find the inspected frame
613
613
  const mainFrameIds = this.findMainFrameIds(keyEvents);
614
614
 
615
- const frames = keyEvents
615
+ /** @type {Map<string, {id: string, url: string, parent?: string}>} */
616
+ const framesById = new Map();
617
+
618
+ // Begin collection of frame tree information with TracingStartedInBrowser,
619
+ // which should be present even without navigations.
620
+ const tracingStartedFrames = keyEvents
621
+ .find(e => e.name === 'TracingStartedInBrowser')?.args?.data?.frames;
622
+ if (tracingStartedFrames) {
623
+ for (const frame of tracingStartedFrames) {
624
+ framesById.set(frame.frame, {
625
+ id: frame.frame,
626
+ url: frame.url,
627
+ parent: frame.parent,
628
+ });
629
+ }
630
+ }
631
+
632
+ // Update known frames if FrameCommittedInBrowser events come in, typically
633
+ // with updated `url`, as well as pid, etc. Some traces (like timespans) may
634
+ // not have any committed frames.
635
+ keyEvents
616
636
  .filter(/** @return {evt is FrameCommittedEvent} */ evt => {
617
637
  return Boolean(
618
638
  evt.name === 'FrameCommittedInBrowser' &&
619
639
  evt.args.data?.frame &&
620
640
  evt.args.data.url
621
641
  );
622
- })
623
- .map(evt => {
624
- return {
642
+ }).forEach(evt => {
643
+ framesById.set(evt.args.data.frame, {
625
644
  id: evt.args.data.frame,
626
645
  url: evt.args.data.url,
627
646
  parent: evt.args.data.parent,
628
- };
647
+ });
629
648
  });
649
+ const frames = [...framesById.values()];
630
650
  const frameIdToRootFrameId = this.resolveRootFrames(frames);
631
651
 
632
652
  // Filter to just events matching the main frame ID, just to make sure.
633
653
  const frameEvents = keyEvents.filter(e => e.args.frame === mainFrameIds.frameId);
634
654
 
635
655
  // Filter to just events matching the main frame ID or any child frame IDs.
636
- // In practice, there should always be FrameCommittedInBrowser events to define the frame tree.
637
- // Unfortunately, many test traces do not include FrameCommittedInBrowser events due to minification.
638
- // This ensures there is always a minimal frame tree and events so those tests don't fail.
639
656
  let frameTreeEvents = [];
640
657
  if (frameIdToRootFrameId.has(mainFrameIds.frameId)) {
641
658
  frameTreeEvents = keyEvents.filter(e => {
642
659
  return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
643
660
  });
644
661
  } else {
662
+ // In practice, there should always be TracingStartedInBrowser/FrameCommittedInBrowser events to
663
+ // define the frame tree. Unfortunately, many test traces do not that frame info due to minification.
664
+ // This ensures there is always a minimal frame tree and events so those tests don't fail.
645
665
  log.warn(
646
666
  'trace-of-tab',
647
- 'frameTreeEvents may be incomplete, make sure the trace has FrameCommittedInBrowser events'
667
+ 'frameTreeEvents may be incomplete, make sure the trace has frame events'
648
668
  );
649
669
  frameIdToRootFrameId.set(mainFrameIds.frameId, mainFrameIds.frameId);
650
670
  frameTreeEvents = frameEvents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.5.0-dev.20220423",
3
+ "version": "9.5.0-dev.20220426",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -139,7 +139,7 @@
139
139
  "cpy": "^8.1.2",
140
140
  "cross-env": "^7.0.2",
141
141
  "csv-validator": "^0.0.3",
142
- "devtools-protocol": "0.0.975298",
142
+ "devtools-protocol": "0.0.995287",
143
143
  "es-main": "^1.0.2",
144
144
  "eslint": "^8.4.1",
145
145
  "eslint-config-google": "^0.14.0",
@@ -210,8 +210,8 @@
210
210
  "yargs-parser": "^21.0.0"
211
211
  },
212
212
  "resolutions": {
213
- "puppeteer/**/devtools-protocol": "0.0.975298",
214
- "puppeteer-core/**/devtools-protocol": "0.0.975298"
213
+ "puppeteer/**/devtools-protocol": "0.0.995287",
214
+ "puppeteer-core/**/devtools-protocol": "0.0.995287"
215
215
  },
216
216
  "repository": "GoogleChrome/lighthouse",
217
217
  "keywords": [
package/readme.md CHANGED
@@ -99,7 +99,7 @@ Configuration:
99
99
  --screenEmulation Sets screen emulation parameters. See also --preset. Use --screenEmulation.disabled to disable. Otherwise set these 4 parameters individually: --screenEmulation.mobile --screenEmulation.width=360 --screenEmulation.height=640 --screenEmulation.deviceScaleFactor=2
100
100
  --emulatedUserAgent Sets useragent emulation [string]
101
101
  --max-wait-for-load The timeout (in milliseconds) to wait before the page is considered done loading and the run should continue. WARNING: Very high values can lead to large traces and instability [number]
102
- --enable-error-reporting Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://git.io/vFFTO [boolean]
102
+ --enable-error-reporting Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://github.com/GoogleChrome/lighthouse/blob/master/docs/error-reporting.md [boolean]
103
103
  --gather-mode, -G Collect artifacts from a connected browser and save to disk. (Artifacts folder path may optionally be provided). If audit-mode is not also enabled, the run will quit early.
104
104
  --audit-mode, -A Process saved artifacts from disk. (Artifacts folder path may be provided, otherwise defaults to ./latest-run/)
105
105
  --only-audits Only run the specified audits [array]
@@ -328,7 +328,7 @@ Array [
328
328
  }
329
329
  });
330
330
 
331
- it('uses null if the metric is missing its value', () => {
331
+ it('uses null if the metric\'s value is undefined', () => {
332
332
  const categoryClone = JSON.parse(JSON.stringify(category));
333
333
  const lcp = categoryClone.auditRefs.find(audit => audit.id === 'largest-contentful-paint');
334
334
  lcp.result.numericValue = undefined;
@@ -336,6 +336,16 @@ Array [
336
336
  const href = renderer._getScoringCalculatorHref(categoryClone.auditRefs);
337
337
  expect(href).toContain('LCP=null');
338
338
  });
339
+
340
+ it('uses null if the metric\'s value is null (LR)', () => {
341
+ const categoryClone = JSON.parse(JSON.stringify(category));
342
+ const lcp = categoryClone.auditRefs.find(audit => audit.id === 'largest-contentful-paint');
343
+ // In LR, we think there might be some case where undefined becomes null, but we can't prove it.
344
+ lcp.result.numericValue = null;
345
+ lcp.result.score = null;
346
+ const href = renderer._getScoringCalculatorHref(categoryClone.auditRefs);
347
+ expect(href).toContain('LCP=null');
348
+ });
339
349
  });
340
350
 
341
351
  // This is done all in CSS, but tested here.
@@ -777,7 +777,7 @@
777
777
  "message": "Specifying a doctype prevents the browser from switching to quirks-mode. [Learn more](https://web.dev/doctype/)."
778
778
  },
779
779
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationBadDoctype": {
780
- "message": "Doctype name must be the lowercase string `html`"
780
+ "message": "Doctype name must be the string `html`"
781
781
  },
782
782
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationNoDoctype": {
783
783
  "message": "Document must contain a doctype"
@@ -788,6 +788,9 @@
788
788
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationSystemId": {
789
789
  "message": "Expected systemId to be an empty string"
790
790
  },
791
+ "lighthouse-core/audits/dobetterweb/doctype.js | explanationWrongDoctype": {
792
+ "message": "Document contains a doctype that triggers quirks-mode"
793
+ },
791
794
  "lighthouse-core/audits/dobetterweb/doctype.js | failureTitle": {
792
795
  "message": "Page lacks the HTML doctype, thus triggering quirks-mode"
793
796
  },
@@ -777,7 +777,7 @@
777
777
  "message": "Ŝṕêćîf́ŷín̂ǵ â d́ôćt̂ýp̂é p̂ŕêv́êńt̂ś t̂h́ê b́r̂óŵśêŕ f̂ŕôḿ ŝẃît́ĉh́îńĝ t́ô q́ûír̂ḱŝ-ḿôd́ê. [Ĺêár̂ń m̂ór̂é](https://web.dev/doctype/)."
778
778
  },
779
779
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationBadDoctype": {
780
- "message": "D̂óĉt́ŷṕê ńâḿê ḿûśt̂ b́ê t́ĥé l̂óŵéćâś śt̂ŕîńĝ `html`"
780
+ "message": "D̂óĉt́ŷṕê ńâḿê ḿûśt̂ b́ê t́ĥé ŝt́r̂ín̂ǵ `html`"
781
781
  },
782
782
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationNoDoctype": {
783
783
  "message": "D̂óĉúm̂én̂t́ m̂úŝt́ ĉón̂t́âín̂ á d̂óĉt́ŷṕê"
@@ -788,6 +788,9 @@
788
788
  "lighthouse-core/audits/dobetterweb/doctype.js | explanationSystemId": {
789
789
  "message": "Êx́p̂éĉt́êd́ ŝýŝt́êḿÎd́ t̂ó b̂é âń êḿp̂t́ŷ śt̂ŕîńĝ"
790
790
  },
791
+ "lighthouse-core/audits/dobetterweb/doctype.js | explanationWrongDoctype": {
792
+ "message": "D̂óĉúm̂én̂t́ ĉón̂t́âín̂ś â d́ôćt̂ýp̂é t̂h́ât́ t̂ŕîǵĝér̂ś q̂úîŕk̂ś-m̂ód̂é"
793
+ },
791
794
  "lighthouse-core/audits/dobetterweb/doctype.js | failureTitle": {
792
795
  "message": "P̂áĝé l̂áĉḱŝ t́ĥé ĤT́M̂Ĺ d̂óĉt́ŷṕê, t́ĥúŝ t́r̂íĝǵêŕîńĝ q́ûír̂ḱŝ-ḿôd́ê"
793
796
  },
@@ -238,6 +238,7 @@ declare module Artifacts {
238
238
  interface Accessibility {
239
239
  violations: Array<AxeRuleResult>;
240
240
  notApplicable: Array<Pick<AxeRuleResult, 'id'>>;
241
+ passes: Array<Pick<AxeRuleResult, 'id'>>;
241
242
  incomplete: Array<AxeRuleResult>;
242
243
  version: string;
243
244
  }
@@ -251,6 +252,7 @@ declare module Artifacts {
251
252
  name: string;
252
253
  publicId: string;
253
254
  systemId: string;
255
+ documentCompatMode: string;
254
256
  }
255
257
 
256
258
  interface DOMStats {
@@ -960,6 +962,7 @@ export interface TraceEvent {
960
962
  documentLoaderURL?: string;
961
963
  frames?: {
962
964
  frame: string;
965
+ url: string;
963
966
  parent?: string;
964
967
  processId?: number;
965
968
  }[];