@testivai/witness 1.3.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baselines/store.d.ts.map +1 -1
- package/dist/baselines/store.js +13 -0
- package/dist/baselines/store.js.map +1 -1
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/report.js +3 -1
- package/dist/commands/report.js.map +1 -1
- package/dist/config/local-config.d.ts +17 -0
- package/dist/config/local-config.d.ts.map +1 -1
- package/dist/config/local-config.js.map +1 -1
- package/dist/diff/attribution.d.ts +88 -0
- package/dist/diff/attribution.d.ts.map +1 -0
- package/dist/diff/attribution.js +189 -0
- package/dist/diff/attribution.js.map +1 -0
- package/dist/diff/diff.d.ts.map +1 -1
- package/dist/diff/diff.js +13 -2
- package/dist/diff/diff.js.map +1 -1
- package/dist/diff/mask.d.ts +98 -0
- package/dist/diff/mask.d.ts.map +1 -0
- package/dist/diff/mask.js +209 -0
- package/dist/diff/mask.js.map +1 -0
- package/dist/diff/regions.d.ts +17 -4
- package/dist/diff/regions.d.ts.map +1 -1
- package/dist/diff/regions.js +70 -5
- package/dist/diff/regions.js.map +1 -1
- package/dist/diff/types.d.ts +13 -0
- package/dist/diff/types.d.ts.map +1 -1
- package/dist/report/compare.d.ts +9 -0
- package/dist/report/compare.d.ts.map +1 -1
- package/dist/report/compare.js +107 -12
- package/dist/report/compare.js.map +1 -1
- package/dist/report/generator.d.ts +12 -1
- package/dist/report/generator.d.ts.map +1 -1
- package/dist/report/generator.js +36 -15
- package/dist/report/generator.js.map +1 -1
- package/dist/report/results.d.ts +34 -3
- package/dist/report/results.d.ts.map +1 -1
- package/dist/report/template.d.ts.map +1 -1
- package/dist/report/template.js +122 -1
- package/dist/report/template.js.map +1 -1
- package/package.json +1 -1
package/dist/report/compare.js
CHANGED
|
@@ -45,13 +45,34 @@ const pngjs_1 = require("pngjs");
|
|
|
45
45
|
const diff_1 = require("../diff");
|
|
46
46
|
const dom_diff_1 = require("../diff/dom-diff");
|
|
47
47
|
const store_1 = require("../baselines/store");
|
|
48
|
+
const mask_1 = require("../diff/mask");
|
|
49
|
+
const attribution_1 = require("../diff/attribution");
|
|
50
|
+
function readElementMap(projectRoot, kind, name) {
|
|
51
|
+
const p = path.join(projectRoot, '.testivai', kind, name, 'elements.json');
|
|
52
|
+
try {
|
|
53
|
+
return (0, attribution_1.parseElementMap)(JSON.parse(fs.readFileSync(p, 'utf-8')));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return []; // absent or malformed map never breaks the compare
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readTempMaskMetadata(projectRoot, name) {
|
|
60
|
+
const metaPath = path.join(projectRoot, '.testivai', 'temp', name, 'metadata.json');
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
63
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {}; // absent or malformed metadata never breaks the compare
|
|
67
|
+
}
|
|
68
|
+
}
|
|
48
69
|
/**
|
|
49
70
|
* Compare all temp screenshots against their baselines.
|
|
50
71
|
*
|
|
51
72
|
* @returns Array of SnapshotResult for each snapshot
|
|
52
73
|
*/
|
|
53
74
|
function compareAll(options) {
|
|
54
|
-
const { projectRoot, reportDir, threshold = 0.1, passCriteria = {} } = options;
|
|
75
|
+
const { projectRoot, reportDir, threshold = 0.1, passCriteria = {}, diffRegions } = options;
|
|
55
76
|
const store = new store_1.BaselineStore(projectRoot);
|
|
56
77
|
const tempNames = store.listTemp();
|
|
57
78
|
const results = [];
|
|
@@ -87,7 +108,16 @@ function compareAll(options) {
|
|
|
87
108
|
// screenshots from the browser, we need to handle the PNG decoding.
|
|
88
109
|
// For simplicity and zero-dep, we do a byte-level comparison first,
|
|
89
110
|
// then fall back to diff engine for actual pixel data.
|
|
90
|
-
const
|
|
111
|
+
const captureMeta = readTempMaskMetadata(projectRoot, name);
|
|
112
|
+
const result = compareBuffers(baselineBuffer, tempBuffer, name, snapshotImagesDir, threshold, {
|
|
113
|
+
configMasks: options.mask ?? [],
|
|
114
|
+
callMasks: [
|
|
115
|
+
...(captureMeta.masks ?? []),
|
|
116
|
+
...(captureMeta.maskSelectors ?? []),
|
|
117
|
+
],
|
|
118
|
+
capturedRects: captureMeta.maskRects,
|
|
119
|
+
regionOptions: diffRegions,
|
|
120
|
+
});
|
|
91
121
|
// DOM-level noise hint. Only meaningful when both sides captured DOM.
|
|
92
122
|
// For 'passed' (pixel-identical) we skip the work — it's already the
|
|
93
123
|
// strongest possible signal.
|
|
@@ -95,9 +125,34 @@ function compareAll(options) {
|
|
|
95
125
|
const baselineDom = store.readDom(name);
|
|
96
126
|
const candidateDom = store.readTempDom(name);
|
|
97
127
|
const domSignal = computeDomSignal(baselineDom, candidateDom);
|
|
128
|
+
// Element attribution + shift classification (element maps are
|
|
129
|
+
// optional; without them regions pass through untouched).
|
|
130
|
+
const baselineElements = readElementMap(projectRoot, 'baselines', name);
|
|
131
|
+
const candidateElements = readElementMap(projectRoot, 'temp', name);
|
|
132
|
+
// Style fingerprint: a stylesheet-only change has an identical DOM
|
|
133
|
+
// but different computed styles — pixels + identical DOM used to
|
|
134
|
+
// read as "likely render noise" (a documented false negative).
|
|
135
|
+
// The hint now requires the style digests to match; a mismatch is
|
|
136
|
+
// surfaced as an explicit, attributed "styles changed" signal.
|
|
98
137
|
if (domSignal) {
|
|
138
|
+
const styleComparison = (0, attribution_1.compareStyleHashes)(baselineElements, candidateElements);
|
|
139
|
+
domSignal.styleCheck = styleComparison.status;
|
|
140
|
+
if (styleComparison.status === 'mismatch') {
|
|
141
|
+
domSignal.styleChanges = {
|
|
142
|
+
count: styleComparison.changed.length,
|
|
143
|
+
elements: styleComparison.changed.slice(0, 10),
|
|
144
|
+
};
|
|
145
|
+
domSignal.noiseHint = false;
|
|
146
|
+
}
|
|
99
147
|
result.dom = domSignal;
|
|
100
148
|
}
|
|
149
|
+
if (result.regions && result.regions.length > 0) {
|
|
150
|
+
result.regions = (0, attribution_1.attributeRegions)(result.regions, baselineElements, candidateElements);
|
|
151
|
+
}
|
|
152
|
+
const pageShift = (0, attribution_1.detectPageShift)(baselineElements, candidateElements);
|
|
153
|
+
if (pageShift) {
|
|
154
|
+
result.pageShift = pageShift;
|
|
155
|
+
}
|
|
101
156
|
applyPassCriteria(result, passCriteria);
|
|
102
157
|
}
|
|
103
158
|
results.push(result);
|
|
@@ -151,6 +206,17 @@ function computeDomSignal(baselineDom, candidateDom) {
|
|
|
151
206
|
noiseHint: !result.domChanged,
|
|
152
207
|
};
|
|
153
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Read a PNG's dimensions from its IHDR header without a full decode.
|
|
211
|
+
* Returns null for anything that doesn't look like a PNG.
|
|
212
|
+
*/
|
|
213
|
+
function readPngDims(buffer) {
|
|
214
|
+
// PNG signature (8 bytes) + IHDR length/type (8) → width at 16, height at 20
|
|
215
|
+
if (buffer.length < 24 || buffer.readUInt32BE(12) !== 0x49484452 /* 'IHDR' */) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
219
|
+
}
|
|
154
220
|
/**
|
|
155
221
|
* Decode a PNG buffer to raw RGBA pixel data using pngjs.
|
|
156
222
|
*/
|
|
@@ -166,16 +232,10 @@ function encodePng(data, width, height) {
|
|
|
166
232
|
png.data = data;
|
|
167
233
|
return pngjs_1.PNG.sync.write(png);
|
|
168
234
|
}
|
|
169
|
-
|
|
170
|
-
* Compare two PNG buffers.
|
|
171
|
-
*
|
|
172
|
-
* Decodes both PNGs to raw RGBA, runs the pixel-level diff engine,
|
|
173
|
-
* writes a valid diff PNG, and returns a SnapshotResult.
|
|
174
|
-
*/
|
|
175
|
-
function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir, threshold) {
|
|
235
|
+
function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir, threshold, maskContext = { configMasks: [], callMasks: [] }) {
|
|
176
236
|
// Quick byte-level comparison — identical files need no decoding
|
|
177
237
|
if (baselineBuffer.equals(candidateBuffer)) {
|
|
178
|
-
|
|
238
|
+
const result = {
|
|
179
239
|
name,
|
|
180
240
|
status: 'passed',
|
|
181
241
|
diffPercent: 0,
|
|
@@ -184,6 +244,23 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
184
244
|
baselinePath: `images/${name}/baseline.png`,
|
|
185
245
|
currentPath: `images/${name}/current.png`,
|
|
186
246
|
};
|
|
247
|
+
// Masks are moot for identical bytes, but the audit trail (and any
|
|
248
|
+
// could-not-apply warnings) must still surface. Header-only dims read.
|
|
249
|
+
const specCount = maskContext.configMasks.length + maskContext.callMasks.length;
|
|
250
|
+
if (specCount > 0) {
|
|
251
|
+
const dims = readPngDims(baselineBuffer);
|
|
252
|
+
if (dims) {
|
|
253
|
+
const fromConfig = (0, mask_1.resolveMasks)(maskContext.configMasks, dims.width, dims.height, maskContext.capturedRects, 'config');
|
|
254
|
+
const fromCall = (0, mask_1.resolveMasks)(maskContext.callMasks, dims.width, dims.height, maskContext.capturedRects, 'call');
|
|
255
|
+
const rects = [...fromConfig.rects, ...fromCall.rects];
|
|
256
|
+
const warnings = [...fromConfig.warnings, ...fromCall.warnings];
|
|
257
|
+
if (rects.length > 0)
|
|
258
|
+
result.masks = rects;
|
|
259
|
+
if (warnings.length > 0)
|
|
260
|
+
result.maskWarnings = warnings;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
187
264
|
}
|
|
188
265
|
try {
|
|
189
266
|
// Decode PNG → raw RGBA so the diff engine gets real pixel data
|
|
@@ -198,7 +275,18 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
198
275
|
const candidate8 = new Uint8ClampedArray(expectedLen);
|
|
199
276
|
candidate8.set(new Uint8ClampedArray(candidate.data.buffer, candidate.data.byteOffset, Math.min(candidate.data.length, expectedLen)));
|
|
200
277
|
const diffOutput = new Uint8ClampedArray(expectedLen);
|
|
201
|
-
|
|
278
|
+
// Resolve masks against this image's dimensions. Config and per-call
|
|
279
|
+
// specs are resolved separately so the audit trail records the origin.
|
|
280
|
+
const fromConfig = (0, mask_1.resolveMasks)(maskContext.configMasks, width, height, maskContext.capturedRects, 'config');
|
|
281
|
+
const fromCall = (0, mask_1.resolveMasks)(maskContext.callMasks, width, height, maskContext.capturedRects, 'call');
|
|
282
|
+
const maskRects = [...fromConfig.rects, ...fromCall.rects];
|
|
283
|
+
const maskWarnings = [...fromConfig.warnings, ...fromCall.warnings];
|
|
284
|
+
const diffResult = (0, diff_1.diff)(baseline8, candidate8, diffOutput, width, height, {
|
|
285
|
+
threshold,
|
|
286
|
+
masks: maskRects,
|
|
287
|
+
detectRegions: true,
|
|
288
|
+
regionOptions: maskContext.regionOptions,
|
|
289
|
+
});
|
|
202
290
|
// Encode the diff RGBA back to a valid PNG and write it
|
|
203
291
|
const diffPngBuffer = encodePng(Buffer.from(diffOutput.buffer), width, height);
|
|
204
292
|
const diffPath = path.join(snapshotImagesDir, 'diff.png');
|
|
@@ -206,7 +294,7 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
206
294
|
// Status starts as 'changed'; applyPassCriteria() in compareAll may
|
|
207
295
|
// flip it to 'passed' based on the configured tolerances.
|
|
208
296
|
const status = 'changed';
|
|
209
|
-
|
|
297
|
+
const result = {
|
|
210
298
|
name,
|
|
211
299
|
status,
|
|
212
300
|
diffPercent: diffResult.diffPercent,
|
|
@@ -216,6 +304,13 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
216
304
|
currentPath: `images/${name}/current.png`,
|
|
217
305
|
diffPath: `images/${name}/diff.png`,
|
|
218
306
|
};
|
|
307
|
+
if (diffResult.regions)
|
|
308
|
+
result.regions = diffResult.regions;
|
|
309
|
+
if (maskRects.length > 0)
|
|
310
|
+
result.masks = maskRects;
|
|
311
|
+
if (maskWarnings.length > 0)
|
|
312
|
+
result.maskWarnings = maskWarnings;
|
|
313
|
+
return result;
|
|
219
314
|
}
|
|
220
315
|
catch (err) {
|
|
221
316
|
// PNG decode failed (corrupt file, unexpected format) — report as changed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/report/compare.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/report/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* → render HTML → optionally auto-open browser.
|
|
6
6
|
*/
|
|
7
7
|
import { PassCriteria } from './compare';
|
|
8
|
-
import { ReportData } from './results';
|
|
8
|
+
import { ReportData, SnapshotResult } from './results';
|
|
9
9
|
export interface GenerateReportOptions {
|
|
10
10
|
projectRoot: string;
|
|
11
11
|
/** Directory for report output. Default: 'visual-report' */
|
|
@@ -35,5 +35,16 @@ export interface GenerateReportOptions {
|
|
|
35
35
|
*
|
|
36
36
|
* @returns The report data
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* results.json schema version. 2.2.0 adds per-snapshot `regions[]`
|
|
40
|
+
* (clustered diff bounding boxes), `masks[]` (applied masks with their
|
|
41
|
+
* source — the audit trail), and `maskWarnings[]`. Additive over 2.1.0.
|
|
42
|
+
*/
|
|
43
|
+
export declare const RESULTS_SCHEMA_VERSION = "2.2.0";
|
|
44
|
+
/**
|
|
45
|
+
* Write results.json for an already-computed set of snapshot results.
|
|
46
|
+
* Exposed for callers that manage comparison and rendering separately.
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateResults(results: SnapshotResult[], reportDir: string, version?: string): void;
|
|
38
49
|
export declare function generateReport(options: GenerateReportOptions): ReportData;
|
|
39
50
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA8B,YAAY,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,UAAU,EAAiB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA8B,YAAY,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,UAAU,EAAiB,cAAc,EAAC,MAAM,WAAW,CAAC;AAIrE,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EAAE,EACzB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAA+B,GACvC,IAAI,CAGN;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,UAAU,CAqDzE"}
|
package/dist/report/generator.js
CHANGED
|
@@ -39,6 +39,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.RESULTS_SCHEMA_VERSION = void 0;
|
|
43
|
+
exports.generateResults = generateResults;
|
|
42
44
|
exports.generateReport = generateReport;
|
|
43
45
|
const fs = __importStar(require("fs"));
|
|
44
46
|
const path = __importStar(require("path"));
|
|
@@ -57,8 +59,22 @@ const local_config_1 = require("../config/local-config");
|
|
|
57
59
|
*
|
|
58
60
|
* @returns The report data
|
|
59
61
|
*/
|
|
62
|
+
/**
|
|
63
|
+
* results.json schema version. 2.2.0 adds per-snapshot `regions[]`
|
|
64
|
+
* (clustered diff bounding boxes), `masks[]` (applied masks with their
|
|
65
|
+
* source — the audit trail), and `maskWarnings[]`. Additive over 2.1.0.
|
|
66
|
+
*/
|
|
67
|
+
exports.RESULTS_SCHEMA_VERSION = '2.2.0';
|
|
68
|
+
/**
|
|
69
|
+
* Write results.json for an already-computed set of snapshot results.
|
|
70
|
+
* Exposed for callers that manage comparison and rendering separately.
|
|
71
|
+
*/
|
|
72
|
+
function generateResults(results, reportDir, version = exports.RESULTS_SCHEMA_VERSION) {
|
|
73
|
+
const data = buildReportData(results, version);
|
|
74
|
+
fs.writeFileSync(path.join(reportDir, 'results.json'), JSON.stringify(data, null, 2));
|
|
75
|
+
}
|
|
60
76
|
function generateReport(options) {
|
|
61
|
-
const { projectRoot, reportDir: reportDirRelative = 'visual-report', threshold = 0.1, autoOpen = true, version =
|
|
77
|
+
const { projectRoot, reportDir: reportDirRelative = 'visual-report', threshold = 0.1, autoOpen = true, version = exports.RESULTS_SCHEMA_VERSION, } = options;
|
|
62
78
|
const reportDir = path.isAbsolute(reportDirRelative)
|
|
63
79
|
? reportDirRelative
|
|
64
80
|
: path.join(projectRoot, reportDirRelative);
|
|
@@ -78,22 +94,12 @@ function generateReport(options) {
|
|
|
78
94
|
reportDir,
|
|
79
95
|
threshold,
|
|
80
96
|
passCriteria,
|
|
97
|
+
mask: localConfig.mask,
|
|
98
|
+
diffRegions: localConfig.diffRegions,
|
|
81
99
|
};
|
|
82
100
|
const snapshots = (0, compare_1.compareAll)(compareOptions);
|
|
83
|
-
// 2. Build summary
|
|
84
|
-
const
|
|
85
|
-
total: snapshots.length,
|
|
86
|
-
passed: snapshots.filter((s) => s.status === 'passed').length,
|
|
87
|
-
changed: snapshots.filter((s) => s.status === 'changed').length,
|
|
88
|
-
newSnapshots: snapshots.filter((s) => s.status === 'new').length,
|
|
89
|
-
};
|
|
90
|
-
// 3. Build report data
|
|
91
|
-
const reportData = {
|
|
92
|
-
version,
|
|
93
|
-
timestamp: new Date().toISOString(),
|
|
94
|
-
summary,
|
|
95
|
-
snapshots,
|
|
96
|
-
};
|
|
101
|
+
// 2-3. Build summary + report data
|
|
102
|
+
const reportData = buildReportData(snapshots, version);
|
|
97
103
|
// 4. Write results.json
|
|
98
104
|
const resultsPath = path.join(reportDir, 'results.json');
|
|
99
105
|
fs.writeFileSync(resultsPath, JSON.stringify(reportData, null, 2));
|
|
@@ -107,6 +113,21 @@ function generateReport(options) {
|
|
|
107
113
|
}
|
|
108
114
|
return reportData;
|
|
109
115
|
}
|
|
116
|
+
/** Assemble the ReportData envelope for a set of snapshot results. */
|
|
117
|
+
function buildReportData(snapshots, version) {
|
|
118
|
+
const summary = {
|
|
119
|
+
total: snapshots.length,
|
|
120
|
+
passed: snapshots.filter((s) => s.status === 'passed').length,
|
|
121
|
+
changed: snapshots.filter((s) => s.status === 'changed').length,
|
|
122
|
+
newSnapshots: snapshots.filter((s) => s.status === 'new').length,
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
version,
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
summary,
|
|
128
|
+
snapshots,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
110
131
|
/**
|
|
111
132
|
* Open a file in the default browser.
|
|
112
133
|
* Best-effort — does not throw on failure.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":"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"}
|
package/dist/report/results.d.ts
CHANGED
|
@@ -15,11 +15,24 @@ export interface SnapshotDomSignal {
|
|
|
15
15
|
textChanges?: number;
|
|
16
16
|
} | null;
|
|
17
17
|
/**
|
|
18
|
-
* Noise hint: pixel diff is non-zero but DOM is structurally unchanged
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Noise hint: pixel diff is non-zero but DOM is structurally unchanged
|
|
19
|
+
* AND (when element maps exist) computed-style digests match. Suggests
|
|
20
|
+
* render noise (anti-aliasing, font hinting, sub-pixel layout) rather
|
|
21
|
+
* than a real visual regression.
|
|
21
22
|
*/
|
|
22
23
|
noiseHint: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Style-fingerprint verdict: 'match' (digests equal — the hint is
|
|
26
|
+
* trustworthy), 'mismatch' (styles changed with identical DOM — a REAL
|
|
27
|
+
* change; the noise hint is suppressed), 'unavailable' (no element
|
|
28
|
+
* maps — legacy DOM-only hint, labeled).
|
|
29
|
+
*/
|
|
30
|
+
styleCheck?: 'match' | 'mismatch' | 'unavailable';
|
|
31
|
+
/** Present on mismatch: how many elements changed styles, and which. */
|
|
32
|
+
styleChanges?: {
|
|
33
|
+
count: number;
|
|
34
|
+
elements: string[];
|
|
35
|
+
};
|
|
23
36
|
}
|
|
24
37
|
export interface SnapshotResult {
|
|
25
38
|
name: string;
|
|
@@ -41,6 +54,24 @@ export interface SnapshotResult {
|
|
|
41
54
|
* captured DOM HTML alongside the screenshot.
|
|
42
55
|
*/
|
|
43
56
|
dom?: SnapshotDomSignal;
|
|
57
|
+
/**
|
|
58
|
+
* Changed-pixel clusters as bounding boxes (top-left sorted). Present
|
|
59
|
+
* for 'changed' results (and auto-passed ones — the boxes stay useful).
|
|
60
|
+
*/
|
|
61
|
+
regions?: import('../diff/attribution').AttributedRegion[];
|
|
62
|
+
/**
|
|
63
|
+
* Masks applied to this comparison (resolved to pixels, with their
|
|
64
|
+
* source) — the audit trail for excluded areas. Present when non-empty.
|
|
65
|
+
*/
|
|
66
|
+
masks?: import('../diff/mask').MaskRect[];
|
|
67
|
+
/** Mask specs that could not be applied (e.g. selector without captured geometry). */
|
|
68
|
+
maskWarnings?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Whole-page uniform displacement (the injected-banner signature):
|
|
71
|
+
* everything at or below `belowY` moved by `dy` px. Derived from the
|
|
72
|
+
* element maps — present only when both captures carried one.
|
|
73
|
+
*/
|
|
74
|
+
pageShift?: import('../diff/attribution').PageShift;
|
|
44
75
|
/**
|
|
45
76
|
* Present when the snapshot's pixels differed but a pass criterion
|
|
46
77
|
* reported it as passed: 'threshold' (within maxDiffPercent /
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../src/report/results.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnG
|
|
1
|
+
{"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../src/report/results.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnG;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;IAClD,wEAAwE;IACxE,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,gBAAgB,EAAE,CAAC;IAC3D;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC1C,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,qBAAqB,EAAE,SAAS,CAAC;IACpD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAkB,MAAM,WAAW,CAAC;AAEvD;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAkB,MAAM,WAAW,CAAC;AAEvD;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAoOnD"}
|
package/dist/report/template.js
CHANGED
|
@@ -78,6 +78,8 @@ function renderHtml(data) {
|
|
|
78
78
|
.dom-hint { display: flex; align-items: center; gap: 8px; margin-top: 8px; padding: 10px 12px; border-radius: 8px; font-size: 12px; line-height: 1.5; }
|
|
79
79
|
.dom-hint.noise { background: #1f6feb1a; border: 1px solid #1f6feb44; color: #79c0ff; }
|
|
80
80
|
.dom-hint.changed { background: #6e768166; border: 1px solid #8b949e44; color: #c9d1d9; }
|
|
81
|
+
.dom-hint.style-changed { background: #f0883e1a; border: 1px solid #f0883e44; color: #f0883e; }
|
|
82
|
+
.dom-hint.style-changed code { background: #0d1117; border-radius: 3px; padding: 1px 4px; font-size: 11px; }
|
|
81
83
|
.dom-hint .label { font-weight: 600; }
|
|
82
84
|
.dom-hint .summary { color: inherit; opacity: 0.85; }
|
|
83
85
|
|
|
@@ -97,6 +99,22 @@ function renderHtml(data) {
|
|
|
97
99
|
.approve-cmd button:hover { background: #30363d; }
|
|
98
100
|
.approve-cmd button.copied { background: #3fb950; color: #0d1117; }
|
|
99
101
|
|
|
102
|
+
/* Regions */
|
|
103
|
+
.regions { margin-top: 8px; padding: 10px 12px; background: #0d1117; border: 1px solid #21262d; border-radius: 8px; font-size: 12px; }
|
|
104
|
+
.regions .regions-title { color: #8b949e; font-weight: 600; margin-bottom: 6px; }
|
|
105
|
+
.regions ul { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
106
|
+
.regions li a { display: inline-block; padding: 3px 10px; border-radius: 10px; background: #f8514922; color: #f85149; text-decoration: none; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; }
|
|
107
|
+
.regions li a:hover { background: #f8514944; }
|
|
108
|
+
.regions li a.region-shift { background: #1f6feb22; color: #79c0ff; }
|
|
109
|
+
.regions li a.region-shift:hover { background: #1f6feb44; }
|
|
110
|
+
.page-shift { margin-top: 8px; padding: 8px 10px; background: #1f6feb1a; border: 1px solid #1f6feb44; border-radius: 6px; font-size: 12px; color: #79c0ff; }
|
|
111
|
+
|
|
112
|
+
/* Masks (audit trail) */
|
|
113
|
+
.masks { margin-top: 8px; padding: 10px 12px; background: repeating-linear-gradient(45deg, #161b22, #161b22 6px, #1c2129 6px, #1c2129 8px); border: 1px solid #30363d; border-radius: 8px; font-size: 12px; color: #8b949e; }
|
|
114
|
+
.masks .label { font-weight: 600; color: #c9d1d9; }
|
|
115
|
+
.masks code { color: #79c0ff; font-size: 11px; }
|
|
116
|
+
.mask-warning { margin-top: 8px; padding: 10px 12px; background: #f0883e1a; border: 1px solid #f0883e44; border-radius: 8px; font-size: 12px; color: #f0883e; }
|
|
117
|
+
|
|
100
118
|
/* Zoom overlay */
|
|
101
119
|
.zoom-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 100; justify-content: center; align-items: center; cursor: zoom-out; }
|
|
102
120
|
.zoom-overlay.active { display: flex; }
|
|
@@ -192,6 +210,17 @@ function renderHtml(data) {
|
|
|
192
210
|
document.getElementById('zoomOverlay').classList.remove('active');
|
|
193
211
|
});
|
|
194
212
|
|
|
213
|
+
// Region chips zoom the diff image for inspection
|
|
214
|
+
document.querySelectorAll('.region-chip').forEach(chip => {
|
|
215
|
+
chip.addEventListener('click', (e) => {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
const diff = chip.dataset.diff;
|
|
218
|
+
if (!diff) return;
|
|
219
|
+
document.getElementById('zoomImage').src = diff;
|
|
220
|
+
document.getElementById('zoomOverlay').classList.add('active');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
195
224
|
// Copy approve command
|
|
196
225
|
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
197
226
|
btn.addEventListener('click', () => {
|
|
@@ -230,6 +259,8 @@ function renderSnapshot(snapshot) {
|
|
|
230
259
|
</div>
|
|
231
260
|
${snapshot.status === 'changed' || snapshot.autoPassed ? `<div class="snapshot-stats">Diff: ${snapshot.diffPercent.toFixed(2)}% (${snapshot.diffCount} pixels)${snapshot.autoPassed ? ` — auto-passed: ${snapshot.autoPassed === 'noise' ? 'DOM unchanged, within noise tolerance' : 'within configured diff tolerance'}` : ''}</div>` : ''}
|
|
232
261
|
${renderDomHint(snapshot)}
|
|
262
|
+
${renderRegions(snapshot)}
|
|
263
|
+
${renderMasks(snapshot)}
|
|
233
264
|
<div class="${gridClass}">
|
|
234
265
|
${hasBaseline ? `<div class="diff-col"><label>Baseline</label><img src="${snapshot.baselinePath}" alt="Baseline"></div>` : ''}
|
|
235
266
|
${hasDiff ? `<div class="diff-col"><label>Diff</label><img src="${snapshot.diffPath}" alt="Diff"></div>` : ''}
|
|
@@ -257,11 +288,30 @@ function renderSnapshot(snapshot) {
|
|
|
257
288
|
function renderDomHint(snapshot) {
|
|
258
289
|
if (!snapshot.dom)
|
|
259
290
|
return '';
|
|
291
|
+
// Style fingerprint verdict: identical DOM but changed computed styles
|
|
292
|
+
// is a REAL change (the stylesheet-only case) — never noise.
|
|
293
|
+
if (snapshot.dom.styleCheck === 'mismatch' && snapshot.dom.styleChanges) {
|
|
294
|
+
const els = snapshot.dom.styleChanges.elements
|
|
295
|
+
.slice(0, 3)
|
|
296
|
+
.map((p) => `<code>${escapeHtml(p.split(' > ').slice(-1)[0])}</code>`)
|
|
297
|
+
.join(', ');
|
|
298
|
+
const n = snapshot.dom.styleChanges.count;
|
|
299
|
+
return `
|
|
300
|
+
<div class="dom-hint style-changed" title="DOM structure is identical but computed styles differ — a stylesheet-level change, not render noise.">
|
|
301
|
+
<span class="label">Styles changed</span>
|
|
302
|
+
<span class="summary">— ${n} element${n === 1 ? '' : 's'} restyled with identical DOM: ${els}${n > 3 ? ', …' : ''}.</span>
|
|
303
|
+
</div>`;
|
|
304
|
+
}
|
|
260
305
|
if (snapshot.dom.noiseHint) {
|
|
306
|
+
const styleNote = snapshot.dom.styleCheck === 'match'
|
|
307
|
+
? ' Styles verified unchanged.'
|
|
308
|
+
: snapshot.dom.styleCheck === 'unavailable'
|
|
309
|
+
? ' (Style check unavailable — no element maps on both sides.)'
|
|
310
|
+
: '';
|
|
261
311
|
return `
|
|
262
312
|
<div class="dom-hint noise" title="DOM tree is identical between baseline and candidate; the pixel diff is likely render noise.">
|
|
263
313
|
<span class="label">DOM unchanged</span>
|
|
264
|
-
<span class="summary">— pixel diff is likely render noise (anti-aliasing, font hinting)
|
|
314
|
+
<span class="summary">— pixel diff is likely render noise (anti-aliasing, font hinting).${styleNote}</span>
|
|
265
315
|
</div>`;
|
|
266
316
|
}
|
|
267
317
|
const s = snapshot.dom.summary;
|
|
@@ -282,6 +332,77 @@ function renderDomHint(snapshot) {
|
|
|
282
332
|
<span class="summary">— ${escapeHtml(parts.join(', ') || 'structural difference')}.</span>
|
|
283
333
|
</div>`;
|
|
284
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Region chip list: "N changed regions" with coordinates. Clicking a chip
|
|
337
|
+
* zooms the diff image so the reviewer can inspect that area.
|
|
338
|
+
*/
|
|
339
|
+
function renderRegions(snapshot) {
|
|
340
|
+
const regions = snapshot.regions ?? [];
|
|
341
|
+
if (regions.length === 0 || snapshot.status === 'new')
|
|
342
|
+
return '';
|
|
343
|
+
const describe = (r) => {
|
|
344
|
+
const el = r.elements?.[0];
|
|
345
|
+
if (!el)
|
|
346
|
+
return `${r.width}×${r.height} @ (${r.x}, ${r.y})`;
|
|
347
|
+
const selector = escapeHtml(el.selector.split(' > ').slice(-1)[0]);
|
|
348
|
+
if (r.classification === 'shift' && r.shift) {
|
|
349
|
+
const parts = [];
|
|
350
|
+
if (r.shift.dy !== 0)
|
|
351
|
+
parts.push(`${r.shift.dy > 0 ? '+' : ''}${r.shift.dy}px vertically`);
|
|
352
|
+
if (r.shift.dx !== 0)
|
|
353
|
+
parts.push(`${r.shift.dx > 0 ? '+' : ''}${r.shift.dx}px horizontally`);
|
|
354
|
+
return `${selector} shifted ${parts.join(', ')} — content unchanged`;
|
|
355
|
+
}
|
|
356
|
+
return `${selector} changed (${r.width}×${r.height} @ ${r.x}, ${r.y})`;
|
|
357
|
+
};
|
|
358
|
+
const chips = regions
|
|
359
|
+
.map((r, i) => `<li><a href="#" class="region-chip${r.classification === 'shift' ? ' region-shift' : ''}" data-diff="${snapshot.diffPath ?? ''}" title="${r.diffPixels} changed pixels${r.elements?.[0] ? ' — ' + escapeHtml(r.elements[0].selector) : ''}">#${i + 1} · ${describe(r)}</a></li>`)
|
|
360
|
+
.join('');
|
|
361
|
+
const shifted = regions.filter((r) => r.classification === 'shift').length;
|
|
362
|
+
const title = shifted > 0
|
|
363
|
+
? `${regions.length} changed region${regions.length === 1 ? '' : 's'} (${shifted} shifted)`
|
|
364
|
+
: `${regions.length} changed region${regions.length === 1 ? '' : 's'}`;
|
|
365
|
+
const pageShift = snapshot.pageShift
|
|
366
|
+
? `<div class="page-shift">↕ Everything below y=${snapshot.pageShift.belowY} shifted ${snapshot.pageShift.dy > 0 ? '+' : ''}${snapshot.pageShift.dy}px (${snapshot.pageShift.count} elements moved together — typically an inserted banner or expanded section above).</div>`
|
|
367
|
+
: '';
|
|
368
|
+
return `
|
|
369
|
+
<div class="regions">
|
|
370
|
+
<div class="regions-title">${title}</div>
|
|
371
|
+
<ul>${chips}</ul>
|
|
372
|
+
${pageShift}
|
|
373
|
+
</div>`;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Mask audit trail: every mask applied to this comparison is listed with
|
|
377
|
+
* its source — masking is visible in review, never silent. Warnings cover
|
|
378
|
+
* masks that could not be applied (e.g. selector without captured rects).
|
|
379
|
+
*/
|
|
380
|
+
function renderMasks(snapshot) {
|
|
381
|
+
const masks = snapshot.masks ?? [];
|
|
382
|
+
const warnings = snapshot.maskWarnings ?? [];
|
|
383
|
+
if (masks.length === 0 && warnings.length === 0)
|
|
384
|
+
return '';
|
|
385
|
+
const describe = (m) => {
|
|
386
|
+
const src = m.source.type === 'selector'
|
|
387
|
+
? `selector <code>${escapeHtml(String(m.source.spec))}</code>`
|
|
388
|
+
: m.source.type === 'edge'
|
|
389
|
+
? `edge <code>${escapeHtml(JSON.stringify(m.source.spec))}</code>`
|
|
390
|
+
: `region <code>${escapeHtml(JSON.stringify(m.source.spec))}</code>`;
|
|
391
|
+
return `${src} → ${m.width}×${m.height} @ (${m.x}, ${m.y}) <em>(${m.source.origin})</em>`;
|
|
392
|
+
};
|
|
393
|
+
const maskBlock = masks.length > 0
|
|
394
|
+
? `
|
|
395
|
+
<div class="masks" title="Masked areas are excluded from the pixel diff and hatched in the diff image.">
|
|
396
|
+
<span class="label">🙈 ${masks.length} mask${masks.length === 1 ? '' : 's'} applied</span>
|
|
397
|
+
— ${masks.map(describe).join('; ')}
|
|
398
|
+
</div>`
|
|
399
|
+
: '';
|
|
400
|
+
const warningBlock = warnings.length > 0
|
|
401
|
+
? `
|
|
402
|
+
<div class="mask-warning">⚠️ ${warnings.map((w) => escapeHtml(w)).join('<br>')}</div>`
|
|
403
|
+
: '';
|
|
404
|
+
return maskBlock + warningBlock;
|
|
405
|
+
}
|
|
285
406
|
function escapeHtml(str) {
|
|
286
407
|
return str
|
|
287
408
|
.replace(/&/g, '&')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAOH,
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":"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"}
|