lighthouse 9.5.0-dev.20221002 → 9.5.0-dev.20221004
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.
- package/cli/test/smokehouse/core-tests.js +2 -0
- package/cli/test/smokehouse/readme.md +1 -1
- package/cli/test/smokehouse/report-assert-test.js +4 -4
- package/cli/test/smokehouse/report-assert.js +1 -1
- package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
- package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
- package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
- package/core/audits/font-display.js +1 -1
- package/core/audits/redirects.js +3 -3
- package/core/audits/seo/crawlable-anchors.js +1 -1
- package/core/audits/seo/font-size.js +1 -1
- package/core/audits/seo/link-text.js +2 -2
- package/core/audits/service-worker.js +1 -1
- package/core/audits/third-party-summary.js +1 -1
- package/core/audits/valid-source-maps.js +1 -1
- package/core/computed/page-dependency-graph.js +1 -1
- package/core/computed/resource-summary.js +1 -1
- package/core/gather/base-artifacts.js +4 -6
- package/core/gather/gatherers/full-page-screenshot.js +18 -5
- package/core/gather/gatherers/link-elements.js +4 -4
- package/core/gather/gatherers/seo/robots-txt.js +2 -2
- package/core/gather/gatherers/web-app-manifest.js +2 -2
- package/core/gather/navigation-runner.js +2 -4
- package/core/gather/snapshot-runner.js +1 -2
- package/core/gather/timespan-runner.js +2 -6
- package/core/legacy/gather/gather-runner.js +3 -4
- package/core/runner.js +4 -3
- package/core/user-flow.js +2 -2
- package/core/util.cjs +25 -0
- package/dist/report/bundle.esm.js +48 -13
- package/dist/report/flow.js +7 -7
- package/dist/report/standalone.js +8 -8
- package/flow-report/src/summary/category.tsx +3 -3
- package/flow-report/src/summary/summary.tsx +2 -2
- package/package.json +1 -1
- package/report/generator/file-namer.js +2 -2
- package/report/generator/report-generator.js +1 -1
- package/report/renderer/element-screenshot-renderer.js +6 -1
- package/report/renderer/open-tab.js +3 -1
- package/report/renderer/report-renderer.js +3 -3
- package/report/renderer/report-ui-features.js +9 -6
- package/report/renderer/util.js +25 -0
- package/report/test/generator/file-namer-test.js +1 -1
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/report-renderer-test.js +4 -1
- package/report/test/renderer/report-ui-features-test.js +1 -1
- package/types/artifacts.d.ts +2 -8
- package/types/lhr/lhr.d.ts +9 -2
- package/types/lhr/treemap.d.ts +3 -1
- package/types/smokehouse.d.ts +1 -1
|
@@ -62,6 +62,7 @@ import seoStatus403 from './test-definitions/seo-status-403.js';
|
|
|
62
62
|
import seoTapTargets from './test-definitions/seo-tap-targets.js';
|
|
63
63
|
import sourceMaps from './test-definitions/source-maps.js';
|
|
64
64
|
import timing from './test-definitions/timing.js';
|
|
65
|
+
import fpsScaled from './test-definitions/fps-scaled.js';
|
|
65
66
|
|
|
66
67
|
/** @type {ReadonlyArray<Smokehouse.TestDfn>} */
|
|
67
68
|
const smokeTests = [
|
|
@@ -123,6 +124,7 @@ const smokeTests = [
|
|
|
123
124
|
seoTapTargets,
|
|
124
125
|
sourceMaps,
|
|
125
126
|
timing,
|
|
127
|
+
fpsScaled,
|
|
126
128
|
];
|
|
127
129
|
|
|
128
130
|
export default smokeTests;
|
|
@@ -188,7 +188,7 @@ Smokehouse comes with a core set of test definitions, but it can run any set of
|
|
|
188
188
|
const expectations = [{
|
|
189
189
|
lhr: {
|
|
190
190
|
requestedUrl: 'http://localhost:8080/index.html',
|
|
191
|
-
|
|
191
|
+
finalDisplayedUrl: 'http://localhost:8080/index.html',
|
|
192
192
|
audits: {
|
|
193
193
|
'preload-as': {
|
|
194
194
|
score: 1,
|
|
@@ -235,7 +235,7 @@ describe('getAssertionReport', () => {
|
|
|
235
235
|
lhr: {
|
|
236
236
|
audits: {},
|
|
237
237
|
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
238
|
-
|
|
238
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
239
239
|
},
|
|
240
240
|
});
|
|
241
241
|
expect(report).toMatchObject({passed: 3, failed: 0, log: ''});
|
|
@@ -252,7 +252,7 @@ describe('getAssertionReport', () => {
|
|
|
252
252
|
},
|
|
253
253
|
},
|
|
254
254
|
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
255
|
-
|
|
255
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
256
256
|
},
|
|
257
257
|
});
|
|
258
258
|
expect(report).toMatchObject({passed: 3, failed: 1});
|
|
@@ -270,7 +270,7 @@ describe('getAssertionReport', () => {
|
|
|
270
270
|
},
|
|
271
271
|
},
|
|
272
272
|
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
273
|
-
|
|
273
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
274
274
|
},
|
|
275
275
|
});
|
|
276
276
|
expect(report).toMatchObject({passed: 3, failed: 1});
|
|
@@ -289,7 +289,7 @@ describe('getAssertionReport', () => {
|
|
|
289
289
|
},
|
|
290
290
|
},
|
|
291
291
|
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
292
|
-
|
|
292
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
293
293
|
},
|
|
294
294
|
});
|
|
295
295
|
expect(report).toMatchObject({passed: 3, failed: 1});
|
|
@@ -408,7 +408,7 @@ function collateResults(localConsole, actual, expected) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
return [
|
|
411
|
-
makeComparison('final url', actual.lhr.
|
|
411
|
+
makeComparison('final url', actual.lhr.finalDisplayedUrl, expected.lhr.finalDisplayedUrl),
|
|
412
412
|
runtimeErrorAssertion,
|
|
413
413
|
runWarningsAssertion,
|
|
414
414
|
...requestCountAssertion,
|
|
@@ -401,7 +401,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
401
401
|
* @return {Promise<ByteEfficiencyProduct>}
|
|
402
402
|
*/
|
|
403
403
|
static async audit_(artifacts, networkRecords, context) {
|
|
404
|
-
const mainDocumentEntity = thirdPartyWeb.getEntity(artifacts.URL.
|
|
404
|
+
const mainDocumentEntity = thirdPartyWeb.getEntity(artifacts.URL.finalDisplayedUrl);
|
|
405
405
|
const bundles = await JSBundles.request(artifacts, context);
|
|
406
406
|
|
|
407
407
|
/** @type {Item[]} */
|
|
@@ -92,7 +92,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
92
92
|
* @return {import('./byte-efficiency-audit.js').ByteEfficiencyProduct}
|
|
93
93
|
*/
|
|
94
94
|
static audit_(artifacts) {
|
|
95
|
-
const pageURL = artifacts.URL.
|
|
95
|
+
const pageURL = artifacts.URL.finalDisplayedUrl;
|
|
96
96
|
const images = artifacts.OptimizedImages;
|
|
97
97
|
const imageElements = artifacts.ImageElements;
|
|
98
98
|
/** @type {Map<string, LH.Artifacts.ImageElement>} */
|
|
@@ -69,7 +69,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
69
69
|
* @return {import('./byte-efficiency-audit.js').ByteEfficiencyProduct}
|
|
70
70
|
*/
|
|
71
71
|
static audit_(artifacts) {
|
|
72
|
-
const pageURL = artifacts.URL.
|
|
72
|
+
const pageURL = artifacts.URL.finalDisplayedUrl;
|
|
73
73
|
const images = artifacts.OptimizedImages;
|
|
74
74
|
const imageElements = artifacts.ImageElements;
|
|
75
75
|
/** @type {Map<string, LH.Artifacts.ImageElement>} */
|
|
@@ -100,7 +100,7 @@ class FontDisplay extends Audit {
|
|
|
100
100
|
for (const relativeURL of relativeURLs) {
|
|
101
101
|
try {
|
|
102
102
|
const relativeRoot = UrlUtils.isValid(stylesheet.header.sourceURL) ?
|
|
103
|
-
stylesheet.header.sourceURL : artifacts.URL.
|
|
103
|
+
stylesheet.header.sourceURL : artifacts.URL.finalDisplayedUrl;
|
|
104
104
|
const absoluteURL = new URL(relativeURL, relativeRoot);
|
|
105
105
|
targetURLSet.add(absoluteURL.href);
|
|
106
106
|
} catch (err) {
|
package/core/audits/redirects.js
CHANGED
|
@@ -42,10 +42,10 @@ class Redirects extends Audit {
|
|
|
42
42
|
* Example:
|
|
43
43
|
* GET /initialUrl => 302 /firstRedirect
|
|
44
44
|
* GET /firstRedirect => 200 /firstRedirect, window.location = '/secondRedirect'
|
|
45
|
-
* GET /secondRedirect => 302 /
|
|
46
|
-
* GET /
|
|
45
|
+
* GET /secondRedirect => 302 /thirdRedirect
|
|
46
|
+
* GET /thirdRedirect => 200 /mainDocumentUrl
|
|
47
47
|
*
|
|
48
|
-
* Returns network records [/initialUrl, /firstRedirect, /secondRedirect, /thirdRedirect, /
|
|
48
|
+
* Returns network records [/initialUrl, /firstRedirect, /secondRedirect, /thirdRedirect, /mainDocumentUrl]
|
|
49
49
|
*
|
|
50
50
|
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
51
51
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -280,7 +280,7 @@ class FontSize extends Audit {
|
|
|
280
280
|
const failingRules = getUniqueFailingRules(analyzedFailingNodesData);
|
|
281
281
|
const percentageOfPassingText =
|
|
282
282
|
(totalTextLength - failingTextLength) / totalTextLength * 100;
|
|
283
|
-
const pageUrl = artifacts.URL.
|
|
283
|
+
const pageUrl = artifacts.URL.finalDisplayedUrl;
|
|
284
284
|
|
|
285
285
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
286
286
|
const headings = [
|
|
@@ -120,8 +120,8 @@ class LinkText extends Audit {
|
|
|
120
120
|
href.startsWith('javascript:') ||
|
|
121
121
|
href.startsWith('mailto:') ||
|
|
122
122
|
// This line prevents the audit from flagging anchor links.
|
|
123
|
-
// In this case it is better to use `
|
|
124
|
-
UrlUtils.equalWithExcludedFragments(link.href, artifacts.URL.
|
|
123
|
+
// In this case it is better to use `finalDisplayedUrl` than `mainDocumentUrl`.
|
|
124
|
+
UrlUtils.equalWithExcludedFragments(link.href, artifacts.URL.finalDisplayedUrl)
|
|
125
125
|
) {
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
@@ -197,7 +197,7 @@ class ThirdPartySummary extends Audit {
|
|
|
197
197
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
198
198
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
199
199
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
200
|
-
const mainEntity = thirdPartyWeb.getEntity(artifacts.URL.
|
|
200
|
+
const mainEntity = thirdPartyWeb.getEntity(artifacts.URL.finalDisplayedUrl);
|
|
201
201
|
const tasks = await MainThreadTasks.request(trace, context);
|
|
202
202
|
const multiplier = settings.throttlingMethod === 'simulate' ?
|
|
203
203
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
@@ -77,7 +77,7 @@ class ValidSourceMaps extends Audit {
|
|
|
77
77
|
for (const script of artifacts.Scripts) {
|
|
78
78
|
const sourceMap = SourceMaps.find(m => m.scriptId === script.scriptId);
|
|
79
79
|
const errors = [];
|
|
80
|
-
const isLargeFirstParty = this.isLargeFirstPartyJS(script, artifacts.URL.
|
|
80
|
+
const isLargeFirstParty = this.isLargeFirstPartyJS(script, artifacts.URL.finalDisplayedUrl);
|
|
81
81
|
|
|
82
82
|
if (isLargeFirstParty && (!sourceMap || !sourceMap.map)) {
|
|
83
83
|
missingMapsForLargeFirstPartyFile = true;
|
|
@@ -14,7 +14,7 @@ import {NetworkRecords} from './network-records.js';
|
|
|
14
14
|
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
15
15
|
|
|
16
16
|
/** @typedef {import('../lib/dependency-graph/base-node.js').Node} Node */
|
|
17
|
-
/** @typedef {Omit<LH.Artifacts['URL'], '
|
|
17
|
+
/** @typedef {Omit<LH.Artifacts['URL'], 'finalDisplayedUrl'>} URLArtifact */
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @typedef {Object} NetworkNodeOutput
|
|
@@ -56,7 +56,7 @@ class ResourceSummary {
|
|
|
56
56
|
if (budget?.options?.firstPartyHostnames) {
|
|
57
57
|
firstPartyHosts = budget.options.firstPartyHostnames;
|
|
58
58
|
} else {
|
|
59
|
-
const rootDomain = Util.getRootDomain(URLArtifact.
|
|
59
|
+
const rootDomain = Util.getRootDomain(URLArtifact.finalDisplayedUrl);
|
|
60
60
|
firstPartyHosts = [`*.${rootDomain}`];
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -34,8 +34,7 @@ async function getBaseArtifacts(config, driver, context) {
|
|
|
34
34
|
'mobile' : 'desktop',
|
|
35
35
|
// Contextual artifacts whose collection changes based on gather mode.
|
|
36
36
|
URL: {
|
|
37
|
-
|
|
38
|
-
finalUrl: '',
|
|
37
|
+
finalDisplayedUrl: '',
|
|
39
38
|
},
|
|
40
39
|
PageLoadError: null,
|
|
41
40
|
GatherContext: context,
|
|
@@ -83,13 +82,12 @@ function finalizeArtifacts(baseArtifacts, gathererArtifacts) {
|
|
|
83
82
|
artifacts.Timing = log.getTimeEntries();
|
|
84
83
|
artifacts.LighthouseRunWarnings = deduplicateWarnings(warnings);
|
|
85
84
|
|
|
86
|
-
if (artifacts.PageLoadError && !artifacts.URL.
|
|
87
|
-
artifacts.URL.
|
|
85
|
+
if (artifacts.PageLoadError && !artifacts.URL.finalDisplayedUrl) {
|
|
86
|
+
artifacts.URL.finalDisplayedUrl = artifacts.URL.requestedUrl || '';
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
// Check that the runner remembered to mutate the special-case URL artifact.
|
|
91
|
-
if (!artifacts.URL.
|
|
92
|
-
if (!artifacts.URL.finalUrl) throw new Error('Runner did not set finalUrl');
|
|
90
|
+
if (!artifacts.URL.finalDisplayedUrl) throw new Error('Runner did not set finalDisplayedUrl');
|
|
93
91
|
|
|
94
92
|
return artifacts;
|
|
95
93
|
}
|
|
@@ -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
|
-
/* globals window
|
|
7
|
+
/* globals window getBoundingClientRect requestAnimationFrame */
|
|
8
8
|
|
|
9
9
|
import FRGatherer from '../base-gatherer.js';
|
|
10
10
|
import * as emulation from '../../lib/emulation.js';
|
|
@@ -31,8 +31,8 @@ function getObservedDeviceMetrics() {
|
|
|
31
31
|
// Convert the Web API's kebab case (landscape-primary) to camel case (landscapePrimary).
|
|
32
32
|
const screenOrientationType = kebabCaseToCamelCase(window.screen.orientation.type);
|
|
33
33
|
return {
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
34
|
+
width: window.outerWidth,
|
|
35
|
+
height: window.outerHeight,
|
|
36
36
|
screenOrientation: {
|
|
37
37
|
type: screenOrientationType,
|
|
38
38
|
angle: window.screen.orientation.angle,
|
|
@@ -41,6 +41,13 @@ function getObservedDeviceMetrics() {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function getScreenshotAreaSize() {
|
|
45
|
+
return {
|
|
46
|
+
width: window.innerWidth,
|
|
47
|
+
height: window.innerHeight,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
function waitForDoubleRaf() {
|
|
45
52
|
return new Promise((resolve) => {
|
|
46
53
|
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
@@ -123,10 +130,16 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
123
130
|
});
|
|
124
131
|
const data = 'data:image/webp;base64,' + result.data;
|
|
125
132
|
|
|
133
|
+
const screenshotAreaSize =
|
|
134
|
+
await context.driver.executionContext.evaluate(getScreenshotAreaSize, {
|
|
135
|
+
args: [],
|
|
136
|
+
useIsolation: true,
|
|
137
|
+
deps: [kebabCaseToCamelCase],
|
|
138
|
+
});
|
|
126
139
|
return {
|
|
127
140
|
data,
|
|
128
|
-
width:
|
|
129
|
-
height,
|
|
141
|
+
width: screenshotAreaSize.width,
|
|
142
|
+
height: screenshotAreaSize.height,
|
|
130
143
|
};
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -22,12 +22,12 @@ import {MainResource} from '../../computed/main-resource.js';
|
|
|
22
22
|
/**
|
|
23
23
|
*
|
|
24
24
|
* @param {string} url
|
|
25
|
-
* @param {string}
|
|
25
|
+
* @param {string} finalDisplayedUrl
|
|
26
26
|
* @return {string|null}
|
|
27
27
|
*/
|
|
28
|
-
function normalizeUrlOrNull(url,
|
|
28
|
+
function normalizeUrlOrNull(url, finalDisplayedUrl) {
|
|
29
29
|
try {
|
|
30
|
-
return new URL(url,
|
|
30
|
+
return new URL(url, finalDisplayedUrl).href;
|
|
31
31
|
} catch (_) {
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
@@ -128,7 +128,7 @@ class LinkElements extends FRGatherer {
|
|
|
128
128
|
for (const link of LinkHeader.parse(header.value).refs) {
|
|
129
129
|
linkElements.push({
|
|
130
130
|
rel: link.rel || '',
|
|
131
|
-
href: normalizeUrlOrNull(link.uri, context.baseArtifacts.URL.
|
|
131
|
+
href: normalizeUrlOrNull(link.uri, context.baseArtifacts.URL.finalDisplayedUrl),
|
|
132
132
|
hrefRaw: link.uri || '',
|
|
133
133
|
hreflang: link.hreflang || '',
|
|
134
134
|
as: link.as || '',
|
|
@@ -17,8 +17,8 @@ class RobotsTxt extends FRGatherer {
|
|
|
17
17
|
* @return {Promise<LH.Artifacts['RobotsTxt']>}
|
|
18
18
|
*/
|
|
19
19
|
async getArtifact(passContext) {
|
|
20
|
-
const {
|
|
21
|
-
const robotsUrl = new URL('/robots.txt',
|
|
20
|
+
const {finalDisplayedUrl} = passContext.baseArtifacts.URL;
|
|
21
|
+
const robotsUrl = new URL('/robots.txt', finalDisplayedUrl).href;
|
|
22
22
|
return passContext.driver.fetcher.fetchResource(robotsUrl)
|
|
23
23
|
.catch(err => ({status: null, content: null, errorMessage: err.message}));
|
|
24
24
|
}
|
|
@@ -94,8 +94,8 @@ class WebAppManifest extends FRGatherer {
|
|
|
94
94
|
*/
|
|
95
95
|
getArtifact(context) {
|
|
96
96
|
const driver = context.driver;
|
|
97
|
-
const {
|
|
98
|
-
return WebAppManifest.getWebAppManifest(driver.defaultSession,
|
|
97
|
+
const {finalDisplayedUrl} = context.baseArtifacts.URL;
|
|
98
|
+
return WebAppManifest.getWebAppManifest(driver.defaultSession, finalDisplayedUrl);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -214,9 +214,8 @@ async function _computeNavigationResult(
|
|
|
214
214
|
*/
|
|
215
215
|
async function _navigation(navigationContext) {
|
|
216
216
|
const artifactState = getEmptyArtifactState();
|
|
217
|
-
const initialUrl = await navigationContext.driver.url();
|
|
218
217
|
const phaseState = {
|
|
219
|
-
url:
|
|
218
|
+
url: await navigationContext.driver.url(),
|
|
220
219
|
gatherMode: /** @type {const} */ ('navigation'),
|
|
221
220
|
driver: navigationContext.driver,
|
|
222
221
|
page: navigationContext.page,
|
|
@@ -236,10 +235,9 @@ async function _navigation(navigationContext) {
|
|
|
236
235
|
// If we haven't set all the required urls yet, set them here.
|
|
237
236
|
if (!Object.values(phaseState.baseArtifacts).every(Boolean)) {
|
|
238
237
|
phaseState.baseArtifacts.URL = {
|
|
239
|
-
initialUrl,
|
|
240
238
|
requestedUrl: navigateResult.requestedUrl,
|
|
241
239
|
mainDocumentUrl: navigateResult.mainDocumentUrl,
|
|
242
|
-
|
|
240
|
+
finalDisplayedUrl: await navigationContext.driver.url(),
|
|
243
241
|
};
|
|
244
242
|
}
|
|
245
243
|
phaseState.url = navigateResult.mainDocumentUrl;
|
|
@@ -33,8 +33,7 @@ async function snapshotGather(options) {
|
|
|
33
33
|
async () => {
|
|
34
34
|
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'snapshot'});
|
|
35
35
|
baseArtifacts.URL = {
|
|
36
|
-
|
|
37
|
-
finalUrl: url,
|
|
36
|
+
finalDisplayedUrl: url,
|
|
38
37
|
};
|
|
39
38
|
|
|
40
39
|
const artifactDefinitions = config.artifacts || [];
|
|
@@ -28,7 +28,6 @@ async function startTimespanGather(options) {
|
|
|
28
28
|
/** @type {Map<string, LH.ArbitraryEqualityMap>} */
|
|
29
29
|
const computedCache = new Map();
|
|
30
30
|
const artifactDefinitions = config.artifacts || [];
|
|
31
|
-
const initialUrl = await driver.url();
|
|
32
31
|
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
|
|
33
32
|
const artifactState = getEmptyArtifactState();
|
|
34
33
|
/** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
|
|
@@ -49,15 +48,12 @@ async function startTimespanGather(options) {
|
|
|
49
48
|
|
|
50
49
|
return {
|
|
51
50
|
async endTimespanGather() {
|
|
52
|
-
const
|
|
51
|
+
const finalDisplayedUrl = await driver.url();
|
|
53
52
|
|
|
54
53
|
const runnerOptions = {config, computedCache};
|
|
55
54
|
const artifacts = await Runner.gather(
|
|
56
55
|
async () => {
|
|
57
|
-
baseArtifacts.URL = {
|
|
58
|
-
initialUrl,
|
|
59
|
-
finalUrl,
|
|
60
|
-
};
|
|
56
|
+
baseArtifacts.URL = {finalDisplayedUrl};
|
|
61
57
|
|
|
62
58
|
await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
|
|
63
59
|
await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
|
|
@@ -82,9 +82,9 @@ class GatherRunner {
|
|
|
82
82
|
});
|
|
83
83
|
passContext.url = mainDocumentUrl;
|
|
84
84
|
const {URL} = passContext.baseArtifacts;
|
|
85
|
-
if (!URL.
|
|
86
|
-
URL.finalUrl = mainDocumentUrl;
|
|
85
|
+
if (!URL.finalDisplayedUrl || !URL.mainDocumentUrl) {
|
|
87
86
|
URL.mainDocumentUrl = mainDocumentUrl;
|
|
87
|
+
URL.finalDisplayedUrl = await passContext.driver.url();
|
|
88
88
|
}
|
|
89
89
|
if (passContext.passConfig.loadFailureMode === 'fatal') {
|
|
90
90
|
passContext.LighthouseRunWarnings.push(...warnings);
|
|
@@ -409,10 +409,9 @@ class GatherRunner {
|
|
|
409
409
|
settings: options.settings,
|
|
410
410
|
GatherContext: {gatherMode: 'navigation'},
|
|
411
411
|
URL: {
|
|
412
|
-
initialUrl: await options.driver.url(),
|
|
413
412
|
requestedUrl: options.requestedUrl,
|
|
414
413
|
mainDocumentUrl: '',
|
|
415
|
-
|
|
414
|
+
finalDisplayedUrl: '',
|
|
416
415
|
},
|
|
417
416
|
Timing: [],
|
|
418
417
|
PageLoadError: null,
|
package/core/runner.js
CHANGED
|
@@ -90,7 +90,9 @@ class Runner {
|
|
|
90
90
|
const i18nLhr = {
|
|
91
91
|
lighthouseVersion,
|
|
92
92
|
requestedUrl: artifacts.URL.requestedUrl,
|
|
93
|
-
|
|
93
|
+
mainDocumentUrl: artifacts.URL.mainDocumentUrl,
|
|
94
|
+
finalDisplayedUrl: artifacts.URL.finalDisplayedUrl,
|
|
95
|
+
finalUrl: artifacts.URL.mainDocumentUrl,
|
|
94
96
|
fetchTime: artifacts.fetchTime,
|
|
95
97
|
gatherMode: artifacts.GatherContext.gatherMode,
|
|
96
98
|
runtimeError: Runner.getArtifactRuntimeError(artifacts),
|
|
@@ -121,8 +123,7 @@ class Runner {
|
|
|
121
123
|
// LHR has now been localized.
|
|
122
124
|
const lhr = /** @type {LH.Result} */ (i18nLhr);
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
if (settings.gatherMode && settings.auditMode) {
|
|
126
|
+
if (settings.auditMode) {
|
|
126
127
|
const path = Runner._getDataSavePath(settings);
|
|
127
128
|
assetSaver.saveLhr(lhr, path);
|
|
128
129
|
}
|
package/core/user-flow.js
CHANGED
|
@@ -46,7 +46,7 @@ class UserFlow {
|
|
|
46
46
|
* @return {string}
|
|
47
47
|
*/
|
|
48
48
|
_getDefaultStepName(artifacts) {
|
|
49
|
-
const shortUrl = this._shortenUrl(artifacts.URL.
|
|
49
|
+
const shortUrl = this._shortenUrl(artifacts.URL.finalDisplayedUrl);
|
|
50
50
|
switch (artifacts.GatherContext.gatherMode) {
|
|
51
51
|
case 'navigation':
|
|
52
52
|
return `Navigation report (${shortUrl})`;
|
|
@@ -264,7 +264,7 @@ async function auditGatherSteps(gatherSteps, options) {
|
|
|
264
264
|
steps.push({lhr: result.lhr, name});
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
const url = new URL(gatherSteps[0].artifacts.URL.
|
|
267
|
+
const url = new URL(gatherSteps[0].artifacts.URL.finalDisplayedUrl);
|
|
268
268
|
const flowName = options.name || `User flow (${url.hostname})`;
|
|
269
269
|
return {steps, name: flowName};
|
|
270
270
|
}
|
package/core/util.cjs
CHANGED
|
@@ -54,6 +54,28 @@ class Util {
|
|
|
54
54
|
return `%10d${NBSP}ms`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* If LHR is older than 10.0 it will not have the `finalDisplayedUrl` property.
|
|
59
|
+
* Old LHRs should have the `finalUrl` property which will work fine for the report.
|
|
60
|
+
*
|
|
61
|
+
* @param {LH.Result} lhr
|
|
62
|
+
*/
|
|
63
|
+
static getFinalDisplayedUrl(lhr) {
|
|
64
|
+
if (lhr.finalDisplayedUrl) return lhr.finalDisplayedUrl;
|
|
65
|
+
if (lhr.finalUrl) return lhr.finalUrl;
|
|
66
|
+
throw new Error('Could not determine final displayed URL');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* If LHR is older than 10.0 it will not have the `mainDocumentUrl` property.
|
|
71
|
+
* Old LHRs should have the `finalUrl` property which is the same as `mainDocumentUrl`.
|
|
72
|
+
*
|
|
73
|
+
* @param {LH.Result} lhr
|
|
74
|
+
*/
|
|
75
|
+
static getMainDocumentUrl(lhr) {
|
|
76
|
+
return lhr.mainDocumentUrl || lhr.finalUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
/**
|
|
58
80
|
* Returns a new LHR that's reshaped for slightly better ergonomics within the report rendereer.
|
|
59
81
|
* Also, sets up the localized UI strings used within renderer and makes changes to old LHRs to be
|
|
@@ -76,6 +98,9 @@ class Util {
|
|
|
76
98
|
clone.configSettings.formFactor = clone.configSettings.emulatedFormFactor;
|
|
77
99
|
}
|
|
78
100
|
|
|
101
|
+
clone.finalDisplayedUrl = this.getFinalDisplayedUrl(clone);
|
|
102
|
+
clone.mainDocumentUrl = this.getMainDocumentUrl(clone);
|
|
103
|
+
|
|
79
104
|
for (const audit of Object.values(clone.audits)) {
|
|
80
105
|
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
81
106
|
// into 'notApplicable' (LHR ≥4.0).
|
|
@@ -50,6 +50,28 @@ class Util {
|
|
|
50
50
|
return `%10d${NBSP}ms`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* If LHR is older than 10.0 it will not have the `finalDisplayedUrl` property.
|
|
55
|
+
* Old LHRs should have the `finalUrl` property which will work fine for the report.
|
|
56
|
+
*
|
|
57
|
+
* @param {LH.Result} lhr
|
|
58
|
+
*/
|
|
59
|
+
static getFinalDisplayedUrl(lhr) {
|
|
60
|
+
if (lhr.finalDisplayedUrl) return lhr.finalDisplayedUrl;
|
|
61
|
+
if (lhr.finalUrl) return lhr.finalUrl;
|
|
62
|
+
throw new Error('Could not determine final displayed URL');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* If LHR is older than 10.0 it will not have the `mainDocumentUrl` property.
|
|
67
|
+
* Old LHRs should have the `finalUrl` property which is the same as `mainDocumentUrl`.
|
|
68
|
+
*
|
|
69
|
+
* @param {LH.Result} lhr
|
|
70
|
+
*/
|
|
71
|
+
static getMainDocumentUrl(lhr) {
|
|
72
|
+
return lhr.mainDocumentUrl || lhr.finalUrl;
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
/**
|
|
54
76
|
* Returns a new LHR that's reshaped for slightly better ergonomics within the report rendereer.
|
|
55
77
|
* Also, sets up the localized UI strings used within renderer and makes changes to old LHRs to be
|
|
@@ -72,6 +94,9 @@ class Util {
|
|
|
72
94
|
clone.configSettings.formFactor = clone.configSettings.emulatedFormFactor;
|
|
73
95
|
}
|
|
74
96
|
|
|
97
|
+
clone.finalDisplayedUrl = this.getFinalDisplayedUrl(clone);
|
|
98
|
+
clone.mainDocumentUrl = this.getMainDocumentUrl(clone);
|
|
99
|
+
|
|
75
100
|
for (const audit of Object.values(clone.audits)) {
|
|
76
101
|
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
77
102
|
// into 'notApplicable' (LHR ≥4.0).
|
|
@@ -2779,7 +2804,12 @@ class ElementScreenshotRenderer {
|
|
|
2779
2804
|
width: maxRenderSizeDC.width / zoomFactor,
|
|
2780
2805
|
height: maxRenderSizeDC.height / zoomFactor,
|
|
2781
2806
|
};
|
|
2782
|
-
|
|
2807
|
+
|
|
2808
|
+
if (screenshot.width < elementPreviewSizeSC.width) {
|
|
2809
|
+
elementPreviewSizeSC.width = screenshot.width;
|
|
2810
|
+
elementPreviewSizeSC.height = screenshot.height;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2783
2813
|
/* This preview size is either the size of the thumbnail or size of the Lightbox */
|
|
2784
2814
|
const elementPreviewSizeDC = {
|
|
2785
2815
|
width: elementPreviewSizeSC.width * zoomFactor,
|
|
@@ -4313,9 +4343,9 @@ class ReportRenderer {
|
|
|
4313
4343
|
_renderReportTopbar(report) {
|
|
4314
4344
|
const el = this._dom.createComponent('topbar');
|
|
4315
4345
|
const metadataUrl = this._dom.find('a.lh-topbar__url', el);
|
|
4316
|
-
metadataUrl.textContent = report.
|
|
4317
|
-
metadataUrl.title = report.
|
|
4318
|
-
this._dom.safelySetHref(metadataUrl, report.
|
|
4346
|
+
metadataUrl.textContent = report.finalDisplayedUrl;
|
|
4347
|
+
metadataUrl.title = report.finalDisplayedUrl;
|
|
4348
|
+
this._dom.safelySetHref(metadataUrl, report.finalDisplayedUrl);
|
|
4319
4349
|
return el;
|
|
4320
4350
|
}
|
|
4321
4351
|
|
|
@@ -4713,7 +4743,7 @@ function computeWindowNameSuffix(json) {
|
|
|
4713
4743
|
// @ts-expect-error - If this is a v2 LHR, use old `generatedTime`.
|
|
4714
4744
|
const fallbackFetchTime = /** @type {string} */ (json.generatedTime);
|
|
4715
4745
|
const fetchTime = json.fetchTime || fallbackFetchTime;
|
|
4716
|
-
return `${json.lighthouseVersion}-${json.
|
|
4746
|
+
return `${json.lighthouseVersion}-${json.finalDisplayedUrl}-${fetchTime}`;
|
|
4717
4747
|
}
|
|
4718
4748
|
|
|
4719
4749
|
/**
|
|
@@ -4795,7 +4825,9 @@ function openTreemap(json) {
|
|
|
4795
4825
|
/** @type {LH.Treemap.Options} */
|
|
4796
4826
|
const treemapOptions = {
|
|
4797
4827
|
lhr: {
|
|
4828
|
+
mainDocumentUrl: json.mainDocumentUrl,
|
|
4798
4829
|
finalUrl: json.finalUrl,
|
|
4830
|
+
finalDisplayedUrl: json.finalDisplayedUrl,
|
|
4799
4831
|
audits: {
|
|
4800
4832
|
'script-treemap-data': json.audits['script-treemap-data'],
|
|
4801
4833
|
},
|
|
@@ -5369,11 +5401,11 @@ function getFilenamePrefix(name, fetchTime) {
|
|
|
5369
5401
|
|
|
5370
5402
|
/**
|
|
5371
5403
|
* Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS.
|
|
5372
|
-
* @param {{
|
|
5404
|
+
* @param {{finalDisplayedUrl: string, fetchTime: string}} lhr
|
|
5373
5405
|
* @return {string}
|
|
5374
5406
|
*/
|
|
5375
5407
|
function getLhrFilenamePrefix(lhr) {
|
|
5376
|
-
const hostname = new URL(lhr.
|
|
5408
|
+
const hostname = new URL(lhr.finalDisplayedUrl).hostname;
|
|
5377
5409
|
return getFilenamePrefix(hostname, lhr.fetchTime);
|
|
5378
5410
|
}
|
|
5379
5411
|
|
|
@@ -5607,7 +5639,7 @@ class ReportUIFeatures {
|
|
|
5607
5639
|
|
|
5608
5640
|
tablesWithUrls.forEach((tableEl) => {
|
|
5609
5641
|
const rowEls = getTableRows(tableEl);
|
|
5610
|
-
const thirdPartyRows = this._getThirdPartyRows(rowEls, this.json
|
|
5642
|
+
const thirdPartyRows = this._getThirdPartyRows(rowEls, Util.getFinalDisplayedUrl(this.json));
|
|
5611
5643
|
|
|
5612
5644
|
// create input box
|
|
5613
5645
|
const filterTemplate = this._dom.createComponent('3pFilter');
|
|
@@ -5683,13 +5715,13 @@ class ReportUIFeatures {
|
|
|
5683
5715
|
* From a table with URL entries, finds the rows containing third-party URLs
|
|
5684
5716
|
* and returns them.
|
|
5685
5717
|
* @param {HTMLElement[]} rowEls
|
|
5686
|
-
* @param {string}
|
|
5718
|
+
* @param {string} finalDisplayedUrl
|
|
5687
5719
|
* @return {Array<HTMLElement>}
|
|
5688
5720
|
*/
|
|
5689
|
-
_getThirdPartyRows(rowEls,
|
|
5721
|
+
_getThirdPartyRows(rowEls, finalDisplayedUrl) {
|
|
5690
5722
|
/** @type {Array<HTMLElement>} */
|
|
5691
5723
|
const thirdPartyRows = [];
|
|
5692
|
-
const
|
|
5724
|
+
const finalDisplayedUrlRootDomain = Util.getRootDomain(finalDisplayedUrl);
|
|
5693
5725
|
|
|
5694
5726
|
for (const rowEl of rowEls) {
|
|
5695
5727
|
if (rowEl.classList.contains('lh-sub-item-row')) continue;
|
|
@@ -5699,7 +5731,7 @@ class ReportUIFeatures {
|
|
|
5699
5731
|
|
|
5700
5732
|
const datasetUrl = urlItem.dataset.url;
|
|
5701
5733
|
if (!datasetUrl) continue;
|
|
5702
|
-
const isThirdParty = Util.getRootDomain(datasetUrl) !==
|
|
5734
|
+
const isThirdParty = Util.getRootDomain(datasetUrl) !== finalDisplayedUrlRootDomain;
|
|
5703
5735
|
if (!isThirdParty) continue;
|
|
5704
5736
|
|
|
5705
5737
|
thirdPartyRows.push(rowEl);
|
|
@@ -5713,7 +5745,10 @@ class ReportUIFeatures {
|
|
|
5713
5745
|
*/
|
|
5714
5746
|
_saveFile(blob) {
|
|
5715
5747
|
const ext = blob.type.match('json') ? '.json' : '.html';
|
|
5716
|
-
const filename = getLhrFilenamePrefix(
|
|
5748
|
+
const filename = getLhrFilenamePrefix({
|
|
5749
|
+
finalDisplayedUrl: Util.getFinalDisplayedUrl(this.json),
|
|
5750
|
+
fetchTime: this.json.fetchTime,
|
|
5751
|
+
}) + ext;
|
|
5717
5752
|
if (this._opts.onSaveFileOverride) {
|
|
5718
5753
|
this._opts.onSaveFileOverride(blob, filename);
|
|
5719
5754
|
} else {
|