diff --git a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html index 0ab54e3eb094..3e98cbe370f4 100644 --- a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html +++ b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html @@ -56,6 +56,9 @@ + + + diff --git a/lighthouse-cli/test/smokehouse/dbw-config.js b/lighthouse-cli/test/smokehouse/dbw-config.js index c4f87d8356db..468dfe34494a 100644 --- a/lighthouse-cli/test/smokehouse/dbw-config.js +++ b/lighthouse-cli/test/smokehouse/dbw-config.js @@ -22,4 +22,8 @@ module.exports = { 'apple-touch-icon', // pull in apple touch icon to test `LinkElements` ], }, + audits: [ + // Test the `ignoredPatterns` audit option. + {path: 'errors-in-console', options: {ignoredPatterns: ['An ignored error']}}, + ], }; diff --git a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js index 20a5f71d6bc7..8a9d0121317b 100644 --- a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js +++ b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js @@ -184,16 +184,21 @@ const expectations = [ score: 0, details: { items: [ - { - source: 'network', - description: 'Failed to load resource: the server responded with a status of 404 (Not Found)', - url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200', - }, { source: 'other', description: 'Application Cache Error event: Manifest fetch failed (404) http://localhost:10200/dobetterweb/clock.appcache', url: 'http://localhost:10200/dobetterweb/dbw_tester.html', }, + { + source: 'Runtime.exception', + description: /^Error: A distinctive error\s+at http:\/\/localhost:10200\/dobetterweb\/dbw_tester.html:\d+:\d+$/, + url: 'http://localhost:10200/dobetterweb/dbw_tester.html', + }, + { + source: 'network', + description: 'Failed to load resource: the server responded with a status of 404 (Not Found)', + url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200', + }, { source: 'network', description: 'Failed to load resource: the server responded with a status of 404 (Not Found)', @@ -209,11 +214,6 @@ const expectations = [ description: 'Failed to load resource: the server responded with a status of 404 (Not Found)', url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200', }, - { - source: 'Runtime.exception', - description: /^Error: A distinctive error\s+at http:\/\/localhost:10200\/dobetterweb\/dbw_tester.html:\d+:\d+$/, - url: 'http://localhost:10200/dobetterweb/dbw_tester.html', - }, ], }, }, diff --git a/lighthouse-core/audits/errors-in-console.js b/lighthouse-core/audits/errors-in-console.js index 05cff456b8c7..341204bfff79 100644 --- a/lighthouse-core/audits/errors-in-console.js +++ b/lighthouse-core/audits/errors-in-console.js @@ -28,6 +28,8 @@ const UIStrings = { const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); +/** @typedef {{ignoredPatterns?: Array}} AuditOptions */ + class ErrorLogs extends Audit { /** * @return {LH.Audit.Meta} @@ -42,11 +44,41 @@ class ErrorLogs extends Audit { }; } + /** @return {AuditOptions} */ + static defaultOptions() { + return {}; + } + + + /** + * @template {{description: string | undefined}} T + * @param {Array} items + * @param {AuditOptions} options + * @return {Array} + */ + static filterAccordingToOptions(items, options) { + const {ignoredPatterns} = options; + 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 {LH.Audit.Product} */ - static audit(artifacts) { + static audit(artifacts, context) { + const auditOptions = /** @type {AuditOptions} */ (context.options); + const consoleEntries = artifacts.ConsoleMessages; const runtimeExceptions = artifacts.RuntimeExceptions; /** @type {Array<{source: string, description: string|undefined, url: string|undefined}>} */ @@ -73,7 +105,10 @@ class ErrorLogs extends Audit { }; }); - const tableRows = consoleRows.concat(runtimeExRows); + const tableRows = ErrorLogs.filterAccordingToOptions( + consoleRows.concat(runtimeExRows), + auditOptions + ).sort((a, b) => (a.description || '').localeCompare(b.description || '')); /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ diff --git a/lighthouse-core/test/audits/errors-in-console-test.js b/lighthouse-core/test/audits/errors-in-console-test.js index 482d52a0d0ad..c9480cb1da5a 100644 --- a/lighthouse-core/test/audits/errors-in-console-test.js +++ b/lighthouse-core/test/audits/errors-in-console-test.js @@ -15,7 +15,7 @@ describe('Console error logs audit', () => { const auditResult = ErrorLogsAudit.audit({ ConsoleMessages: [], RuntimeExceptions: [], - }); + }, {options: {}}); assert.equal(auditResult.numericValue, 0); assert.equal(auditResult.score, 1); assert.ok(!auditResult.displayValue, 0); @@ -34,7 +34,7 @@ describe('Console error logs audit', () => { }, ], RuntimeExceptions: [], - }); + }, {options: {}}); assert.equal(auditResult.numericValue, 0); assert.equal(auditResult.score, 1); assert.equal(auditResult.details.items.length, 0); @@ -79,7 +79,7 @@ describe('Console error logs audit', () => { 'executionContextId': 3, }, }], - }); + }, {options: {}}); assert.equal(auditResult.numericValue, 3); assert.equal(auditResult.score, 0); @@ -87,13 +87,13 @@ describe('Console error logs audit', () => { assert.equal(auditResult.details.items[0].url, 'http://www.example.com/favicon.ico'); assert.equal(auditResult.details.items[0].description, 'The server responded with a status of 404 (Not Found)'); - assert.equal(auditResult.details.items[1].url, 'http://www.example.com/wsconnect.ws'); - assert.equal(auditResult.details.items[1].description, - 'WebSocket connection failed: Unexpected response code: 500'); - assert.equal(auditResult.details.items[2].url, + assert.equal(auditResult.details.items[1].url, 'http://example.com/fancybox.js'); - assert.equal(auditResult.details.items[2].description, + assert.equal(auditResult.details.items[1].description, 'TypeError: Cannot read property \'msie\' of undefined'); + assert.equal(auditResult.details.items[2].url, 'http://www.example.com/wsconnect.ws'); + assert.equal(auditResult.details.items[2].description, + 'WebSocket connection failed: Unexpected response code: 500'); }); it('handle the case when some logs fields are undefined', () => { @@ -106,7 +106,7 @@ describe('Console error logs audit', () => { }, ], RuntimeExceptions: [], - }); + }, {options: {}}); assert.equal(auditResult.numericValue, 1); assert.equal(auditResult.score, 0); assert.equal(auditResult.details.items.length, 1); @@ -137,7 +137,7 @@ describe('Console error logs audit', () => { 'executionContextId': 3, }, }], - }); + }, {options: {}}); assert.equal(auditResult.numericValue, 1); assert.equal(auditResult.score, 0); assert.equal(auditResult.details.items.length, 1); @@ -145,4 +145,121 @@ describe('Console error logs audit', () => { assert.strictEqual(auditResult.details.items[0].description, 'TypeError: Cannot read property \'msie\' of undefined'); }); + + describe('options', () => { + it('does nothing with an empty pattern', () => { + const options = {ignoredPatterns: ''}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [ + { + entry: { + level: 'error', + source: 'network', + text: 'This is a simple error msg', + }, + }, + ], + RuntimeExceptions: [], + }, {options}); + + expect(result.score).toBe(0); + expect(result.details.items).toHaveLength(1); + }); + + it('does nothing with an empty description', () => { + const options = {ignoredPatterns: 'pattern'}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [ + { + entry: { + level: 'error', + }, + }, + ], + RuntimeExceptions: [], + }, {options}); + + expect(result.score).toBe(0); + expect(result.details.items).toHaveLength(1); + }); + + it('does nothing with an empty description', () => { + const options = {ignoredPatterns: 'pattern'}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [ + { + entry: { + level: 'error', + }, + }, + ], + RuntimeExceptions: [], + }, {options}); + + expect(result.score).toBe(0); + expect(result.details.items).toHaveLength(1); + }); + + it('filters console messages as a string', () => { + const options = {ignoredPatterns: ['simple']}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [ + { + entry: { + level: 'error', + source: 'network', + text: 'This is a simple error msg', + }, + }, + ], + RuntimeExceptions: [], + }, {options}); + + expect(result.score).toBe(1); + expect(result.details.items).toHaveLength(0); + }); + + it('filters console messages as a regex', () => { + const options = {ignoredPatterns: [/simple.*msg/]}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [ + { + entry: { + level: 'error', + source: 'network', + text: 'This is a simple error msg', + }, + }, + ], + RuntimeExceptions: [], + }, {options}); + + expect(result.score).toBe(1); + expect(result.details.items).toHaveLength(0); + }); + + it('filters exceptions with both regex and strings', () => { + const options = {ignoredPatterns: [/s.mple/i, 'really']}; + const result = ErrorLogsAudit.audit({ + ConsoleMessages: [], + RuntimeExceptions: [ + { + exceptionDetails: { + url: 'http://example.com/url.js', + text: 'Simple Error: You messed up', + }, + }, + { + exceptionDetails: { + url: 'http://example.com/url.js', + text: 'Bad Error: You really messed up', + }, + }, + ], + }, {options}); + + expect(result.score).toBe(1); + expect(result.details.items).toHaveLength(0); + }); + }); }); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index fe2621605fac..20f6dc01d8d1 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -236,6 +236,11 @@ "description": "Application Cache Error event: Manifest fetch failed (404) http://localhost:10200/dobetterweb/clock.appcache", "url": "http://localhost:10200/dobetterweb/dbw_tester.html" }, + { + "source": "Runtime.exception", + "description": "Error: An error\n at http://localhost:10200/dobetterweb/dbw_tester.html:42:38", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html" + }, { "source": "network", "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", @@ -250,11 +255,6 @@ "source": "network", "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200" - }, - { - "source": "Runtime.exception", - "description": "Error: An error\n at http://localhost:10200/dobetterweb/dbw_tester.html:42:38", - "url": "http://localhost:10200/dobetterweb/dbw_tester.html" } ] } diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index 0df7875e302a..588fc80074ec 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -600,6 +600,11 @@ "source": "other", "url": "http://localhost:10200/dobetterweb/dbw_tester.html" }, + { + "description": "Error: An error\n at http://localhost:10200/dobetterweb/dbw_tester.html:42:38", + "source": "Runtime.exception", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html" + }, { "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", "source": "network", @@ -614,11 +619,6 @@ "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", "source": "network", "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200" - }, - { - "description": "Error: An error\n at http://localhost:10200/dobetterweb/dbw_tester.html:42:38", - "source": "Runtime.exception", - "url": "http://localhost:10200/dobetterweb/dbw_tester.html" } ], "type": "table"