diff --git a/packages/utils/src/audit-diff-finder.js b/packages/utils/src/audit-diff-finder.js index 231ced94a..43a7d258e 100644 --- a/packages/utils/src/audit-diff-finder.js +++ b/packages/utils/src/audit-diff-finder.js @@ -242,6 +242,15 @@ function findAuditDetailItemKeyDiffs(auditId, baseEntry, compareEntry) { return diffs; } + +/** @param {string} s */ +function replaceNondeterministicStrings(s) { + return s + .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, 'UUID') + .replace(/:[0-9]{3,5}\//, ':PORT/') + .replace(/\.[0-9a-f]{8}\.(js|css|woff|html|png|jpeg|jpg|svg)/, '.HASH.$1'); +} + /** * TODO: consider doing more than URL-based comparisons. * @@ -255,7 +264,7 @@ function zipBaseAndCompareItems(baseItems, compareItems) { ...baseItems.map((item, i) => ({item, kind: 'base', index: i})), ...compareItems.map((item, i) => ({item, kind: 'compare', index: i})), ], - entry => (entry.item.url === undefined ? JSON.stringify(entry.item) : entry.item.url) + entry => replaceNondeterministicStrings(getItemKey(entry.item)) ); /** @type {Array<{base?: DetailItemEntry, compare?: DetailItemEntry}>} */ @@ -453,4 +462,5 @@ module.exports = { getRowLabelForIndex, getMostSevereDiffLabel, zipBaseAndCompareItems, + replaceNondeterministicStrings, }; diff --git a/packages/utils/test/audit-diff-finder.test.js b/packages/utils/test/audit-diff-finder.test.js index 6aa00a56f..731769fa3 100644 --- a/packages/utils/test/audit-diff-finder.test.js +++ b/packages/utils/test/audit-diff-finder.test.js @@ -13,6 +13,8 @@ const { getDiffLabel, getRowLabel, getRowLabelForIndex, + zipBaseAndCompareItems, + replaceNondeterministicStrings, } = require('@lhci/utils/src/audit-diff-finder.js'); describe('#findAuditDiffs', () => { @@ -516,3 +518,35 @@ describe('#getRowLabel', () => { expect(getRowLabelForIndex(diffs, 2, 0)).toEqual('better'); }); }); + +describe('#replaceNondeterministicStrings', () => { + it('should work on non-replacements', () => { + expect(replaceNondeterministicStrings('nonsense')).toEqual('nonsense'); + expect(replaceNondeterministicStrings('Other')).toEqual('Other'); + expect(replaceNondeterministicStrings('Unknown')).toEqual('Unknown'); + expect(replaceNondeterministicStrings('foo.notahash.js')).toEqual('foo.notahash.js'); + expect(replaceNondeterministicStrings('foo.1234567.js')).toEqual('foo.1234567.js'); + expect(replaceNondeterministicStrings('at foo.js:1234')).toEqual('at foo.js:1234'); + expect(replaceNondeterministicStrings('http://localhost/foo')).toEqual('http://localhost/foo'); + expect(replaceNondeterministicStrings('data:image/png;base64,abcdef')).toEqual( + 'data:image/png;base64,abcdef' + ); + }); + + it('should replace hash parts', () => { + expect(replaceNondeterministicStrings('foo.12345678.js')).toEqual('foo.HASH.js'); + expect(replaceNondeterministicStrings('foo.abcdef12.js')).toEqual('foo.HASH.js'); + }); + + it('should replace ports', () => { + expect(replaceNondeterministicStrings('http://localhost:1337/foo?bar=1#baz')).toEqual( + 'http://localhost:PORT/foo?bar=1#baz' + ); + }); + + it('should replace uuids', () => { + expect( + replaceNondeterministicStrings('Text') + ).toEqual('Text'); + }); +});