lighthouse 9.4.0-dev.20220227 → 9.4.0-dev.20220302
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/lighthouse-cli/test/smokehouse/core-tests.js +2 -0
- package/lighthouse-cli/test/smokehouse/frontends/lib.js +3 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -1
- package/lighthouse-cli/test/smokehouse/readme.md +14 -0
- package/lighthouse-cli/test/smokehouse/report-assert.js +64 -28
- package/lighthouse-cli/test/smokehouse/smokehouse.js +5 -2
- package/lighthouse-core/audits/dobetterweb/inspector-issues.js +6 -6
- package/lighthouse-core/audits/script-treemap-data.js +1 -1
- package/lighthouse-core/config/config-helpers.js +1 -1
- package/lighthouse-core/fraggle-rock/api.js +44 -3
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +1 -1
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +4 -4
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +7 -4
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +5 -5
- package/lighthouse-core/fraggle-rock/user-flow.js +54 -43
- package/lighthouse-core/gather/gatherers/inspector-issues.js +1 -1
- package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
- package/lighthouse-core/runner.js +1 -1
- package/package.json +5 -11
- package/shared/localization/swap-locale.js +1 -2
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/snyk/snapshot.json +3 -3
- package/types/artifacts.d.ts +1 -1
- package/types/gatherer.d.ts +8 -0
- package/types/smokehouse.d.ts +1 -1
|
@@ -33,6 +33,7 @@ import offlineReady from './test-definitions/offline-ready.js';
|
|
|
33
33
|
import offlineSwBroken from './test-definitions/offline-sw-broken.js';
|
|
34
34
|
import offlineSwSlow from './test-definitions/offline-sw-slow.js';
|
|
35
35
|
import oopifRequests from './test-definitions/oopif-requests.js';
|
|
36
|
+
import oopifScripts from './test-definitions/oopif-scripts.js';
|
|
36
37
|
import perfBudgets from './test-definitions/perf-budgets.js';
|
|
37
38
|
import perfDebug from './test-definitions/perf-debug.js';
|
|
38
39
|
import perfDiagnosticsAnimations from './test-definitions/perf-diagnostics-animations.js';
|
|
@@ -92,6 +93,7 @@ const smokeTests = [
|
|
|
92
93
|
offlineSwBroken,
|
|
93
94
|
offlineSwSlow,
|
|
94
95
|
oopifRequests,
|
|
96
|
+
oopifScripts,
|
|
95
97
|
perfBudgets,
|
|
96
98
|
perfDebug,
|
|
97
99
|
perfDiagnosticsAnimations,
|
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
/* eslint-disable no-console */
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import _ from 'lodash';
|
|
17
17
|
|
|
18
18
|
import smokeTests from '../core-tests.js';
|
|
19
19
|
import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
|
|
20
20
|
|
|
21
|
+
const {cloneDeep} = _;
|
|
22
|
+
|
|
21
23
|
/**
|
|
22
24
|
* @param {Smokehouse.SmokehouseLibOptions} options
|
|
23
25
|
*/
|
|
@@ -17,7 +17,7 @@ import path from 'path';
|
|
|
17
17
|
import fs from 'fs';
|
|
18
18
|
import url from 'url';
|
|
19
19
|
|
|
20
|
-
import
|
|
20
|
+
import _ from 'lodash';
|
|
21
21
|
import yargs from 'yargs';
|
|
22
22
|
import * as yargsHelpers from 'yargs/helpers';
|
|
23
23
|
import log from 'lighthouse-logger';
|
|
@@ -26,6 +26,8 @@ import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
|
|
|
26
26
|
import {updateTestDefnFormat} from './back-compat-util.js';
|
|
27
27
|
import {LH_ROOT} from '../../../../root.js';
|
|
28
28
|
|
|
29
|
+
const {cloneDeep} = _;
|
|
30
|
+
|
|
29
31
|
const coreTestDefnsPath =
|
|
30
32
|
path.join(LH_ROOT, 'lighthouse-cli/test/smokehouse/core-tests.js');
|
|
31
33
|
|
|
@@ -186,6 +188,7 @@ async function begin() {
|
|
|
186
188
|
console.log('\n✨ Be sure to have recently run this: yarn build-all');
|
|
187
189
|
}
|
|
188
190
|
const {runLighthouse} = await import(runnerPath);
|
|
191
|
+
runLighthouse.runnerName = argv.runner;
|
|
189
192
|
|
|
190
193
|
// Find test definition file and filter by requestedTestIds.
|
|
191
194
|
let testDefnPath = argv.testsPath || coreTestDefnsPath;
|
|
@@ -66,6 +66,8 @@ However, if an array literal is used as the expectation, an extra condition is e
|
|
|
66
66
|
|
|
67
67
|
Arrays can be checked against a subset of elements using the special `_includes` property. The value of `_includes` _must_ be an array. Each assertion in `_includes` will remove the matching item from consideration for the rest.
|
|
68
68
|
|
|
69
|
+
Arrays can be asserted to not match any elements using the special `_excludes` property. The value of `_excludes` _must_ be an array. If an `_includes` check is defined before an `_excludes` check, only the element not matched under the previous will be considered.
|
|
70
|
+
|
|
69
71
|
**Examples**:
|
|
70
72
|
| Actual | Expected | Result |
|
|
71
73
|
| -- | -- | -- |
|
|
@@ -73,6 +75,9 @@ Arrays can be checked against a subset of elements using the special `_includes`
|
|
|
73
75
|
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{length: 2}` | ✅ PASS |
|
|
74
76
|
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}]}` | ✅ PASS |
|
|
75
77
|
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}, {timeInMs: 5}]}` | ❌ FAIL |
|
|
78
|
+
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{timeInMs: 5}]}` | ✅ PASS |
|
|
79
|
+
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{timeInMs: 15}]}` | ❌ FAIL |
|
|
80
|
+
| `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{}]}` | ❌ FAIL |
|
|
76
81
|
| `[{timeInMs: 5}, {timeInMs: 15}]` | `[{timeInMs: 5}]` | ❌ FAIL |
|
|
77
82
|
|
|
78
83
|
### Special environment checks
|
|
@@ -104,6 +109,15 @@ If an expectation requires a minimum version of Chromium, use `_minChromiumMiles
|
|
|
104
109
|
},
|
|
105
110
|
```
|
|
106
111
|
|
|
112
|
+
All pruning checks:
|
|
113
|
+
|
|
114
|
+
- `_minChromiumMilestone`
|
|
115
|
+
- `_maxChromiumMilestone`
|
|
116
|
+
- `_legacyOnly`
|
|
117
|
+
- `_fraggleRockOnly`
|
|
118
|
+
- `_skipInBundled`
|
|
119
|
+
- `_runner` (set to same value provided to CLI --runner flag, ex: `'devtools'`)
|
|
120
|
+
|
|
107
121
|
## Pipeline
|
|
108
122
|
|
|
109
123
|
The different frontends launch smokehouse with a set of tests to run. Smokehouse then coordinates the tests using a particular method of running Lighthouse (CLI, as a bundle, etc).
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
* against the results actually collected from Lighthouse.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import _ from 'lodash';
|
|
14
14
|
import log from 'lighthouse-logger';
|
|
15
15
|
|
|
16
16
|
import {LocalConsole} from './lib/local-console.js';
|
|
17
17
|
|
|
18
|
+
const {cloneDeep} = _;
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* @typedef Difference
|
|
20
22
|
* @property {string} path
|
|
@@ -103,6 +105,8 @@ function findDifference(path, actual, expected) {
|
|
|
103
105
|
};
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
let inclExclCopy;
|
|
109
|
+
|
|
106
110
|
// We only care that all expected's own properties are on actual (and not the other way around).
|
|
107
111
|
// Note an expected `undefined` can match an actual that is either `undefined` or not defined.
|
|
108
112
|
for (const key of Object.keys(expected)) {
|
|
@@ -112,6 +116,8 @@ function findDifference(path, actual, expected) {
|
|
|
112
116
|
const expectedValue = expected[key];
|
|
113
117
|
|
|
114
118
|
if (key === '_includes') {
|
|
119
|
+
inclExclCopy = [...actual];
|
|
120
|
+
|
|
115
121
|
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
116
122
|
if (!Array.isArray(actual)) {
|
|
117
123
|
return {
|
|
@@ -121,12 +127,12 @@ function findDifference(path, actual, expected) {
|
|
|
121
127
|
};
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
const actualCopy = [...actual];
|
|
125
130
|
for (const expectedEntry of expectedValue) {
|
|
126
131
|
const matchingIndex =
|
|
127
|
-
|
|
132
|
+
inclExclCopy.findIndex(actualEntry =>
|
|
133
|
+
!findDifference(keyPath, actualEntry, expectedEntry));
|
|
128
134
|
if (matchingIndex !== -1) {
|
|
129
|
-
|
|
135
|
+
inclExclCopy.splice(matchingIndex, 1);
|
|
130
136
|
continue;
|
|
131
137
|
}
|
|
132
138
|
|
|
@@ -140,6 +146,33 @@ function findDifference(path, actual, expected) {
|
|
|
140
146
|
continue;
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
if (key === '_excludes') {
|
|
150
|
+
// Re-use state from `_includes` check, if there was one.
|
|
151
|
+
/** @type {any[]} */
|
|
152
|
+
const arrToCheckAgainst = inclExclCopy || actual;
|
|
153
|
+
|
|
154
|
+
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
155
|
+
if (!Array.isArray(actual)) continue;
|
|
156
|
+
|
|
157
|
+
const expectedExclusions = expectedValue;
|
|
158
|
+
for (const expectedExclusion of expectedExclusions) {
|
|
159
|
+
const matchingIndex = arrToCheckAgainst.findIndex(actualEntry =>
|
|
160
|
+
!findDifference(keyPath, actualEntry, expectedExclusion));
|
|
161
|
+
if (matchingIndex !== -1) {
|
|
162
|
+
return {
|
|
163
|
+
path,
|
|
164
|
+
actual: arrToCheckAgainst[matchingIndex],
|
|
165
|
+
expected: {
|
|
166
|
+
message: 'Expected to not find matching entry via _excludes',
|
|
167
|
+
expectedExclusion,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
143
176
|
const actualValue = actual[key];
|
|
144
177
|
const subDifference = findDifference(keyPath, actualValue, expectedValue);
|
|
145
178
|
|
|
@@ -187,7 +220,7 @@ function makeComparison(name, actualResult, expectedResult) {
|
|
|
187
220
|
* @param {LocalConsole} localConsole
|
|
188
221
|
* @param {LH.Result} lhr
|
|
189
222
|
* @param {Smokehouse.ExpectedRunnerResult} expected
|
|
190
|
-
* @param {{isBundled?: boolean}=} reportOptions
|
|
223
|
+
* @param {{runner?: string, isBundled?: boolean}=} reportOptions
|
|
191
224
|
*/
|
|
192
225
|
function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
193
226
|
const isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
|
|
@@ -217,8 +250,20 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
217
250
|
* @param {*} obj
|
|
218
251
|
*/
|
|
219
252
|
function pruneRecursively(obj) {
|
|
220
|
-
|
|
221
|
-
|
|
253
|
+
/**
|
|
254
|
+
* @param {string} key
|
|
255
|
+
*/
|
|
256
|
+
const remove = (key) => {
|
|
257
|
+
if (Array.isArray(obj)) {
|
|
258
|
+
obj.splice(Number(key), 1);
|
|
259
|
+
} else {
|
|
260
|
+
delete obj[key];
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Because we may be deleting keys, we should iterate the keys backwards
|
|
265
|
+
// otherwise arrays with multiple pruning checks will skip elements.
|
|
266
|
+
for (const [key, value] of Object.entries(obj).reverse()) {
|
|
222
267
|
if (!value || typeof value !== 'object') {
|
|
223
268
|
continue;
|
|
224
269
|
}
|
|
@@ -229,42 +274,32 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
229
274
|
JSON.stringify(value, null, 2),
|
|
230
275
|
`Actual Chromium version: ${getChromeVersion()}`,
|
|
231
276
|
].join(' '));
|
|
232
|
-
|
|
233
|
-
obj.splice(Number(key), 1);
|
|
234
|
-
} else {
|
|
235
|
-
delete obj[key];
|
|
236
|
-
}
|
|
277
|
+
remove(key);
|
|
237
278
|
} else if (value._legacyOnly && isFraggleRock) {
|
|
238
279
|
localConsole.log([
|
|
239
280
|
`[${key}] marked legacy only but run is Fraggle Rock, pruning expectation:`,
|
|
240
281
|
JSON.stringify(value, null, 2),
|
|
241
282
|
].join(' '));
|
|
242
|
-
|
|
243
|
-
obj.splice(Number(key), 1);
|
|
244
|
-
} else {
|
|
245
|
-
delete obj[key];
|
|
246
|
-
}
|
|
283
|
+
remove(key);
|
|
247
284
|
} else if (value._fraggleRockOnly && !isFraggleRock) {
|
|
248
285
|
localConsole.log([
|
|
249
286
|
`[${key}] marked Fraggle Rock only but run is legacy, pruning expectation:`,
|
|
250
287
|
JSON.stringify(value, null, 2),
|
|
251
288
|
`Actual channel: ${lhr.configSettings.channel}`,
|
|
252
289
|
].join(' '));
|
|
253
|
-
|
|
254
|
-
obj.splice(Number(key), 1);
|
|
255
|
-
} else {
|
|
256
|
-
delete obj[key];
|
|
257
|
-
}
|
|
290
|
+
remove(key);
|
|
258
291
|
} else if (value._skipInBundled && !isBundled) {
|
|
259
292
|
localConsole.log([
|
|
260
293
|
`[${key}] marked as skip in bundled and runner is bundled, pruning expectation:`,
|
|
261
294
|
JSON.stringify(value, null, 2),
|
|
262
295
|
].join(' '));
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
296
|
+
remove(key);
|
|
297
|
+
} else if (value._runner && reportOptions?.runner !== value._runner) {
|
|
298
|
+
localConsole.log([
|
|
299
|
+
`[${key}] is only for runner ${value._runner}, pruning expectation:`,
|
|
300
|
+
JSON.stringify(value, null, 2),
|
|
301
|
+
].join(' '));
|
|
302
|
+
remove(key);
|
|
268
303
|
} else {
|
|
269
304
|
pruneRecursively(value);
|
|
270
305
|
}
|
|
@@ -275,6 +310,7 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
275
310
|
delete obj._skipInBundled;
|
|
276
311
|
delete obj._minChromiumMilestone;
|
|
277
312
|
delete obj._maxChromiumMilestone;
|
|
313
|
+
delete obj._runner;
|
|
278
314
|
}
|
|
279
315
|
|
|
280
316
|
const cloned = cloneDeep(expected);
|
|
@@ -420,7 +456,7 @@ function reportAssertion(localConsole, assertion) {
|
|
|
420
456
|
* summary. Returns count of passed and failed tests.
|
|
421
457
|
* @param {{lhr: LH.Result, artifacts: LH.Artifacts, networkRequests?: string[]}} actual
|
|
422
458
|
* @param {Smokehouse.ExpectedRunnerResult} expected
|
|
423
|
-
* @param {{isDebug?: boolean, isBundled?: boolean}=} reportOptions
|
|
459
|
+
* @param {{runner?: string, isDebug?: boolean, isBundled?: boolean}=} reportOptions
|
|
424
460
|
* @return {{passed: number, failed: number, log: string}}
|
|
425
461
|
*/
|
|
426
462
|
function getAssertionReport(actual, expected, reportOptions = {}) {
|
|
@@ -57,7 +57,7 @@ async function runSmokehouse(smokeTestDefns, smokehouseOptions) {
|
|
|
57
57
|
useFraggleRock,
|
|
58
58
|
jobs = DEFAULT_CONCURRENT_RUNS,
|
|
59
59
|
retries = DEFAULT_RETRIES,
|
|
60
|
-
lighthouseRunner = cliLighthouseRunner,
|
|
60
|
+
lighthouseRunner = Object.assign(cliLighthouseRunner, {runnerName: 'cli'}),
|
|
61
61
|
takeNetworkRequestUrls,
|
|
62
62
|
} = smokehouseOptions;
|
|
63
63
|
assertPositiveInteger('jobs', jobs);
|
|
@@ -159,7 +159,10 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
// Assert result.
|
|
162
|
-
report = getAssertionReport(result, expectations, {
|
|
162
|
+
report = getAssertionReport(result, expectations, {
|
|
163
|
+
runner: lighthouseRunner.runnerName,
|
|
164
|
+
isDebug,
|
|
165
|
+
});
|
|
163
166
|
|
|
164
167
|
runs.push({
|
|
165
168
|
...result,
|
|
@@ -71,19 +71,19 @@ class IssuesPanelEntries extends Audit {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* @param {Array<LH.Crdp.Audits.
|
|
74
|
+
* @param {Array<LH.Crdp.Audits.CookieIssueDetails>} CookieIssues
|
|
75
75
|
* @return {LH.Audit.Details.TableItem}
|
|
76
76
|
*/
|
|
77
|
-
static
|
|
77
|
+
static getCookieRow(CookieIssues) {
|
|
78
78
|
const requestUrls = new Set();
|
|
79
|
-
for (const issue of
|
|
79
|
+
for (const issue of CookieIssues) {
|
|
80
80
|
const requestUrl = (issue.request?.url) || issue.cookieUrl;
|
|
81
81
|
if (requestUrl) {
|
|
82
82
|
requestUrls.add(requestUrl);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
return {
|
|
86
|
-
issueType: '
|
|
86
|
+
issueType: 'Cookie',
|
|
87
87
|
subItems: {
|
|
88
88
|
type: 'subitems',
|
|
89
89
|
items: Array.from(requestUrls).map(url => {
|
|
@@ -164,8 +164,8 @@ class IssuesPanelEntries extends Audit {
|
|
|
164
164
|
if (issues.mixedContentIssue.length) {
|
|
165
165
|
items.push(this.getMixedContentRow(issues.mixedContentIssue));
|
|
166
166
|
}
|
|
167
|
-
if (issues.
|
|
168
|
-
items.push(this.
|
|
167
|
+
if (issues.cookieIssue.length) {
|
|
168
|
+
items.push(this.getCookieRow(issues.cookieIssue));
|
|
169
169
|
}
|
|
170
170
|
if (issues.blockedByResponseIssue.length) {
|
|
171
171
|
items.push(this.getBlockedByResponseRow(issues.blockedByResponseIssue));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const isDeepEqual = require('lodash
|
|
9
|
+
const {isEqual: isDeepEqual} = require('lodash');
|
|
10
10
|
const constants = require('./constants.js');
|
|
11
11
|
const Budget = require('./budget.js');
|
|
12
12
|
const ConfigPlugin = require('./config-plugin.js');
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const {
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
8
|
+
const {snapshotGather} = require('./gather/snapshot-runner.js');
|
|
9
|
+
const {startTimespanGather} = require('./gather/timespan-runner.js');
|
|
10
|
+
const {navigationGather} = require('./gather/navigation-runner.js');
|
|
11
|
+
const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
|
|
12
|
+
const Runner = require('../runner.js');
|
|
11
13
|
const UserFlow = require('./user-flow.js');
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -18,9 +20,48 @@ async function startFlow(page, options) {
|
|
|
18
20
|
return new UserFlow(page, options);
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {Parameters<navigationGather>} params
|
|
25
|
+
* @return {Promise<LH.RunnerResult|undefined>}
|
|
26
|
+
*/
|
|
27
|
+
async function navigation(...params) {
|
|
28
|
+
const gatherResult = await navigationGather(...params);
|
|
29
|
+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {Parameters<snapshotGather>} params
|
|
34
|
+
* @return {Promise<LH.RunnerResult|undefined>}
|
|
35
|
+
*/
|
|
36
|
+
async function snapshot(...params) {
|
|
37
|
+
const gatherResult = await snapshotGather(...params);
|
|
38
|
+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Parameters<startTimespanGather>} params
|
|
43
|
+
* @return {Promise<{endTimespan: () => Promise<LH.RunnerResult|undefined>}>}
|
|
44
|
+
*/
|
|
45
|
+
async function startTimespan(...params) {
|
|
46
|
+
const {endTimespanGather} = await startTimespanGather(...params);
|
|
47
|
+
const endTimespan = async () => {
|
|
48
|
+
const gatherResult = await endTimespanGather();
|
|
49
|
+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
|
|
50
|
+
};
|
|
51
|
+
return {endTimespan};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {LH.FlowResult} flowResult
|
|
56
|
+
*/
|
|
57
|
+
async function generateFlowReport(flowResult) {
|
|
58
|
+
return generateFlowReportHtml(flowResult);
|
|
59
|
+
}
|
|
60
|
+
|
|
21
61
|
module.exports = {
|
|
22
62
|
snapshot,
|
|
23
63
|
startTimespan,
|
|
24
64
|
navigation,
|
|
25
65
|
startFlow,
|
|
66
|
+
generateFlowReport,
|
|
26
67
|
};
|
|
@@ -290,9 +290,9 @@ async function _cleanup({requestedUrl, driver, config}) {
|
|
|
290
290
|
/**
|
|
291
291
|
* @param {LH.NavigationRequestor} requestor
|
|
292
292
|
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
|
|
293
|
-
* @return {Promise<LH.
|
|
293
|
+
* @return {Promise<LH.Gatherer.FRGatherResult>}
|
|
294
294
|
*/
|
|
295
|
-
async function
|
|
295
|
+
async function navigationGather(requestor, options) {
|
|
296
296
|
const {page, configContext = {}} = options;
|
|
297
297
|
log.setLevel(configContext.logLevel || 'error');
|
|
298
298
|
|
|
@@ -325,11 +325,11 @@ async function navigation(requestor, options) {
|
|
|
325
325
|
},
|
|
326
326
|
runnerOptions
|
|
327
327
|
);
|
|
328
|
-
return
|
|
328
|
+
return {artifacts, runnerOptions};
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
module.exports = {
|
|
332
|
-
|
|
332
|
+
navigationGather,
|
|
333
333
|
_setup,
|
|
334
334
|
_setupNavigation,
|
|
335
335
|
_navigate,
|
|
@@ -16,8 +16,11 @@ const {
|
|
|
16
16
|
const {initializeConfig} = require('../config/config.js');
|
|
17
17
|
const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
|
|
21
|
+
* @return {Promise<LH.Gatherer.FRGatherResult>}
|
|
22
|
+
*/
|
|
23
|
+
async function snapshotGather(options) {
|
|
21
24
|
const {configContext = {}} = options;
|
|
22
25
|
log.setLevel(configContext.logLevel || 'error');
|
|
23
26
|
|
|
@@ -57,9 +60,9 @@ async function snapshot(options) {
|
|
|
57
60
|
},
|
|
58
61
|
runnerOptions
|
|
59
62
|
);
|
|
60
|
-
return
|
|
63
|
+
return {artifacts, runnerOptions};
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
module.exports = {
|
|
64
|
-
|
|
67
|
+
snapshotGather,
|
|
65
68
|
};
|
|
@@ -19,9 +19,9 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
|
|
22
|
-
* @return {Promise<{
|
|
22
|
+
* @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
|
|
23
23
|
*/
|
|
24
|
-
async function
|
|
24
|
+
async function startTimespanGather(options) {
|
|
25
25
|
const {configContext = {}} = options;
|
|
26
26
|
log.setLevel(configContext.logLevel || 'error');
|
|
27
27
|
|
|
@@ -52,7 +52,7 @@ async function startTimespan(options) {
|
|
|
52
52
|
await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
|
-
async
|
|
55
|
+
async endTimespanGather() {
|
|
56
56
|
const finalUrl = await driver.url();
|
|
57
57
|
phaseOptions.url = finalUrl;
|
|
58
58
|
|
|
@@ -72,11 +72,11 @@ async function startTimespan(options) {
|
|
|
72
72
|
},
|
|
73
73
|
runnerOptions
|
|
74
74
|
);
|
|
75
|
-
return
|
|
75
|
+
return {artifacts, runnerOptions};
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
module.exports = {
|
|
81
|
-
|
|
81
|
+
startTimespanGather,
|
|
82
82
|
};
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
9
|
+
const {snapshotGather} = require('./gather/snapshot-runner.js');
|
|
10
|
+
const {startTimespanGather} = require('./gather/timespan-runner.js');
|
|
11
|
+
const {navigationGather} = require('./gather/navigation-runner.js');
|
|
12
|
+
const Runner = require('../runner.js');
|
|
12
13
|
|
|
13
|
-
/** @typedef {Parameters<
|
|
14
|
+
/** @typedef {Parameters<snapshotGather>[0]} FrOptions */
|
|
14
15
|
/** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
|
|
15
16
|
/** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
|
|
17
|
+
/** @typedef {{gatherResult: LH.Gatherer.FRGatherResult, name: string}} StepArtifact */
|
|
16
18
|
|
|
17
19
|
class UserFlow {
|
|
18
20
|
/**
|
|
@@ -24,8 +26,8 @@ class UserFlow {
|
|
|
24
26
|
this.options = {page, ...options};
|
|
25
27
|
/** @type {string|undefined} */
|
|
26
28
|
this.name = options?.name;
|
|
27
|
-
/** @type {
|
|
28
|
-
this.
|
|
29
|
+
/** @type {StepArtifact[]} */
|
|
30
|
+
this.stepArtifacts = [];
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -38,12 +40,12 @@ class UserFlow {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
|
-
* @param {LH.
|
|
43
|
+
* @param {LH.Artifacts} artifacts
|
|
42
44
|
* @return {string}
|
|
43
45
|
*/
|
|
44
|
-
_getDefaultStepName(
|
|
45
|
-
const shortUrl = this._shortenUrl(
|
|
46
|
-
switch (
|
|
46
|
+
_getDefaultStepName(artifacts) {
|
|
47
|
+
const shortUrl = this._shortenUrl(artifacts.URL.finalUrl);
|
|
48
|
+
switch (artifacts.GatherContext.gatherMode) {
|
|
47
49
|
case 'navigation':
|
|
48
50
|
return `Navigation report (${shortUrl})`;
|
|
49
51
|
case 'timespan':
|
|
@@ -66,7 +68,8 @@ class UserFlow {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
// On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
|
|
69
|
-
const isSubsequentNavigation = this.
|
|
71
|
+
const isSubsequentNavigation = this.stepArtifacts
|
|
72
|
+
.some(step => step.gatherResult.artifacts.GatherContext.gatherMode === 'navigation');
|
|
70
73
|
if (isSubsequentNavigation) {
|
|
71
74
|
if (settingsOverrides.disableStorageReset === undefined) {
|
|
72
75
|
settingsOverrides.disableStorageReset = true;
|
|
@@ -84,85 +87,93 @@ class UserFlow {
|
|
|
84
87
|
* @param {StepOptions=} stepOptions
|
|
85
88
|
*/
|
|
86
89
|
async navigate(requestor, stepOptions) {
|
|
87
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
90
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
88
91
|
|
|
89
|
-
const
|
|
92
|
+
const gatherResult = await navigationGather(
|
|
90
93
|
requestor,
|
|
91
94
|
this._getNextNavigationOptions(stepOptions)
|
|
92
95
|
);
|
|
93
|
-
if (!result) throw Error('Navigation returned undefined');
|
|
94
96
|
|
|
95
97
|
const providedName = stepOptions?.stepName;
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
name: providedName || this._getDefaultStepName(
|
|
98
|
+
this.stepArtifacts.push({
|
|
99
|
+
gatherResult,
|
|
100
|
+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
return
|
|
103
|
+
return gatherResult;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
107
|
* @param {StepOptions=} stepOptions
|
|
106
108
|
*/
|
|
107
109
|
async startTimespan(stepOptions) {
|
|
108
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
110
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
109
111
|
|
|
110
112
|
const options = {...this.options, ...stepOptions};
|
|
111
|
-
const timespan = await
|
|
113
|
+
const timespan = await startTimespanGather(options);
|
|
112
114
|
this.currentTimespan = {timespan, options};
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
async endTimespan() {
|
|
116
|
-
if (!this.currentTimespan) throw Error('No timespan in progress');
|
|
118
|
+
if (!this.currentTimespan) throw new Error('No timespan in progress');
|
|
117
119
|
|
|
118
120
|
const {timespan, options} = this.currentTimespan;
|
|
119
|
-
const
|
|
121
|
+
const gatherResult = await timespan.endTimespanGather();
|
|
120
122
|
this.currentTimespan = undefined;
|
|
121
|
-
if (!result) throw Error('Timespan returned undefined');
|
|
122
123
|
|
|
123
124
|
const providedName = options?.stepName;
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
name: providedName || this._getDefaultStepName(
|
|
125
|
+
this.stepArtifacts.push({
|
|
126
|
+
gatherResult,
|
|
127
|
+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
|
|
127
128
|
});
|
|
128
129
|
|
|
129
|
-
return
|
|
130
|
+
return gatherResult;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
134
|
* @param {StepOptions=} stepOptions
|
|
134
135
|
*/
|
|
135
136
|
async snapshot(stepOptions) {
|
|
136
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
137
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
137
138
|
|
|
138
139
|
const options = {...this.options, ...stepOptions};
|
|
139
|
-
const
|
|
140
|
-
if (!result) throw Error('Snapshot returned undefined');
|
|
140
|
+
const gatherResult = await snapshotGather(options);
|
|
141
141
|
|
|
142
142
|
const providedName = stepOptions?.stepName;
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
name: providedName || this._getDefaultStepName(
|
|
143
|
+
this.stepArtifacts.push({
|
|
144
|
+
gatherResult,
|
|
145
|
+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
return
|
|
148
|
+
return gatherResult;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* @
|
|
152
|
+
* @returns {Promise<LH.FlowResult>}
|
|
153
153
|
*/
|
|
154
|
-
|
|
155
|
-
if (!this.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
async createFlowResult() {
|
|
155
|
+
if (!this.stepArtifacts.length) {
|
|
156
|
+
throw new Error('Need at least one step before getting the result');
|
|
157
|
+
}
|
|
158
|
+
const url = new URL(this.stepArtifacts[0].gatherResult.artifacts.URL.finalUrl);
|
|
159
|
+
const flowName = this.name || `User flow (${url.hostname})`;
|
|
160
|
+
|
|
161
|
+
/** @type {LH.FlowResult['steps']} */
|
|
162
|
+
const steps = [];
|
|
163
|
+
for (const {gatherResult, name} of this.stepArtifacts) {
|
|
164
|
+
const result = await Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
|
|
165
|
+
if (!result) throw new Error(`Step "${name}" did not return a result`);
|
|
166
|
+
steps.push({lhr: result.lhr, name});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {steps, name: flowName};
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
/**
|
|
162
|
-
* @return {string}
|
|
173
|
+
* @return {Promise<string>}
|
|
163
174
|
*/
|
|
164
|
-
generateReport() {
|
|
165
|
-
const flowResult = this.
|
|
175
|
+
async generateReport() {
|
|
176
|
+
const flowResult = await this.createFlowResult();
|
|
166
177
|
return generateFlowReportHtml(flowResult);
|
|
167
178
|
}
|
|
168
179
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const isDeepEqual = require('lodash
|
|
8
|
+
const {isEqual: isDeepEqual} = require('lodash');
|
|
9
9
|
const Driver = require('./gather/driver.js');
|
|
10
10
|
const GatherRunner = require('./gather/gather-runner.js');
|
|
11
11
|
const ReportScoring = require('./scoring.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.4.0-dev.
|
|
3
|
+
"version": "9.4.0-dev.20220302",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -115,10 +115,7 @@
|
|
|
115
115
|
"@types/jest": "^27.0.3",
|
|
116
116
|
"@types/jpeg-js": "^0.3.7",
|
|
117
117
|
"@types/jsdom": "^16.2.13",
|
|
118
|
-
"@types/lodash
|
|
119
|
-
"@types/lodash.get": "^4.4.6",
|
|
120
|
-
"@types/lodash.isequal": "^4.5.2",
|
|
121
|
-
"@types/lodash.set": "^4.3.6",
|
|
118
|
+
"@types/lodash": "^4.14.178",
|
|
122
119
|
"@types/node": "*",
|
|
123
120
|
"@types/pako": "^1.0.1",
|
|
124
121
|
"@types/resize-observer-browser": "^0.1.1",
|
|
@@ -140,7 +137,7 @@
|
|
|
140
137
|
"cpy": "^8.1.2",
|
|
141
138
|
"cross-env": "^7.0.2",
|
|
142
139
|
"csv-validator": "^0.0.3",
|
|
143
|
-
"devtools-protocol": "0.0.
|
|
140
|
+
"devtools-protocol": "0.0.975298",
|
|
144
141
|
"es-main": "^1.0.2",
|
|
145
142
|
"eslint": "^8.4.1",
|
|
146
143
|
"eslint-config-google": "^0.14.0",
|
|
@@ -195,10 +192,7 @@
|
|
|
195
192
|
"js-library-detector": "^6.4.0",
|
|
196
193
|
"lighthouse-logger": "^1.3.0",
|
|
197
194
|
"lighthouse-stack-packs": "^1.7.0",
|
|
198
|
-
"lodash
|
|
199
|
-
"lodash.get": "^4.4.2",
|
|
200
|
-
"lodash.isequal": "^4.5.0",
|
|
201
|
-
"lodash.set": "^4.3.2",
|
|
195
|
+
"lodash": "^4.17.21",
|
|
202
196
|
"lookup-closest-locale": "6.2.0",
|
|
203
197
|
"metaviewport-parser": "0.2.0",
|
|
204
198
|
"open": "^8.4.0",
|
|
@@ -213,7 +207,7 @@
|
|
|
213
207
|
"yargs-parser": "^21.0.0"
|
|
214
208
|
},
|
|
215
209
|
"resolutions": {
|
|
216
|
-
"puppeteer/**/devtools-protocol": "0.0.
|
|
210
|
+
"puppeteer/**/devtools-protocol": "0.0.975298"
|
|
217
211
|
},
|
|
218
212
|
"repository": "GoogleChrome/lighthouse",
|
|
219
213
|
"keywords": [
|
|
@@ -38,6 +38,7 @@ Array [
|
|
|
38
38
|
"blockedByResponseIssueDetails",
|
|
39
39
|
"clientHintIssueDetails",
|
|
40
40
|
"contentSecurityPolicyIssueDetails",
|
|
41
|
+
"cookieIssueDetails",
|
|
41
42
|
"corsIssueDetails",
|
|
42
43
|
"deprecationIssueDetails",
|
|
43
44
|
"federatedAuthRequestIssueDetails",
|
|
@@ -47,7 +48,6 @@ Array [
|
|
|
47
48
|
"mixedContentIssueDetails",
|
|
48
49
|
"navigatorUserAgentIssueDetails",
|
|
49
50
|
"quirksModeIssueDetails",
|
|
50
|
-
"sameSiteCookieIssueDetails",
|
|
51
51
|
"sharedArrayBufferIssueDetails",
|
|
52
52
|
"twaQualityEnforcementDetails",
|
|
53
53
|
]
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{"id":"npm:bootstrap:20120510","severity":"medium","semver":{"vulnerable":["<2.1.0"]}}
|
|
46
46
|
],
|
|
47
47
|
"dojo":[
|
|
48
|
-
{"id":"SNYK-JS-DOJO-1535223","severity":"high","semver":{"vulnerable":["
|
|
48
|
+
{"id":"SNYK-JS-DOJO-1535223","severity":"high","semver":{"vulnerable":["<1.17.0"]}},
|
|
49
49
|
{"id":"SNYK-JS-DOJO-559224","severity":"medium","semver":{"vulnerable":["<1.11.10",">=1.12.1 <1.12.8",">=1.13.0 <1.13.7",">=1.14.0 <1.14.6",">=1.15.0 <1.15.3",">=1.16.0 <1.16.2"]}},
|
|
50
50
|
{"id":"SNYK-JS-DOJO-174934","severity":"medium","semver":{"vulnerable":[">=1.0.0 <1.0.3",">=1.1.0 <1.1.2",">=1.2.0 <1.2.4",">=1.3.0 <1.3.3",">=1.4.0 <1.4.2"]}},
|
|
51
51
|
{"id":"SNYK-JS-DOJO-174933","severity":"medium","semver":{"vulnerable":["<1.2.0"]}},
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
{"id":"SNYK-JS-HANDLEBARS-1279029","severity":"medium","semver":{"vulnerable":["<4.7.7"]}},
|
|
71
71
|
{"id":"SNYK-JS-HANDLEBARS-1056767","severity":"high","semver":{"vulnerable":["<4.7.7"]}},
|
|
72
72
|
{"id":"SNYK-JS-HANDLEBARS-567742","severity":"medium","semver":{"vulnerable":["<4.6.0"]}},
|
|
73
|
-
{"id":"SNYK-JS-HANDLEBARS-534988","severity":"
|
|
73
|
+
{"id":"SNYK-JS-HANDLEBARS-534988","severity":"critical","semver":{"vulnerable":[">=4.0.0 <4.5.3","<3.0.8"]}},
|
|
74
74
|
{"id":"SNYK-JS-HANDLEBARS-534478","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.5.3","<3.0.8"]}},
|
|
75
75
|
{"id":"SNYK-JS-HANDLEBARS-480388","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.4.5"]}},
|
|
76
76
|
{"id":"SNYK-JS-HANDLEBARS-469063","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.3.0","<3.8.0"]}},
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
{"id":"SNYK-JS-LODASH-1040724","severity":"high","semver":{"vulnerable":["<4.17.21"]}},
|
|
117
117
|
{"id":"SNYK-JS-LODASH-1018905","severity":"medium","semver":{"vulnerable":["<4.17.21"]}},
|
|
118
118
|
{"id":"SNYK-JS-LODASH-608086","severity":"high","semver":{"vulnerable":["<4.17.17"]}},
|
|
119
|
-
{"id":"SNYK-JS-LODASH-590103","severity":"
|
|
119
|
+
{"id":"SNYK-JS-LODASH-590103","severity":"critical","semver":{"vulnerable":["<4.17.20"]}},
|
|
120
120
|
{"id":"SNYK-JS-LODASH-567746","severity":"medium","semver":{"vulnerable":["<4.17.16"]}},
|
|
121
121
|
{"id":"SNYK-JS-LODASH-450202","severity":"high","semver":{"vulnerable":["<4.17.12"]}},
|
|
122
122
|
{"id":"SNYK-JS-LODASH-73639","severity":"medium","semver":{"vulnerable":["<4.17.11"]}},
|
package/types/artifacts.d.ts
CHANGED
|
@@ -566,7 +566,7 @@ declare module Artifacts {
|
|
|
566
566
|
mixedContentIssue: LH.Crdp.Audits.MixedContentIssueDetails[];
|
|
567
567
|
navigatorUserAgentIssue: LH.Crdp.Audits.NavigatorUserAgentIssueDetails[];
|
|
568
568
|
quirksModeIssue: LH.Crdp.Audits.QuirksModeIssueDetails[];
|
|
569
|
-
|
|
569
|
+
cookieIssue: LH.Crdp.Audits.CookieIssueDetails[];
|
|
570
570
|
sharedArrayBufferIssue: LH.Crdp.Audits.SharedArrayBufferIssueDetails[];
|
|
571
571
|
twaQualityEnforcement: LH.Crdp.Audits.TrustedWebActivityIssueDetails[];
|
|
572
572
|
}
|
package/types/gatherer.d.ts
CHANGED
|
@@ -62,6 +62,14 @@ declare module Gatherer {
|
|
|
62
62
|
settings: Config.Settings;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
interface FRGatherResult {
|
|
66
|
+
artifacts: Artifacts;
|
|
67
|
+
runnerOptions: {
|
|
68
|
+
config: Config.FRConfig;
|
|
69
|
+
computedCache: Map<string, ArbitraryEqualityMap>
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
interface PassContext {
|
|
66
74
|
gatherMode: 'navigation';
|
|
67
75
|
/** The url of the currently loaded page. If the main document redirects, this will be updated to keep track. */
|
package/types/smokehouse.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ declare global {
|
|
|
52
52
|
{expectations: Smokehouse.ExpectedRunnerResult | Array<Smokehouse.ExpectedRunnerResult>}
|
|
53
53
|
|
|
54
54
|
export type LighthouseRunner =
|
|
55
|
-
(url: string, configJson?: Config.Json, runnerOptions?: {isDebug?: boolean; useFraggleRock?: boolean}) => Promise<{lhr: LHResult, artifacts: Artifacts, log: string}
|
|
55
|
+
{runnerName?: string} & ((url: string, configJson?: Config.Json, runnerOptions?: {isDebug?: boolean; useFraggleRock?: boolean}) => Promise<{lhr: LHResult, artifacts: Artifacts, log: string}>);
|
|
56
56
|
|
|
57
57
|
export interface SmokehouseOptions {
|
|
58
58
|
/** If true, performs extra logging from the test runs. */
|