lighthouse 13.5.0 → 13.6.0-dev.20261027
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/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -1
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/autocomplete-valid.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/presentation-role-conflict.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/svg-img-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/agentic/ard-schema.d.ts +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +8 -28
- package/core/config/config.js +1 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +0 -1
- package/core/lib/i18n/i18n.js +17 -11
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +1 -2
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
package/cli/run.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/* eslint-disable no-console */
|
|
8
8
|
|
|
9
|
+
import fs from 'fs';
|
|
9
10
|
import path from 'path';
|
|
10
11
|
import os from 'os';
|
|
11
12
|
|
|
@@ -131,7 +132,7 @@ function printErrorAndExit(err) {
|
|
|
131
132
|
/**
|
|
132
133
|
* @param {LH.RunnerResult} runnerResult
|
|
133
134
|
* @param {LH.CliFlags} flags
|
|
134
|
-
* @return {Promise<
|
|
135
|
+
* @return {Promise<string|undefined>}
|
|
135
136
|
*/
|
|
136
137
|
async function saveResults(runnerResult, flags) {
|
|
137
138
|
const cwd = process.cwd();
|
|
@@ -145,43 +146,55 @@ async function saveResults(runnerResult, flags) {
|
|
|
145
146
|
if (!shouldSaveResults) return;
|
|
146
147
|
const {lhr, artifacts, report} = runnerResult;
|
|
147
148
|
|
|
149
|
+
const isDirectoryOutput = Boolean(
|
|
150
|
+
flags.outputPath &&
|
|
151
|
+
fs.existsSync(flags.outputPath) &&
|
|
152
|
+
fs.statSync(flags.outputPath).isDirectory()
|
|
153
|
+
);
|
|
154
|
+
|
|
148
155
|
// Use the output path as the prefix for all generated files.
|
|
149
156
|
// If no output path is set, generate a file prefix using the URL and date.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
let configuredPath;
|
|
158
|
+
if (!flags.outputPath || flags.outputPath === 'stdout') {
|
|
159
|
+
configuredPath = getLhrFilenamePrefix(lhr);
|
|
160
|
+
} else if (isDirectoryOutput) {
|
|
161
|
+
configuredPath = path.join(flags.outputPath, getLhrFilenamePrefix(lhr));
|
|
162
|
+
} else {
|
|
163
|
+
configuredPath = flags.outputPath.replace(/\.\w{2,4}$/, '');
|
|
164
|
+
}
|
|
153
165
|
const resolvedPath = path.resolve(cwd, configuredPath);
|
|
154
166
|
|
|
155
167
|
if (flags.saveAssets) {
|
|
156
168
|
await assetSaver.saveAssets(artifacts, lhr.audits, resolvedPath);
|
|
157
169
|
}
|
|
158
170
|
|
|
171
|
+
/** @type {string|undefined} */
|
|
172
|
+
let htmlOutputPath;
|
|
159
173
|
for (const outputType of flags.output) {
|
|
160
174
|
const extension = outputType;
|
|
161
175
|
const output = report[flags.output.indexOf(outputType)];
|
|
162
176
|
let outputPath = `${resolvedPath}.report.${extension}`;
|
|
163
|
-
// If there was only a single output and the user specified
|
|
164
|
-
if (flags.outputPath && flags.output.length === 1)
|
|
177
|
+
// If there was only a single output and the user specified a file outputPath, force usage of it.
|
|
178
|
+
if (flags.outputPath && !isDirectoryOutput && flags.output.length === 1) {
|
|
179
|
+
outputPath = flags.outputPath;
|
|
180
|
+
}
|
|
165
181
|
await Printer.write(output, outputType, outputPath);
|
|
166
182
|
|
|
167
183
|
if (outputType === Printer.OutputMode[Printer.OutputMode.html]) {
|
|
168
|
-
|
|
169
|
-
open(outputPath, {wait: false});
|
|
170
|
-
} else {
|
|
171
|
-
// eslint-disable-next-line max-len
|
|
172
|
-
log.log('CLI', 'Protip: Run lighthouse with `--view` to immediately open the HTML report in your browser');
|
|
173
|
-
}
|
|
184
|
+
htmlOutputPath = outputPath;
|
|
174
185
|
}
|
|
175
186
|
}
|
|
187
|
+
|
|
188
|
+
return htmlOutputPath;
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
/**
|
|
179
|
-
* @param {string}
|
|
192
|
+
* @param {string|string[]|undefined} urlOrUrls
|
|
180
193
|
* @param {LH.CliFlags} flags
|
|
181
194
|
* @param {LH.Config|undefined} config
|
|
182
|
-
* @return {Promise<LH.RunnerResult|undefined>}
|
|
195
|
+
* @return {Promise<LH.RunnerResult|LH.RunnerResult[]|undefined>}
|
|
183
196
|
*/
|
|
184
|
-
async function runLighthouse(
|
|
197
|
+
async function runLighthouse(urlOrUrls, flags, config) {
|
|
185
198
|
/** @param {any} reason */
|
|
186
199
|
function handleTheUnhandled(reason) {
|
|
187
200
|
process.stderr.write(`Unhandled Rejection. Reason: ${reason}\n`);
|
|
@@ -193,48 +206,85 @@ async function runLighthouse(url, flags, config) {
|
|
|
193
206
|
/** @type {ChromeLauncher.LaunchedChrome|undefined} */
|
|
194
207
|
let launchedChrome;
|
|
195
208
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
209
|
+
const urls = [urlOrUrls].flat();
|
|
210
|
+
if (urls.length === 0) urls.push(undefined);
|
|
211
|
+
const isBatch = urls.length > 1;
|
|
200
212
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
flags.port = launchedChrome.port;
|
|
206
|
-
}
|
|
213
|
+
const shouldGather = flags.gatherMode || flags.gatherMode === flags.auditMode;
|
|
214
|
+
const shouldUseLocalChrome = UrlUtils.isLikeLocalhost(flags.hostname);
|
|
215
|
+
const initialPort = flags.port;
|
|
216
|
+
flags.channel = 'cli';
|
|
207
217
|
|
|
208
|
-
|
|
218
|
+
/** @type {Array<LH.RunnerResult>} */
|
|
219
|
+
const results = [];
|
|
220
|
+
/** @type {Array<string>} */
|
|
221
|
+
const htmlReportPaths = [];
|
|
222
|
+
/** @type {Array<ExitError>} */
|
|
223
|
+
const errors = [];
|
|
209
224
|
|
|
210
|
-
|
|
225
|
+
try {
|
|
226
|
+
for (const url of urls) {
|
|
227
|
+
if (url && flags.auditMode && !flags.gatherMode) {
|
|
228
|
+
log.warn('CLI', 'URL parameter is ignored if -A flag is used without -G flag');
|
|
229
|
+
}
|
|
211
230
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
try {
|
|
232
|
+
if (shouldGather && shouldUseLocalChrome && !launchedChrome) {
|
|
233
|
+
launchedChrome = await getDebuggableChrome({...flags, port: initialPort});
|
|
234
|
+
flags.port = launchedChrome.port;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const runnerResult = await lighthouse(url, flags, config);
|
|
238
|
+
|
|
239
|
+
// If in gatherMode only, there will be no runnerResult.
|
|
240
|
+
if (runnerResult) {
|
|
241
|
+
const htmlPath = await saveResults(runnerResult, flags);
|
|
242
|
+
if (htmlPath) htmlReportPaths.push(htmlPath);
|
|
243
|
+
results.push(runnerResult);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Runtime errors indicate something was *very* wrong with the page result.
|
|
247
|
+
// We don't want the user to have to parse the report to figure it out, so we'll still exit
|
|
248
|
+
// with an error code after we saved the results.
|
|
249
|
+
if (runnerResult?.lhr.runtimeError) {
|
|
250
|
+
const {runtimeError} = runnerResult.lhr;
|
|
251
|
+
throw Object.assign(new Error(runtimeError.message), {
|
|
252
|
+
name: 'LighthouseError',
|
|
253
|
+
friendlyMessage: runtimeError.message,
|
|
254
|
+
code: runtimeError.code,
|
|
255
|
+
stack: undefined,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
errors.push(err);
|
|
260
|
+
await launchedChrome?.kill();
|
|
261
|
+
launchedChrome = undefined;
|
|
262
|
+
if (!isBatch || flags.failFast) break;
|
|
263
|
+
const errMsg = err.friendlyMessage || err.message;
|
|
264
|
+
log.error('CLI', `Runtime error encountered for ${url}: ${errMsg}`);
|
|
265
|
+
}
|
|
215
266
|
}
|
|
216
|
-
|
|
217
|
-
launchedChrome?.kill();
|
|
267
|
+
} finally {
|
|
268
|
+
await launchedChrome?.kill();
|
|
218
269
|
process.removeListener('unhandledRejection', handleTheUnhandled);
|
|
270
|
+
}
|
|
219
271
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
code: runtimeError.code,
|
|
229
|
-
message: runtimeError.message,
|
|
230
|
-
});
|
|
272
|
+
if (htmlReportPaths.length > 0) {
|
|
273
|
+
if (flags.view) {
|
|
274
|
+
for (const htmlPath of htmlReportPaths) {
|
|
275
|
+
open(htmlPath, {wait: false});
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// eslint-disable-next-line max-len
|
|
279
|
+
log.log('CLI', 'Protip: Run lighthouse with `--view` to immediately open the HTML report in your browser');
|
|
231
280
|
}
|
|
281
|
+
}
|
|
232
282
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
launchedChrome?.kill();
|
|
236
|
-
return printErrorAndExit(err);
|
|
283
|
+
if (errors.length > 0) {
|
|
284
|
+
return printErrorAndExit(errors[0]);
|
|
237
285
|
}
|
|
286
|
+
|
|
287
|
+
return isBatch ? results : results[0];
|
|
238
288
|
}
|
|
239
289
|
|
|
240
290
|
export {
|
package/cli/sentry-prompt.js
CHANGED
|
@@ -60,16 +60,15 @@ function prompt() {
|
|
|
60
60
|
function askPermission() {
|
|
61
61
|
return Promise.resolve().then(_ => {
|
|
62
62
|
const configstore = new Configstore('lighthouse');
|
|
63
|
-
|
|
63
|
+
const isErrorReportingEnabled = configstore.get('isErrorReportingEnabled');
|
|
64
64
|
if (typeof isErrorReportingEnabled === 'boolean') {
|
|
65
65
|
return Promise.resolve(isErrorReportingEnabled);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return prompt()
|
|
69
69
|
.then(response => {
|
|
70
|
-
isErrorReportingEnabled
|
|
71
|
-
|
|
72
|
-
return isErrorReportingEnabled;
|
|
70
|
+
configstore.set('isErrorReportingEnabled', response);
|
|
71
|
+
return response;
|
|
73
72
|
});
|
|
74
73
|
// Error accessing configstore; default to false.
|
|
75
74
|
}).catch(_ => false);
|
|
@@ -23,7 +23,7 @@ const exclusions = {
|
|
|
23
23
|
'redirects-http',
|
|
24
24
|
// Disabled because these tests use settings that cannot be fully configured in
|
|
25
25
|
// DevTools (e.g. throttling method "provided").
|
|
26
|
-
'metrics-tricky-tti', 'metrics-tricky-tti-late-fcp', 'screenshot',
|
|
26
|
+
'metrics-delayed-lcp', 'metrics-tricky-tti', 'metrics-tricky-tti-late-fcp', 'screenshot',
|
|
27
27
|
// Disabled because of differences that need further investigation
|
|
28
28
|
'byte-efficiency', 'byte-gzip', 'perf-preload',
|
|
29
29
|
// Disabled because a renderer crash also breaks devtools frontend
|
|
@@ -78,6 +78,8 @@ const exclusions = {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
for (const array of Object.values(exclusions)) {
|
|
81
|
+
// https://github.com/GoogleChrome/lighthouse/issues/17277
|
|
82
|
+
array.push('webmcp');
|
|
81
83
|
// https://github.com/GoogleChrome/lighthouse/issues/14271
|
|
82
84
|
array.push('lantern-idle-callback-short');
|
|
83
85
|
// glitch is gone.
|
|
@@ -216,7 +216,9 @@ async function begin() {
|
|
|
216
216
|
if (testDefnPath === coreTestDefnsPath) {
|
|
217
217
|
const {createServers} = await import('../../fixtures/static-server.js');
|
|
218
218
|
servers = await createServers();
|
|
219
|
-
|
|
219
|
+
if (servers.length) {
|
|
220
|
+
takeNetworkRequestUrls = servers[0].takeRequestUrls.bind(servers[0]);
|
|
221
|
+
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
const prunedTestDefns = pruneExpectedNetworkRequests(testDefns, takeNetworkRequestUrls);
|
|
@@ -234,7 +236,7 @@ async function begin() {
|
|
|
234
236
|
|
|
235
237
|
smokehouseResult = (await runSmokehouse(prunedTestDefns, options));
|
|
236
238
|
} finally {
|
|
237
|
-
servers
|
|
239
|
+
if (servers) await Promise.all(servers.map(s => s.close()));
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
let smokehouseOutputDir;
|
|
@@ -244,7 +246,7 @@ async function begin() {
|
|
|
244
246
|
smokehouseOutputDir = `${LH_ROOT}/.tmp/smokehouse-failures`;
|
|
245
247
|
testResultsToOutput = smokehouseResult.testResults.filter(r => r.failed);
|
|
246
248
|
} else if (!process.env.CI) {
|
|
247
|
-
// Otherwise,
|
|
249
|
+
// Otherwise, write all results to disk when running locally.
|
|
248
250
|
smokehouseOutputDir = `${LH_ROOT}/.tmp/smokehouse-output`;
|
|
249
251
|
testResultsToOutput = smokehouseResult.testResults;
|
|
250
252
|
}
|
|
@@ -73,6 +73,31 @@ describe('findDiffersences', () => {
|
|
|
73
73
|
expected: {duration: '<100'},
|
|
74
74
|
diffs: [{path: '.duration', actual: 100, expected: '<100'}],
|
|
75
75
|
},
|
|
76
|
+
'range (negative 1)': {
|
|
77
|
+
actual: {duration: -3},
|
|
78
|
+
expected: {duration: '>-5'},
|
|
79
|
+
diffs: null,
|
|
80
|
+
},
|
|
81
|
+
'range (negative 2)': {
|
|
82
|
+
actual: {duration: -9},
|
|
83
|
+
expected: {duration: '-10 +/- 2'},
|
|
84
|
+
diffs: null,
|
|
85
|
+
},
|
|
86
|
+
'range (invalid numerical expectation without prefix)': {
|
|
87
|
+
actual: {duration: 3},
|
|
88
|
+
expected: {duration: '+/- 5'},
|
|
89
|
+
diffs: [{path: '.duration', actual: 3, expected: '+/- 5'}],
|
|
90
|
+
},
|
|
91
|
+
'range (invalid numerical expectation with prefix)': {
|
|
92
|
+
actual: {duration: 15},
|
|
93
|
+
expected: {duration: '10 < 20'},
|
|
94
|
+
diffs: [{path: '.duration', actual: 15, expected: '10 < 20'}],
|
|
95
|
+
},
|
|
96
|
+
'range (numeric actual vs array expected)': {
|
|
97
|
+
actual: {duration: 10},
|
|
98
|
+
expected: {duration: ['>0']},
|
|
99
|
+
diffs: [{path: '.duration', actual: 10, expected: ['>0']}],
|
|
100
|
+
},
|
|
76
101
|
|
|
77
102
|
'array (1)': {
|
|
78
103
|
actual: {prices: [0, 1, 2, 3, 4, 5]},
|
|
@@ -112,6 +137,11 @@ describe('findDiffersences', () => {
|
|
|
112
137
|
expected: {prices: {'3': {nested: '>3'}}},
|
|
113
138
|
diffs: [{path: '.prices[3].nested', actual: 3, expected: '>3'}],
|
|
114
139
|
},
|
|
140
|
+
'array (8)': {
|
|
141
|
+
actual: {prices: {0: 1, length: 1}},
|
|
142
|
+
expected: {prices: [1]},
|
|
143
|
+
diffs: [{path: '.prices.length', actual: {0: 1, length: 1}, expected: [1]}],
|
|
144
|
+
},
|
|
115
145
|
|
|
116
146
|
'_includes (1)': {
|
|
117
147
|
actual: {prices: [0, 1, 2, 3, 4, 5]},
|
|
@@ -295,4 +325,39 @@ describe('getAssertionReport', () => {
|
|
|
295
325
|
expect(report).toMatchObject({passed: 3, failed: 1});
|
|
296
326
|
expect(clean(report.log)).toMatchSnapshot();
|
|
297
327
|
});
|
|
328
|
+
|
|
329
|
+
it('asserts expected.lhr.userAgent', () => {
|
|
330
|
+
const passingReport = getAssertionReport({lhr, artifacts}, {
|
|
331
|
+
lhr: {
|
|
332
|
+
audits: {},
|
|
333
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
334
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
335
|
+
userAgent: /Chrome\//,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
expect(passingReport).toMatchObject({passed: 4, failed: 0, log: ''});
|
|
339
|
+
|
|
340
|
+
const failingReport = getAssertionReport({lhr, artifacts}, {
|
|
341
|
+
lhr: {
|
|
342
|
+
audits: {},
|
|
343
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
344
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
345
|
+
userAgent: 'NotTheUserAgent',
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
expect(failingReport).toMatchObject({passed: 3, failed: 1});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('lazily evaluates hostUserAgent only when chromium version check is used', () => {
|
|
352
|
+
const customLhr = structuredClone(lhr);
|
|
353
|
+
customLhr.environment.hostUserAgent = 'InvalidUserAgentWithoutChromeVersion';
|
|
354
|
+
const report = getAssertionReport({lhr: customLhr, artifacts}, {
|
|
355
|
+
lhr: {
|
|
356
|
+
audits: {},
|
|
357
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
358
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
expect(report).toMatchObject({passed: 3, failed: 0, log: ''});
|
|
362
|
+
});
|
|
298
363
|
});
|
|
@@ -30,7 +30,7 @@ import {chromiumVersionCheck} from './version-check.js';
|
|
|
30
30
|
* @property {Difference[]|null} diffs
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
const NUMBER_REGEXP =
|
|
33
|
+
const NUMBER_REGEXP = /-?(?:\d|\.)+/.source;
|
|
34
34
|
const OPS_REGEXP = /<=?|>=?|\+\/-|±/.source;
|
|
35
35
|
// An optional number, optional whitespace, an operator, optional whitespace, a number.
|
|
36
36
|
const NUMERICAL_EXPECTATION_REGEXP =
|
|
@@ -48,21 +48,27 @@ const NUMERICAL_EXPECTATION_REGEXP =
|
|
|
48
48
|
* @return {boolean}
|
|
49
49
|
*/
|
|
50
50
|
function matchesExpectation(actual, expected) {
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
typeof actual === 'number' &&
|
|
53
|
+
typeof expected === 'string' &&
|
|
54
|
+
NUMERICAL_EXPECTATION_REGEXP.test(expected)
|
|
55
|
+
) {
|
|
52
56
|
const parts = expected.match(NUMERICAL_EXPECTATION_REGEXP);
|
|
57
|
+
if (!parts) return false;
|
|
53
58
|
const [, prefixNumber, operator, postfixNumber] = parts;
|
|
54
59
|
switch (operator) {
|
|
55
60
|
case '>':
|
|
56
|
-
return actual > postfixNumber;
|
|
61
|
+
return prefixNumber === undefined && actual > Number(postfixNumber);
|
|
57
62
|
case '>=':
|
|
58
|
-
return actual >= postfixNumber;
|
|
63
|
+
return prefixNumber === undefined && actual >= Number(postfixNumber);
|
|
59
64
|
case '<':
|
|
60
|
-
return actual < postfixNumber;
|
|
65
|
+
return prefixNumber === undefined && actual < Number(postfixNumber);
|
|
61
66
|
case '<=':
|
|
62
|
-
return actual <= postfixNumber;
|
|
67
|
+
return prefixNumber === undefined && actual <= Number(postfixNumber);
|
|
63
68
|
case '+/-':
|
|
64
69
|
case '±':
|
|
65
|
-
return
|
|
70
|
+
return prefixNumber !== undefined &&
|
|
71
|
+
Math.abs(actual - Number(prefixNumber)) <= Number(postfixNumber);
|
|
66
72
|
default:
|
|
67
73
|
throw new Error(`unexpected operator ${operator}`);
|
|
68
74
|
}
|
|
@@ -117,21 +123,8 @@ function findDifferences(path, actual, expected) {
|
|
|
117
123
|
const expectedValue = expected[key];
|
|
118
124
|
|
|
119
125
|
if (key === '_includes') {
|
|
120
|
-
if (Array.isArray(actual)) {
|
|
121
|
-
inclExclCopy = [...actual];
|
|
122
|
-
} else if (typeof actual === 'object') {
|
|
123
|
-
inclExclCopy = Object.entries(actual);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
126
|
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
127
|
-
|
|
128
|
-
diffs.push({
|
|
129
|
-
path,
|
|
130
|
-
actual: 'Actual value is not an array or object',
|
|
131
|
-
expected,
|
|
132
|
-
});
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
127
|
+
inclExclCopy = Array.isArray(actual) ? [...actual] : Object.entries(actual);
|
|
135
128
|
|
|
136
129
|
for (const expectedEntry of expectedValue) {
|
|
137
130
|
const matchingIndex =
|
|
@@ -153,24 +146,11 @@ function findDifferences(path, actual, expected) {
|
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
if (key === '_excludes') {
|
|
156
|
-
// Re-use state from `_includes` check, if there was one.
|
|
157
|
-
if (!inclExclCopy) {
|
|
158
|
-
if (Array.isArray(actual)) {
|
|
159
|
-
// We won't be removing items, so we can just copy the reference.
|
|
160
|
-
inclExclCopy = actual;
|
|
161
|
-
} else if (typeof actual === 'object') {
|
|
162
|
-
inclExclCopy = Object.entries(actual);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
149
|
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
150
|
+
// Re-use state from `_includes` check, if there was one.
|
|
167
151
|
if (!inclExclCopy) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
actual: 'Actual value is not an array or object',
|
|
171
|
-
expected,
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
152
|
+
// We won't be removing items, so we can just copy the reference.
|
|
153
|
+
inclExclCopy = Array.isArray(actual) ? actual : Object.entries(actual);
|
|
174
154
|
}
|
|
175
155
|
|
|
176
156
|
const expectedExclusions = expectedValue;
|
|
@@ -201,7 +181,7 @@ function findDifferences(path, actual, expected) {
|
|
|
201
181
|
// This still allows for asserting that the first n elements of an array are specified elements,
|
|
202
182
|
// but requires using an object literal (ex: {0: x, 1: y, 2: z} matches [x, y, z, q, w, e] and
|
|
203
183
|
// {0: x, 1: y, 2: z, length: 5} does not match [x, y, z].
|
|
204
|
-
if (Array.isArray(expected) && actual.length !== expected.length) {
|
|
184
|
+
if (Array.isArray(expected) && (!Array.isArray(actual) || actual.length !== expected.length)) {
|
|
205
185
|
diffs.push({
|
|
206
186
|
path: `${path}.length`,
|
|
207
187
|
actual,
|
|
@@ -256,6 +236,7 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
256
236
|
* @param {*} obj
|
|
257
237
|
*/
|
|
258
238
|
function failsChromeVersionCheck(obj) {
|
|
239
|
+
if (!obj._minChromiumVersion && !obj._maxChromiumVersion) return false;
|
|
259
240
|
return !chromiumVersionCheck({
|
|
260
241
|
version: getChromeVersionString(),
|
|
261
242
|
min: obj._minChromiumVersion,
|
|
@@ -376,6 +357,10 @@ function collateResults(localConsole, actual, expected) {
|
|
|
376
357
|
/** @type {Comparison[]} */
|
|
377
358
|
const extraAssertions = [];
|
|
378
359
|
|
|
360
|
+
if (expected.lhr.userAgent) {
|
|
361
|
+
extraAssertions.push(makeComparison('userAgent', actual.lhr.userAgent, expected.lhr.userAgent));
|
|
362
|
+
}
|
|
363
|
+
|
|
379
364
|
if (expected.lhr.timing) {
|
|
380
365
|
const comparison = makeComparison('timing', actual.lhr.timing, expected.lhr.timing);
|
|
381
366
|
extraAssertions.push(comparison);
|
|
@@ -425,7 +410,7 @@ function reportAssertion(localConsole, assertion) {
|
|
|
425
410
|
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
|
426
411
|
|
|
427
412
|
if (assertion.equal) {
|
|
428
|
-
if (isPlainObject(assertion.actual)) {
|
|
413
|
+
if (isPlainObject(assertion.actual) || Array.isArray(assertion.actual)) {
|
|
429
414
|
localConsole.log(` ${log.greenify(log.tick)} ${assertion.name}`);
|
|
430
415
|
} else {
|
|
431
416
|
localConsole.log(` ${log.greenify(log.tick)} ${assertion.name}: ` +
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as td from 'testdouble';
|
|
8
|
+
|
|
9
|
+
import {runSmokehouse} from './smokehouse.js';
|
|
10
|
+
import {ChildProcessError} from './lib/child-process-error.js';
|
|
11
|
+
|
|
12
|
+
/** @type {Smokehouse.TestDfn} */
|
|
13
|
+
const smokeTestDefn = {
|
|
14
|
+
id: 'test-smoke',
|
|
15
|
+
expectations: {
|
|
16
|
+
lhr: {
|
|
17
|
+
requestedUrl: 'http://localhost:10200/simple-page.html',
|
|
18
|
+
finalDisplayedUrl: 'http://localhost:10200/simple-page.html',
|
|
19
|
+
audits: {},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('runSmokehouse error logging', () => {
|
|
25
|
+
/** @type {string[]} */
|
|
26
|
+
let loggedMessages;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
loggedMessages = [];
|
|
30
|
+
td.replace(console, 'log', (/** @type {unknown[]} */ ...args) => {
|
|
31
|
+
loggedMessages.push(args.map(String).join(' '));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
td.reset();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('logs runner logger output when lighthouseRunner returns a malformed result', async () => {
|
|
40
|
+
/** @type {Smokehouse.LighthouseRunner} */
|
|
41
|
+
// @ts-expect-error - testing malformed runner result
|
|
42
|
+
const lighthouseRunner = async (_url, _config, logger) => {
|
|
43
|
+
logger?.log('runner progress before malformed result');
|
|
44
|
+
return {lhr: {}, artifacts: undefined};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const {success, testResults} = await runSmokehouse([smokeTestDefn], {
|
|
48
|
+
lighthouseRunner,
|
|
49
|
+
retries: 0,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(success).toBe(false);
|
|
53
|
+
expect(testResults).toHaveLength(1);
|
|
54
|
+
expect(testResults[0].failed).toBe(1);
|
|
55
|
+
|
|
56
|
+
const combinedOutput = loggedMessages.join('\n');
|
|
57
|
+
expect(combinedOutput).toContain('lighthouse runner returned a bad result');
|
|
58
|
+
expect(combinedOutput).toContain('runner progress before malformed result');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('logs runner logger output when lighthouseRunner throws a regular Error', async () => {
|
|
62
|
+
/** @type {Smokehouse.LighthouseRunner} */
|
|
63
|
+
const lighthouseRunner = async (_url, _config, logger) => {
|
|
64
|
+
logger?.log('runner progress before throwing');
|
|
65
|
+
throw new Error('unexpected runner failure');
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const {success, testResults} = await runSmokehouse([smokeTestDefn], {
|
|
69
|
+
lighthouseRunner,
|
|
70
|
+
retries: 0,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(success).toBe(false);
|
|
74
|
+
expect(testResults).toHaveLength(1);
|
|
75
|
+
expect(testResults[0].failed).toBe(1);
|
|
76
|
+
|
|
77
|
+
const combinedOutput = loggedMessages.join('\n');
|
|
78
|
+
expect(combinedOutput).toContain('unexpected runner failure');
|
|
79
|
+
expect(combinedOutput).toContain('runner progress before throwing');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('does not duplicate runner logger output for ChildProcessError', async () => {
|
|
83
|
+
/** @type {Smokehouse.LighthouseRunner} */
|
|
84
|
+
const lighthouseRunner = async (_url, _config, logger) => {
|
|
85
|
+
logger?.log('unique-cli-runner-log-line');
|
|
86
|
+
throw new ChildProcessError('Lighthouse run failed', logger?.getLog());
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const {success} = await runSmokehouse([smokeTestDefn], {
|
|
90
|
+
lighthouseRunner,
|
|
91
|
+
retries: 0,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(success).toBe(false);
|
|
95
|
+
|
|
96
|
+
const combinedOutput = loggedMessages.join('\n');
|
|
97
|
+
const occurrences = combinedOutput.split('unique-cli-runner-log-line').length - 1;
|
|
98
|
+
expect(occurrences).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('does not duplicate runner logger output when Error message already includes it', async () => {
|
|
102
|
+
/** @type {Smokehouse.LighthouseRunner} */
|
|
103
|
+
const lighthouseRunner = async (_url, _config, logger) => {
|
|
104
|
+
logger?.log('unique-worker-runner-log-line');
|
|
105
|
+
const log = logger?.getLog() || '';
|
|
106
|
+
throw new Error(`Worker returned an error: boom\nLog:\n${log}\n`);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const {success} = await runSmokehouse([smokeTestDefn], {
|
|
110
|
+
lighthouseRunner,
|
|
111
|
+
retries: 0,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(success).toBe(false);
|
|
115
|
+
|
|
116
|
+
const combinedOutput = loggedMessages.join('\n');
|
|
117
|
+
const occurrences = combinedOutput.split('unique-worker-runner-log-line').length - 1;
|
|
118
|
+
expect(occurrences).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
});
|