diff --git a/cli/cli-flags.js b/cli/cli-flags.js index 8dba9776406d..edcb0d641d5d 100644 --- a/cli/cli-flags.js +++ b/cli/cli-flags.js @@ -204,12 +204,16 @@ function getYargsParser(manualArgv) { type: 'boolean', describe: 'Disables collection of the full page screenshot, which can be quite large', }, + 'ignore-status-code': { + type: 'boolean', + describe: 'Disables failing on all error status codes, and instead issues a warning.', + }, }) .group([ 'save-assets', 'list-all-audits', 'list-locales', 'list-trace-categories', 'additional-trace-categories', 'config-path', 'preset', 'chrome-flags', 'port', 'hostname', 'form-factor', 'screenEmulation', 'emulatedUserAgent', 'max-wait-for-load', 'enable-error-reporting', 'gather-mode', 'audit-mode', - 'only-audits', 'only-categories', 'skip-audits', 'budget-path', 'disable-full-page-screenshot', + 'only-audits', 'only-categories', 'skip-audits', 'budget-path', 'disable-full-page-screenshot', 'ignore-status-code', ], 'Configuration:') // Output diff --git a/core/config/constants.js b/core/config/constants.js index e77da5a79866..27eaf2ba389f 100644 --- a/core/config/constants.js +++ b/core/config/constants.js @@ -113,6 +113,7 @@ const defaultSettings = { disableFullPageScreenshot: false, skipAboutBlank: false, blankPage: 'about:blank', + ignoreStatusCode: false, // the following settings have no defaults but we still want ensure that `key in settings` // in config will work in a typechecked way diff --git a/core/gather/navigation-runner.js b/core/gather/navigation-runner.js index bd68b24d8edc..8cdd5de231ef 100644 --- a/core/gather/navigation-runner.js +++ b/core/gather/navigation-runner.js @@ -155,6 +155,7 @@ async function _computeNavigationResult( const pageLoadError = debugData.records ? getPageLoadError(navigationError, { url: mainDocumentUrl, + ignoreStatusCode: navigationContext.resolvedConfig.settings.ignoreStatusCode, networkRecords: debugData.records, warnings: navigationContext.baseArtifacts.LighthouseRunWarnings, }) diff --git a/core/lib/navigation-error.js b/core/lib/navigation-error.js index 4e7fb4f33eb0..d7fc35c3ce47 100644 --- a/core/lib/navigation-error.js +++ b/core/lib/navigation-error.js @@ -16,6 +16,13 @@ const UIStrings = { */ warningXhtml: 'The page MIME type is XHTML: Lighthouse does not explicitly support this document type', + /** + * @description Warning shown in report when the page under test returns an error code, which Lighthouse is not able to reliably load so we display a warning. + * @example {404} errorCode + */ + warningStatusCode: 'Lighthouse was unable to reliably load the page you requested. Make sure' + + ' you are testing the correct URL and that the server is properly responding' + + ' to all requests. (Status code: {errorCode})', }; const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); @@ -27,9 +34,10 @@ const XHTML_MIME_TYPE = 'application/xhtml+xml'; /** * Returns an error if the original network request failed or wasn't found. * @param {LH.Artifacts.NetworkRequest|undefined} mainRecord + * @param {{warnings: Array, ignoreStatusCode?: LH.Config.Settings['ignoreStatusCode']}} context * @return {LH.LighthouseError|undefined} */ -function getNetworkError(mainRecord) { +function getNetworkError(mainRecord, context) { if (!mainRecord) { return new LighthouseError(LighthouseError.errors.NO_DOCUMENT_REQUEST); } else if (mainRecord.failed) { @@ -47,9 +55,13 @@ function getNetworkError(mainRecord) { LighthouseError.errors.FAILED_DOCUMENT_REQUEST, {errorDetails: netErr}); } } else if (mainRecord.hasErrorStatusCode()) { - return new LighthouseError(LighthouseError.errors.ERRORED_DOCUMENT_REQUEST, { - statusCode: `${mainRecord.statusCode}`, - }); + if (context.ignoreStatusCode) { + context.warnings.push(str_(UIStrings.warningStatusCode, {errorCode: mainRecord.statusCode})); + } else { + return new LighthouseError(LighthouseError.errors.ERRORED_DOCUMENT_REQUEST, { + statusCode: `${mainRecord.statusCode}`, + }); + } } } @@ -113,7 +125,7 @@ function getNonHtmlError(finalRecord) { * Returns an error if the page load should be considered failed, e.g. from a * main document request failure, a security issue, etc. * @param {LH.LighthouseError|undefined} navigationError - * @param {{url: string, networkRecords: Array, warnings: Array}} context + * @param {{url: string, ignoreStatusCode?: LH.Config.Settings['ignoreStatusCode'], networkRecords: Array, warnings: Array}} context * @return {LH.LighthouseError|undefined} */ function getPageLoadError(navigationError, context) { @@ -144,7 +156,7 @@ function getPageLoadError(navigationError, context) { context.warnings.push(str_(UIStrings.warningXhtml)); } - const networkError = getNetworkError(mainRecord); + const networkError = getNetworkError(mainRecord, context); const interstitialError = getInterstitialError(mainRecord, networkRecords); const nonHtmlError = getNonHtmlError(finalRecord); diff --git a/core/scripts/i18n/collect-strings.js b/core/scripts/i18n/collect-strings.js index 998ad88856e6..6c6641f63552 100644 --- a/core/scripts/i18n/collect-strings.js +++ b/core/scripts/i18n/collect-strings.js @@ -720,6 +720,8 @@ function checkKnownFixedCollisions(strings) { 'Document has a valid $MARKDOWN_SNIPPET_0$', 'Failing Elements', 'Failing Elements', + 'Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests. (Status code: $ICU_0$)', + 'Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests. (Status code: $ICU_0$)', 'Name', 'Name', 'Pages that use portals are not currently eligible for back/forward cache.', diff --git a/core/test/fixtures/user-flows/artifacts/step0/artifacts.json b/core/test/fixtures/user-flows/artifacts/step0/artifacts.json index 7deaf88dedda..20482e420574 100644 --- a/core/test/fixtures/user-flows/artifacts/step0/artifacts.json +++ b/core/test/fixtures/user-flows/artifacts/step0/artifacts.json @@ -502,6 +502,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": true, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, diff --git a/core/test/fixtures/user-flows/artifacts/step1/artifacts.json b/core/test/fixtures/user-flows/artifacts/step1/artifacts.json index 1b8c6ecce0f3..5df868da5c8c 100644 --- a/core/test/fixtures/user-flows/artifacts/step1/artifacts.json +++ b/core/test/fixtures/user-flows/artifacts/step1/artifacts.json @@ -294,6 +294,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, diff --git a/core/test/fixtures/user-flows/artifacts/step2/artifacts.json b/core/test/fixtures/user-flows/artifacts/step2/artifacts.json index 97965a27e92b..b60af9c44083 100644 --- a/core/test/fixtures/user-flows/artifacts/step2/artifacts.json +++ b/core/test/fixtures/user-flows/artifacts/step2/artifacts.json @@ -214,6 +214,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, diff --git a/core/test/fixtures/user-flows/artifacts/step3/artifacts.json b/core/test/fixtures/user-flows/artifacts/step3/artifacts.json index 94c86fe8a65f..31fd53eb918f 100644 --- a/core/test/fixtures/user-flows/artifacts/step3/artifacts.json +++ b/core/test/fixtures/user-flows/artifacts/step3/artifacts.json @@ -486,6 +486,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": true, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, diff --git a/core/test/fixtures/user-flows/reports/sample-flow-result.json b/core/test/fixtures/user-flows/reports/sample-flow-result.json index 204a555cf3bf..2fe8f67dd27f 100644 --- a/core/test/fixtures/user-flows/reports/sample-flow-result.json +++ b/core/test/fixtures/user-flows/reports/sample-flow-result.json @@ -3753,6 +3753,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": true, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -10677,6 +10678,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -14798,6 +14800,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -21359,6 +21362,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": true, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, diff --git a/core/test/lib/navigation-error-test.js b/core/test/lib/navigation-error-test.js index ccf4d8c020cb..51aa9c68362c 100644 --- a/core/test/lib/navigation-error-test.js +++ b/core/test/lib/navigation-error-test.js @@ -31,9 +31,10 @@ function makeNetworkRequest() { describe('#getNetworkError', () => { /** * @param {NetworkRequest=} mainRecord + * @param {{warnings: Array, ignoreStatusCode?: LH.Config.Settings['ignoreStatusCode']}=} context */ - function getAndExpectError(mainRecord) { - const error = getNetworkError(mainRecord); + function getAndExpectError(mainRecord, context) { + const error = getNetworkError(mainRecord, {warnings: [], ...context}); if (!error) throw new Error('expected a network error'); return error; } @@ -42,7 +43,7 @@ describe('#getNetworkError', () => { const url = 'http://the-page.com'; const mainRecord = makeNetworkRequest(); mainRecord.url = url; - expect(getNetworkError(mainRecord)).toBeUndefined(); + expect(getNetworkError(mainRecord, {warnings: []})).toBeUndefined(); }); it('fails when page fails to load', () => { @@ -66,18 +67,44 @@ describe('#getNetworkError', () => { expect(error.friendlyMessage).toBeDisplayString(/^Lighthouse was unable to reliably load/); }); - it('fails when page returns with a 404', () => { + it('warns when page returns with a 404 with flag', () => { const url = 'http://the-page.com'; const mainRecord = makeNetworkRequest(); mainRecord.url = url; mainRecord.statusCode = 404; - const error = getAndExpectError(mainRecord); + const context = { + url, + networkRecords: [mainRecord], + warnings: [], + loadFailureMode: LoadFailureMode.warn, + ignoreStatusCode: true, + }; + + const error = getNetworkError(mainRecord, context); + expect(error).toBeUndefined(); + expect(context.warnings[0]).toBeDisplayString(/^Lighthouse was unable to reliably load/); + }); + + it('fails when page returns with a 404 without flag', () => { + const url = 'http://the-page.com'; + const mainRecord = makeNetworkRequest(); + mainRecord.url = url; + mainRecord.statusCode = 404; + const context = { + url, + networkRecords: [mainRecord], + warnings: [], + loadFailureMode: LoadFailureMode.warn, + }; + + const error = getAndExpectError(mainRecord, context); + expect(error).toBeDefined(); expect(error.message).toEqual('ERRORED_DOCUMENT_REQUEST'); expect(error.code).toEqual('ERRORED_DOCUMENT_REQUEST'); expect(error.friendlyMessage).toBeDisplayString(/^Lighthouse was unable to reliably load.*404/); }); - it('fails when page returns with a 500', () => { + it('fails when page returns with a 500 without flag', () => { const url = 'http://the-page.com'; const mainRecord = makeNetworkRequest(); mainRecord.url = url; diff --git a/core/test/results/artifacts/artifacts.json b/core/test/results/artifacts/artifacts.json index 6b2ea45aac08..0399bc8a4ccd 100644 --- a/core/test/results/artifacts/artifacts.json +++ b/core/test/results/artifacts/artifacts.json @@ -71,6 +71,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": [ { "path": "/", diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 9645e1a6beb8..5f7b9a461da3 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -5749,6 +5749,7 @@ "disableFullPageScreenshot": false, "skipAboutBlank": false, "blankPage": "about:blank", + "ignoreStatusCode": false, "budgets": [ { "path": "/", diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index e3daf436c505..7a4548a20154 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -2657,6 +2657,9 @@ "core/lib/lh-error.js | urlInvalid": { "message": "The URL you have provided appears to be invalid." }, + "core/lib/navigation-error.js | warningStatusCode": { + "message": "Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests. (Status code: {errorCode})" + }, "core/lib/navigation-error.js | warningXhtml": { "message": "The page MIME type is XHTML: Lighthouse does not explicitly support this document type" }, diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index 80f1dc3b4aa6..5057be880762 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -2657,6 +2657,9 @@ "core/lib/lh-error.js | urlInvalid": { "message": "T̂h́ê ÚR̂Ĺ ŷóû h́âv́ê ṕr̂óv̂íd̂éd̂ áp̂ṕêár̂ś t̂ó b̂é îńv̂ál̂íd̂." }, + "core/lib/navigation-error.js | warningStatusCode": { + "message": "L̂íĝh́t̂h́ôúŝé ŵáŝ ún̂áb̂ĺê t́ô ŕêĺîáb̂ĺŷ ĺôád̂ t́ĥé p̂áĝé ŷóû ŕêq́ûéŝt́êd́. M̂ák̂é ŝúr̂é ŷóû ár̂é t̂éŝt́îńĝ t́ĥé ĉór̂ŕêćt̂ ÚR̂Ĺ âńd̂ t́ĥát̂ t́ĥé ŝér̂v́êŕ îś p̂ŕôṕêŕl̂ý r̂éŝṕôńd̂ín̂ǵ t̂ó âĺl̂ ŕêq́ûéŝt́ŝ. (Śt̂át̂úŝ ćôd́ê: {errorCode})" + }, "core/lib/navigation-error.js | warningXhtml": { "message": "T̂h́ê ṕâǵê ḾÎḾÊ t́ŷṕê íŝ X́ĤT́M̂Ĺ: L̂íĝh́t̂h́ôúŝé d̂óêś n̂ót̂ éx̂ṕl̂íĉít̂ĺŷ śûṕp̂ór̂t́ t̂h́îś d̂óĉúm̂én̂t́ t̂ýp̂é" }, diff --git a/types/lhr/settings.d.ts b/types/lhr/settings.d.ts index 46dfe7c6f601..bc80aa37741b 100644 --- a/types/lhr/settings.d.ts +++ b/types/lhr/settings.d.ts @@ -114,6 +114,9 @@ export type ScreenEmulationSettings = { blankPage?: string; /** Disables collection of the full page screenshot, which can be rather large and possibly leave the page in an undesirable state. */ disableFullPageScreenshot?: boolean; + + /** Disables failing on 404 status code, and instead issues a warning */ + ignoreStatusCode?: boolean; } export interface ConfigSettings extends Required {