/** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Audits a page to determine whether it contains console errors. * This is done by collecting Chrome console log messages and filtering out the non-error ones. */ import log from 'lighthouse-logger'; import {Audit} from './audit.js'; import {JSBundles} from '../computed/js-bundles.js'; import * as i18n from '../lib/i18n/i18n.js'; const UIStrings = { /** Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when no browser errors were logged into the devtools console. */ title: 'No browser errors logged to the console', /** Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when browser errors occurred and were logged into the devtools console. */ failureTitle: 'Browser errors were logged to the console', /** Description of a Lighthouse audit that tells the user why errors being logged to the devtools console are a cause for concern and so should be fixed. This is displayed after a user expands the section to see more. No character length limits. */ description: 'Errors logged to the console indicate unresolved problems. ' + 'They can come from network request failures and other browser concerns. ' + '[Learn more about this errors in console diagnostic audit](https://developer.chrome.com/docs/lighthouse/best-practices/errors-in-console/)', }; const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); /** @typedef {{ignoredPatterns?: Array}} AuditOptions */ class ErrorLogs extends Audit { /** * @return {LH.Audit.Meta} */ static get meta() { return { id: 'errors-in-console', title: str_(UIStrings.title), failureTitle: str_(UIStrings.failureTitle), description: str_(UIStrings.description), requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'], }; } /** @return {AuditOptions} */ static get defaultOptions() { // Any failed network requests with error messsage aren't actionable return {ignoredPatterns: ['ERR_BLOCKED_BY_CLIENT.Inspector']}; } /** * @template {{description: string | undefined}} T * @param {Array} items * @param {AuditOptions} options * @return {Array} */ static filterAccordingToOptions(items, options) { const {ignoredPatterns, ...restOfOptions} = options; const otherOptionKeys = Object.keys(restOfOptions); if (otherOptionKeys.length) log.warn(this.meta.id, 'Unrecognized options', otherOptionKeys); if (!ignoredPatterns) return items; return items.filter(({description}) => { if (!description) return true; for (const pattern of ignoredPatterns) { if (pattern instanceof RegExp && pattern.test(description)) return false; if (typeof pattern === 'string' && description.includes(pattern)) return false; } return true; }); } /** * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context * @return {Promise} */ static async audit(artifacts, context) { /** @type {AuditOptions} */ const auditOptions = context.options; const bundles = await JSBundles.request(artifacts, context); /** @type {Array<{source: string, description: string|undefined, sourceLocation: LH.Audit.Details.SourceLocationValue|undefined}>} */ const consoleRows = artifacts.ConsoleMessages .filter(item => item.level === 'error') .map(item => { const bundle = bundles.find(bundle => bundle.script.scriptId === item.scriptId); return { source: item.source, description: item.text, sourceLocation: Audit.makeSourceLocationFromConsoleMessage(item, bundle), }; }); const tableRows = ErrorLogs.filterAccordingToOptions(consoleRows, auditOptions) .sort((a, b) => (a.description || '').localeCompare(b.description || '')); /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ /* eslint-disable max-len */ {key: 'sourceLocation', valueType: 'source-location', label: str_(i18n.UIStrings.columnSource)}, {key: 'description', valueType: 'code', label: str_(i18n.UIStrings.columnDescription)}, /* eslint-enable max-len */ ]; const details = Audit.makeTableDetails(headings, tableRows); const numErrors = tableRows.length; return { score: Number(numErrors === 0), details, }; } } export default ErrorLogs; export {UIStrings};