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');
+ });
+});