lighthouse 9.5.0-dev.20220328 → 9.5.0-dev.20220329

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.
@@ -35,7 +35,12 @@ async function getBaseArtifacts(config, driver, context) {
35
35
  HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
36
36
  'mobile' : 'desktop',
37
37
  // Contextual artifacts whose collection changes based on gather mode.
38
- URL: {requestedUrl: '', finalUrl: ''},
38
+ // TODO: Make `requestedUrl` optional in timespan and snapshot modes.
39
+ URL: {
40
+ initialUrl: '',
41
+ requestedUrl: '',
42
+ finalUrl: '',
43
+ },
39
44
  PageLoadError: null,
40
45
  GatherContext: context,
41
46
  // Artifacts that have been replaced by regular gatherers in Fraggle Rock.
@@ -87,6 +92,8 @@ function finalizeArtifacts(baseArtifacts, gathererArtifacts) {
87
92
  }
88
93
 
89
94
  // Check that the runner remembered to mutate the special-case URL artifact.
95
+ // TODO: Make `requestedUrl` optional.
96
+ if (!artifacts.URL.initialUrl) throw new Error('Runner did not set initialUrl');
90
97
  if (!artifacts.URL.requestedUrl) throw new Error('Runner did not set requestedUrl');
91
98
  if (!artifacts.URL.finalUrl) throw new Error('Runner did not set finalUrl');
92
99
 
@@ -44,17 +44,16 @@ const NetworkRecords = require('../../computed/network-records.js');
44
44
  /** @typedef {Omit<Parameters<typeof collectPhaseArtifacts>[0], 'phase'>} PhaseState */
45
45
 
46
46
  /**
47
- * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor, options?: InternalOptions}} args
47
+ * @param {{driver: Driver, config: LH.Config.FRConfig, options?: InternalOptions}} args
48
48
  * @return {Promise<{baseArtifacts: LH.FRBaseArtifacts}>}
49
49
  */
50
- async function _setup({driver, config, requestor, options}) {
50
+ async function _setup({driver, config, options}) {
51
51
  await driver.connect();
52
52
  if (!options?.skipAboutBlank) {
53
53
  await gotoURL(driver, defaultNavigationConfig.blankPage, {waitUntil: ['navigated']});
54
54
  }
55
55
 
56
56
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'navigation'});
57
- if (typeof requestor === 'string') baseArtifacts.URL.requestedUrl = requestor;
58
57
 
59
58
  await prepare.prepareTargetForNavigationMode(driver, config.settings);
60
59
 
@@ -90,25 +89,32 @@ async function _cleanupNavigation({driver}) {
90
89
 
91
90
  /**
92
91
  * @param {NavigationContext} navigationContext
93
- * @return {Promise<{requestedUrl: string, finalUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
92
+ * @return {Promise<{requestedUrl: string, mainDocumentUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
94
93
  */
95
94
  async function _navigate(navigationContext) {
96
95
  const {driver, config, requestor} = navigationContext;
97
96
 
98
97
  try {
99
- const {requestedUrl, finalUrl, warnings} = await gotoURL(driver, requestor, {
98
+ const {requestedUrl, mainDocumentUrl, warnings} = await gotoURL(driver, requestor, {
100
99
  ...navigationContext.navigation,
101
100
  debugNavigation: config.settings.debugNavigation,
102
101
  maxWaitForFcp: config.settings.maxWaitForFcp,
103
102
  maxWaitForLoad: config.settings.maxWaitForLoad,
104
103
  waitUntil: navigationContext.navigation.pauseAfterFcpMs ? ['fcp', 'load'] : ['load'],
105
104
  });
106
- return {requestedUrl, finalUrl, navigationError: undefined, warnings};
105
+ return {requestedUrl, mainDocumentUrl, navigationError: undefined, warnings};
107
106
  } catch (err) {
108
107
  if (!(err instanceof LighthouseError)) throw err;
109
108
  if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG') throw err;
110
109
  if (typeof requestor !== 'string') throw err;
111
- return {requestedUrl: requestor, finalUrl: requestor, navigationError: err, warnings: []};
110
+
111
+ // TODO: Make the urls optional here so we don't need to throw an error with a callback requestor.
112
+ return {
113
+ requestedUrl: requestor,
114
+ mainDocumentUrl: requestor,
115
+ navigationError: err,
116
+ warnings: [],
117
+ };
112
118
  }
113
119
  }
114
120
 
@@ -152,7 +158,7 @@ async function _collectDebugData(navigationContext, phaseState) {
152
158
  * @param {PhaseState} phaseState
153
159
  * @param {Awaited<ReturnType<typeof _setupNavigation>>} setupResult
154
160
  * @param {Awaited<ReturnType<typeof _navigate>>} navigateResult
155
- * @return {Promise<{requestedUrl: string, finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
161
+ * @return {Promise<{artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
156
162
  */
157
163
  async function _computeNavigationResult(
158
164
  navigationContext,
@@ -160,12 +166,12 @@ async function _computeNavigationResult(
160
166
  setupResult,
161
167
  navigateResult
162
168
  ) {
163
- const {navigationError, finalUrl} = navigateResult;
169
+ const {navigationError, mainDocumentUrl} = navigateResult;
164
170
  const warnings = [...setupResult.warnings, ...navigateResult.warnings];
165
171
  const debugData = await _collectDebugData(navigationContext, phaseState);
166
172
  const pageLoadError = debugData.records
167
173
  ? getPageLoadError(navigationError, {
168
- url: finalUrl,
174
+ url: mainDocumentUrl,
169
175
  loadFailureMode: navigationContext.navigation.loadFailureMode,
170
176
  networkRecords: debugData.records,
171
177
  })
@@ -183,8 +189,6 @@ async function _computeNavigationResult(
183
189
  if (debugData.trace) artifacts.traces = {[pageLoadErrorId]: debugData.trace};
184
190
 
185
191
  return {
186
- requestedUrl: navigateResult.requestedUrl,
187
- finalUrl,
188
192
  pageLoadError,
189
193
  artifacts,
190
194
  warnings: [...warnings, pageLoadError.friendlyMessage],
@@ -194,8 +198,6 @@ async function _computeNavigationResult(
194
198
 
195
199
  const artifacts = await awaitArtifacts(phaseState.artifactState);
196
200
  return {
197
- requestedUrl: navigateResult.requestedUrl,
198
- finalUrl,
199
201
  artifacts,
200
202
  warnings,
201
203
  pageLoadError: undefined,
@@ -209,8 +211,9 @@ async function _computeNavigationResult(
209
211
  */
210
212
  async function _navigation(navigationContext) {
211
213
  const artifactState = getEmptyArtifactState();
214
+ const initialUrl = await navigationContext.driver.url();
212
215
  const phaseState = {
213
- url: await navigationContext.driver.url(),
216
+ url: initialUrl,
214
217
  gatherMode: /** @type {const} */ ('navigation'),
215
218
  driver: navigationContext.driver,
216
219
  computedCache: navigationContext.computedCache,
@@ -224,8 +227,20 @@ async function _navigation(navigationContext) {
224
227
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseState});
225
228
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseState});
226
229
  const navigateResult = await _navigate(navigationContext);
227
- phaseState.baseArtifacts.URL.finalUrl = navigateResult.finalUrl;
228
- phaseState.url = navigateResult.finalUrl;
230
+
231
+ // Every required url is initialized to an empty string in `getBaseArtifacts`.
232
+ // If we haven't set the required urls yet, set them here.
233
+ const {URL} = phaseState.baseArtifacts;
234
+ if (!URL.finalUrl || !URL.initialUrl) {
235
+ phaseState.baseArtifacts.URL = {
236
+ initialUrl,
237
+ requestedUrl: navigateResult.requestedUrl,
238
+ mainDocumentUrl: navigateResult.mainDocumentUrl,
239
+ finalUrl: navigateResult.mainDocumentUrl,
240
+ };
241
+ }
242
+ phaseState.url = navigateResult.mainDocumentUrl;
243
+
229
244
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseState});
230
245
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseState});
231
246
  await _cleanupNavigation(navigationContext);
@@ -263,11 +278,6 @@ async function _navigations({driver, config, requestor, baseArtifacts, computedC
263
278
  artifacts.PageLoadError = navigationResult.pageLoadError;
264
279
  shouldHaltNavigations = true;
265
280
  }
266
-
267
- artifacts.URL = {
268
- requestedUrl: navigationResult.requestedUrl,
269
- finalUrl: navigationResult.finalUrl,
270
- };
271
281
  }
272
282
 
273
283
  LighthouseRunWarnings.push(...navigationResult.warnings);
@@ -36,8 +36,12 @@ async function snapshotGather(options) {
36
36
  const artifacts = await Runner.gather(
37
37
  async () => {
38
38
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'snapshot'});
39
- baseArtifacts.URL.requestedUrl = url;
40
- baseArtifacts.URL.finalUrl = url;
39
+ baseArtifacts.URL = {
40
+ initialUrl: url,
41
+ // TODO: Remove `requestedUrl` from snapshot mode.
42
+ requestedUrl: url,
43
+ finalUrl: url,
44
+ };
41
45
 
42
46
  const artifactDefinitions = config.artifacts || [];
43
47
  const artifactState = getEmptyArtifactState();
@@ -32,12 +32,12 @@ async function startTimespanGather(options) {
32
32
  /** @type {Map<string, LH.ArbitraryEqualityMap>} */
33
33
  const computedCache = new Map();
34
34
  const artifactDefinitions = config.artifacts || [];
35
- const requestedUrl = await driver.url();
35
+ const initialUrl = await driver.url();
36
36
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
37
37
  const artifactState = getEmptyArtifactState();
38
38
  /** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
39
39
  const phaseOptions = {
40
- url: requestedUrl,
40
+ url: initialUrl,
41
41
  driver,
42
42
  artifactDefinitions,
43
43
  artifactState,
@@ -59,8 +59,12 @@ async function startTimespanGather(options) {
59
59
  const runnerOptions = {config, computedCache};
60
60
  const artifacts = await Runner.gather(
61
61
  async () => {
62
- baseArtifacts.URL.requestedUrl = requestedUrl;
63
- baseArtifacts.URL.finalUrl = finalUrl;
62
+ baseArtifacts.URL = {
63
+ initialUrl,
64
+ // TODO: Remove requestedUrl from timespan mode
65
+ requestedUrl: initialUrl,
66
+ finalUrl,
67
+ };
64
68
 
65
69
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
66
70
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
@@ -80,7 +80,7 @@ function resolveWaitForFullyLoadedOptions(options) {
80
80
  * @param {LH.Gatherer.FRTransitionalDriver} driver
81
81
  * @param {LH.NavigationRequestor} requestor
82
82
  * @param {NavigationOptions} options
83
- * @return {Promise<{requestedUrl: string, finalUrl: string, warnings: Array<LH.IcuMessage>}>}
83
+ * @return {Promise<{requestedUrl: string, mainDocumentUrl: string, warnings: Array<LH.IcuMessage>}>}
84
84
  */
85
85
  async function gotoURL(driver, requestor, options) {
86
86
  const status = typeof requestor === 'string' ?
@@ -130,14 +130,14 @@ async function gotoURL(driver, requestor, options) {
130
130
 
131
131
  let requestedUrl = navigationUrls.requestedUrl;
132
132
  if (typeof requestor === 'string') {
133
- if (requestor !== requestedUrl) {
133
+ if (requestedUrl && !URL.equalWithExcludedFragments(requestor, requestedUrl)) {
134
134
  log.error('Navigation', 'Provided URL did not match initial navigation URL');
135
135
  }
136
136
  requestedUrl = requestor;
137
137
  }
138
138
  if (!requestedUrl) throw Error('No navigations detected when running user defined requestor.');
139
139
 
140
- const finalUrl = navigationUrls.finalUrl || requestedUrl;
140
+ const mainDocumentUrl = navigationUrls.mainDocumentUrl || requestedUrl;
141
141
 
142
142
  // Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
143
143
  await waitForNavigationTriggered;
@@ -150,26 +150,26 @@ async function gotoURL(driver, requestor, options) {
150
150
  log.timeEnd(status);
151
151
  return {
152
152
  requestedUrl,
153
- finalUrl,
154
- warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl}),
153
+ mainDocumentUrl,
154
+ warnings: getNavigationWarnings({timedOut, mainDocumentUrl, requestedUrl}),
155
155
  };
156
156
  }
157
157
 
158
158
  /**
159
- * @param {{timedOut: boolean, requestedUrl: string, finalUrl: string; }} navigation
159
+ * @param {{timedOut: boolean, requestedUrl: string, mainDocumentUrl: string; }} navigation
160
160
  * @return {Array<LH.IcuMessage>}
161
161
  */
162
162
  function getNavigationWarnings(navigation) {
163
- const {requestedUrl, finalUrl} = navigation;
163
+ const {requestedUrl, mainDocumentUrl} = navigation;
164
164
  /** @type {Array<LH.IcuMessage>} */
165
165
  const warnings = [];
166
166
 
167
167
  if (navigation.timedOut) warnings.push(str_(UIStrings.warningTimeout));
168
168
 
169
- if (!URL.equalWithExcludedFragments(requestedUrl, finalUrl)) {
169
+ if (!URL.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
170
170
  warnings.push(str_(UIStrings.warningRedirected, {
171
171
  requested: requestedUrl,
172
- final: finalUrl,
172
+ final: mainDocumentUrl,
173
173
  }));
174
174
  }
175
175
 
@@ -133,7 +133,7 @@ class NetworkMonitor {
133
133
  this._sessions = new Map();
134
134
  }
135
135
 
136
- /** @return {Promise<{requestedUrl?: string, finalUrl?: string}>} */
136
+ /** @return {Promise<{requestedUrl?: string, mainDocumentUrl?: string}>} */
137
137
  async getNavigationUrls() {
138
138
  const frameNavigations = this._frameNavigations;
139
139
  if (!frameNavigations.length) return {};
@@ -143,9 +143,22 @@ class NetworkMonitor {
143
143
  const mainFrameNavigations = frameNavigations.filter(frame => frame.id === mainFrameId);
144
144
  if (!mainFrameNavigations.length) log.warn('NetworkMonitor', 'No detected navigations');
145
145
 
146
+ // The requested URL is the initiator request for the first frame navigation.
147
+ /** @type {string|undefined} */
148
+ let requestedUrl = mainFrameNavigations[0]?.url;
149
+ if (this._networkRecorder) {
150
+ const records = this._networkRecorder.getRawRecords();
151
+
152
+ let initialUrlRequest = records.find(record => record.url === requestedUrl);
153
+ while (initialUrlRequest?.redirectSource) {
154
+ initialUrlRequest = initialUrlRequest.redirectSource;
155
+ requestedUrl = initialUrlRequest.url;
156
+ }
157
+ }
158
+
146
159
  return {
147
- requestedUrl: mainFrameNavigations[0]?.url,
148
- finalUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
160
+ requestedUrl,
161
+ mainDocumentUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
149
162
  };
150
163
  }
151
164
 
@@ -470,6 +470,11 @@ class Driver {
470
470
  this._devtoolsLog.endRecording();
471
471
  return this._devtoolsLog.messages;
472
472
  }
473
+
474
+ async url() {
475
+ const {frameTree} = await this.sendCommand('Page.getFrameTree');
476
+ return `${frameTree.frame.url}${frameTree.frame.urlFragment || ''}`;
477
+ }
473
478
  }
474
479
 
475
480
  module.exports = Driver;
@@ -71,7 +71,7 @@ class GatherRunner {
71
71
  log.time(status);
72
72
  try {
73
73
  const requestedUrl = passContext.url;
74
- const {finalUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
74
+ const {mainDocumentUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
75
75
  waitUntil: passContext.passConfig.recordTrace ?
76
76
  ['load', 'fcp'] : ['load'],
77
77
  debugNavigation: passContext.settings.debugNavigation,
@@ -79,8 +79,12 @@ class GatherRunner {
79
79
  maxWaitForLoad: passContext.settings.maxWaitForLoad,
80
80
  ...passContext.passConfig,
81
81
  });
82
- passContext.url = finalUrl;
83
- passContext.baseArtifacts.URL.finalUrl = finalUrl;
82
+ passContext.url = mainDocumentUrl;
83
+ const {URL} = passContext.baseArtifacts;
84
+ if (!URL.finalUrl || !URL.mainDocumentUrl) {
85
+ URL.finalUrl = mainDocumentUrl;
86
+ URL.mainDocumentUrl = mainDocumentUrl;
87
+ }
84
88
  if (passContext.passConfig.loadFailureMode === 'fatal') {
85
89
  passContext.LighthouseRunWarnings.push(...warnings);
86
90
  }
@@ -408,7 +412,12 @@ class GatherRunner {
408
412
  devtoolsLogs: {},
409
413
  settings: options.settings,
410
414
  GatherContext: {gatherMode: 'navigation'},
411
- URL: {requestedUrl: options.requestedUrl, finalUrl: options.requestedUrl},
415
+ URL: {
416
+ initialUrl: await options.driver.url(),
417
+ requestedUrl: options.requestedUrl,
418
+ mainDocumentUrl: '',
419
+ finalUrl: '',
420
+ },
412
421
  Timing: [],
413
422
  PageLoadError: null,
414
423
  };
@@ -426,9 +435,6 @@ class GatherRunner {
426
435
 
427
436
  const baseArtifacts = passContext.baseArtifacts;
428
437
 
429
- // Copy redirected URL to artifact.
430
- baseArtifacts.URL.finalUrl = passContext.url;
431
-
432
438
  // Fetch the manifest, if it exists.
433
439
  try {
434
440
  baseArtifacts.WebAppManifest = await WebAppManifest.getWebAppManifest(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.5.0-dev.20220328",
3
+ "version": "9.5.0-dev.20220329",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -66,7 +66,7 @@ interface UniversalBaseArtifacts {
66
66
  */
67
67
  interface ContextualBaseArtifacts {
68
68
  /** The URL initially requested and the post-redirects URL that was actually loaded. */
69
- URL: {requestedUrl: string, finalUrl: string};
69
+ URL: Artifacts.URL;
70
70
  /** If loading the page failed, value is the error that caused it. Otherwise null. */
71
71
  PageLoadError: LighthouseError | null;
72
72
  }
@@ -187,6 +187,28 @@ declare module Artifacts {
187
187
  type TaskNode = _TaskNode;
188
188
  type MetaElement = Artifacts['MetaElements'][0];
189
189
 
190
+ interface URL {
191
+ /** URL of the main frame before Lighthouse starts. */
192
+ initialUrl: string;
193
+ /**
194
+ * URL of the first document request during a Lighthouse navigation.
195
+ * Will be the same as `initialUrl` in timespan/snapshot.
196
+ * TODO: Make this property `undefined` in timespan/snapshot.
197
+ */
198
+ requestedUrl: string;
199
+ /**
200
+ * URL of the last document request during a Lighthouse navigation.
201
+ * Will be `undefined` in timespan/snapshot.
202
+ */
203
+ mainDocumentUrl?: string;
204
+ /**
205
+ * Will be the same as `mainDocumentUrl` in navigation mode.
206
+ * Wil be the URL of the main frame after Lighthouse finishes in timespan/snapshot.
207
+ * TODO: Use the main frame URL in navigation mode as well.
208
+ */
209
+ finalUrl: string;
210
+ }
211
+
190
212
  interface NodeDetails {
191
213
  lhId: string,
192
214
  devtoolsNodePath: string,
@@ -299,7 +321,7 @@ declare module Artifacts {
299
321
  url: string;
300
322
  content?: string;
301
323
  }
302
-
324
+
303
325
  interface ScriptElement {
304
326
  type: string | null
305
327
  src: string | null
@@ -42,6 +42,7 @@ declare module Gatherer {
42
42
  defaultSession: FRProtocolSession;
43
43
  executionContext: ExecutionContext;
44
44
  fetcher: Fetcher;
45
+ url: () => Promise<string>;
45
46
  }
46
47
 
47
48
  /** The limited context interface shared between pre and post Fraggle Rock Lighthouse. */