@testivai/witness 1.0.1 → 1.0.3
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/README.md +44 -13
- package/dist/baselines/index.d.ts +6 -0
- package/dist/baselines/index.d.ts.map +1 -0
- package/dist/baselines/index.js +9 -0
- package/dist/baselines/index.js.map +1 -0
- package/dist/baselines/store.d.ts +100 -0
- package/dist/baselines/store.d.ts.map +1 -0
- package/dist/baselines/store.js +298 -0
- package/dist/baselines/store.js.map +1 -0
- package/dist/bin/testivai.js +2 -0
- package/dist/bin/testivai.js.map +1 -1
- package/dist/browser/binding.d.ts.map +1 -1
- package/dist/browser/binding.js +173 -42
- package/dist/browser/binding.js.map +1 -1
- package/dist/browser/capture.d.ts.map +1 -1
- package/dist/browser/capture.js +7 -4
- package/dist/browser/capture.js.map +1 -1
- package/dist/commands/approve.d.ts +3 -0
- package/dist/commands/approve.d.ts.map +1 -0
- package/dist/commands/approve.js +94 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +53 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +98 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +14 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/local-config.d.ts +54 -0
- package/dist/config/local-config.d.ts.map +1 -0
- package/dist/config/local-config.js +130 -0
- package/dist/config/local-config.js.map +1 -0
- package/dist/diff/diff.d.ts +27 -0
- package/dist/diff/diff.d.ts.map +1 -0
- package/dist/diff/diff.js +216 -0
- package/dist/diff/diff.js.map +1 -0
- package/dist/diff/dom-diff.d.ts +85 -0
- package/dist/diff/dom-diff.d.ts.map +1 -0
- package/dist/diff/dom-diff.js +291 -0
- package/dist/diff/dom-diff.js.map +1 -0
- package/dist/diff/ignore.d.ts +22 -0
- package/dist/diff/ignore.d.ts.map +1 -0
- package/dist/diff/ignore.js +43 -0
- package/dist/diff/ignore.js.map +1 -0
- package/dist/diff/index.d.ts +18 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +25 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/diff/minimap.d.ts +23 -0
- package/dist/diff/minimap.d.ts.map +1 -0
- package/dist/diff/minimap.js +43 -0
- package/dist/diff/minimap.js.map +1 -0
- package/dist/diff/regions.d.ts +23 -0
- package/dist/diff/regions.d.ts.map +1 -0
- package/dist/diff/regions.js +129 -0
- package/dist/diff/regions.js.map +1 -0
- package/dist/diff/resize.d.ts +27 -0
- package/dist/diff/resize.d.ts.map +1 -0
- package/dist/diff/resize.js +82 -0
- package/dist/diff/resize.js.map +1 -0
- package/dist/diff/types.d.ts +79 -0
- package/dist/diff/types.d.ts.map +1 -0
- package/dist/diff/types.js +10 -0
- package/dist/diff/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -1
- package/dist/report/compare.d.ts +18 -0
- package/dist/report/compare.d.ts.map +1 -0
- package/dist/report/compare.js +193 -0
- package/dist/report/compare.js.map +1 -0
- package/dist/report/generator.d.ts +31 -0
- package/dist/report/generator.d.ts.map +1 -0
- package/dist/report/generator.js +123 -0
- package/dist/report/generator.js.map +1 -0
- package/dist/report/index.d.ts +10 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +13 -0
- package/dist/report/index.js.map +1 -0
- package/dist/report/results.d.ts +56 -0
- package/dist/report/results.d.ts.map +1 -0
- package/dist/report/results.js +8 -0
- package/dist/report/results.js.map +1 -0
- package/dist/report/template.d.ts +14 -0
- package/dist/report/template.d.ts.map +1 -0
- package/dist/report/template.js +275 -0
- package/dist/report/template.js.map +1 -0
- package/package.json +58 -15
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TestivAI Diff Engine — Core comparison loop
|
|
4
|
+
*
|
|
5
|
+
* Originally forked from pixel-buffer-diff (Pbd) by @p01, licensed under MIT.
|
|
6
|
+
* https://github.com/p01/pixel-buffer-diff
|
|
7
|
+
*
|
|
8
|
+
* The core pixel comparison algorithm is preserved exactly.
|
|
9
|
+
* Extended with: ignore regions, size mismatch handling, region detection,
|
|
10
|
+
* and enriched DiffResult.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.diff = diff;
|
|
14
|
+
const ignore_1 = require("./ignore");
|
|
15
|
+
const resize_1 = require("./resize");
|
|
16
|
+
const regions_1 = require("./regions");
|
|
17
|
+
const minimap_1 = require("./minimap");
|
|
18
|
+
// ── Pbd constants (unchanged) ───────────────────────────────────────────────
|
|
19
|
+
const MINIMAP_SCALE = 128;
|
|
20
|
+
const COLOR32_ADDED = 0x03f00cc00;
|
|
21
|
+
const COLOR32_REMOVED = 0x03f0000ff;
|
|
22
|
+
const COLOR32_MINIMAP = 0x0207f0000;
|
|
23
|
+
const HASH_SPREAD = 0x0f0731337;
|
|
24
|
+
// ── Default options ─────────────────────────────────────────────────────────
|
|
25
|
+
const DEFAULT_OPTIONS = {
|
|
26
|
+
threshold: 0.1,
|
|
27
|
+
cumulatedThreshold: 0.5,
|
|
28
|
+
enableMinimap: false,
|
|
29
|
+
detectRegions: false,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Compare two pixel buffers and produce a diff image.
|
|
33
|
+
*
|
|
34
|
+
* Drop-in compatible with pixelmatch when called as:
|
|
35
|
+
* diff(img1, img2, output, w, h) → returns DiffResult
|
|
36
|
+
*
|
|
37
|
+
* @param baseline8 - Baseline image pixel data (RGBA, 4 bytes per pixel)
|
|
38
|
+
* @param candidate8 - Candidate image pixel data (RGBA, 4 bytes per pixel)
|
|
39
|
+
* @param diff8 - Output buffer for the diff image (same size as inputs)
|
|
40
|
+
* @param width - Width of the images in pixels
|
|
41
|
+
* @param height - Height of the images in pixels
|
|
42
|
+
* @param options - Comparison options
|
|
43
|
+
* @returns DiffResult with counts, percentages, and optional regions
|
|
44
|
+
*/
|
|
45
|
+
function diff(baseline8, candidate8, diff8, width, height, options = {}) {
|
|
46
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
47
|
+
// ── Size mismatch handling ──────────────────────────────────────────────
|
|
48
|
+
let sizeMismatch;
|
|
49
|
+
let effectiveWidth = width;
|
|
50
|
+
let effectiveHeight = height;
|
|
51
|
+
let effectiveBaseline = baseline8;
|
|
52
|
+
let effectiveCandidate = candidate8;
|
|
53
|
+
// If caller provided handleSizeMismatch and dimensions were embedded,
|
|
54
|
+
// we need external width/height per buffer. For the simple API the caller
|
|
55
|
+
// is expected to have already normalized — but we expose normalizeDimensions
|
|
56
|
+
// as a public utility. Here we just validate.
|
|
57
|
+
const expectedLen = width * height * 4;
|
|
58
|
+
if (baseline8.length !== expectedLen || candidate8.length !== expectedLen) {
|
|
59
|
+
if (opts.handleSizeMismatch) {
|
|
60
|
+
// Infer baseline dimensions from baseline8.length and supplied width
|
|
61
|
+
const baselineHeight = baseline8.length / (width * 4);
|
|
62
|
+
const candidateHeight = candidate8.length / (width * 4);
|
|
63
|
+
// If widths differ, we need both dimensions — use normalizeDimensions
|
|
64
|
+
const bw = width;
|
|
65
|
+
const bh = Math.round(baselineHeight);
|
|
66
|
+
const cw = width;
|
|
67
|
+
const ch = Math.round(candidateHeight);
|
|
68
|
+
const normalized = (0, resize_1.normalizeDimensions)(baseline8, bw, bh, candidate8, cw, ch, opts.handleSizeMismatch);
|
|
69
|
+
effectiveBaseline = normalized.normalizedBaseline;
|
|
70
|
+
effectiveCandidate = normalized.normalizedCandidate;
|
|
71
|
+
effectiveWidth = normalized.width;
|
|
72
|
+
effectiveHeight = normalized.height;
|
|
73
|
+
sizeMismatch = normalized.sizeMismatch;
|
|
74
|
+
// Re-create diff buffer if size changed
|
|
75
|
+
if (diff8.length !== effectiveWidth * effectiveHeight * 4) {
|
|
76
|
+
diff8 = new Uint8ClampedArray(effectiveWidth * effectiveHeight * 4);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw new Error(`Buffer size mismatch: expected ${expectedLen} bytes (${width}×${height}×4), ` +
|
|
81
|
+
`got baseline=${baseline8.length}, candidate=${candidate8.length}. ` +
|
|
82
|
+
`Set handleSizeMismatch option to 'pad', 'crop', or 'error'.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ── Ignore regions ──────────────────────────────────────────────────────
|
|
86
|
+
if (opts.ignoreRegions && opts.ignoreRegions.length > 0) {
|
|
87
|
+
// Clone buffers so we don't mutate originals
|
|
88
|
+
effectiveBaseline = new Uint8ClampedArray(effectiveBaseline);
|
|
89
|
+
effectiveCandidate = new Uint8ClampedArray(effectiveCandidate);
|
|
90
|
+
(0, ignore_1.applyIgnoreRegions)(effectiveBaseline, effectiveCandidate, effectiveWidth, effectiveHeight, opts.ignoreRegions);
|
|
91
|
+
}
|
|
92
|
+
// ── Core Pbd diff loop (basic mode — pixel-level YIQ comparison) ──────
|
|
93
|
+
const raw = pbdDiffCore(effectiveBaseline, effectiveCandidate, diff8, effectiveWidth, effectiveHeight, opts.threshold, opts.cumulatedThreshold, opts.enableMinimap);
|
|
94
|
+
// ── Compute extended result ─────────────────────────────────────────────
|
|
95
|
+
const totalPixels = effectiveWidth * effectiveHeight;
|
|
96
|
+
const diffPercent = totalPixels > 0 ? (raw.diff / totalPixels) * 100 : 0;
|
|
97
|
+
const isIdentical = raw.diff === 0;
|
|
98
|
+
const result = {
|
|
99
|
+
diffCount: raw.diff,
|
|
100
|
+
cumulatedDiff: raw.cumulatedDiff,
|
|
101
|
+
hash: raw.hash,
|
|
102
|
+
diffPercent,
|
|
103
|
+
totalPixels,
|
|
104
|
+
isIdentical,
|
|
105
|
+
};
|
|
106
|
+
if (sizeMismatch) {
|
|
107
|
+
result.sizeMismatch = sizeMismatch;
|
|
108
|
+
}
|
|
109
|
+
// ── Region detection ────────────────────────────────────────────────────
|
|
110
|
+
if (opts.detectRegions && !isIdentical) {
|
|
111
|
+
result.regions = (0, regions_1.detectRegions)(diff8, effectiveWidth, effectiveHeight);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Core Pbd diff loop — pixel-level YIQ comparison.
|
|
117
|
+
* Algorithm is preserved exactly from pixel-buffer-diff.
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
function pbdDiffCore(baseline8, candidate8, diff8, width, height, threshold, cumulatedThreshold, enableMinimap) {
|
|
121
|
+
const area = width * height;
|
|
122
|
+
const b8l = baseline8.length;
|
|
123
|
+
// Maximum acceptable square distance between two colors;
|
|
124
|
+
// 35215 is the maximum possible value for the YIQ difference metric
|
|
125
|
+
const deltaThreshold = threshold * threshold * 35215;
|
|
126
|
+
// Use ArrayBuffer views for 32-bit access
|
|
127
|
+
const bBuffer = baseline8.buffer;
|
|
128
|
+
const bOffset = baseline8.byteOffset;
|
|
129
|
+
const cBuffer = candidate8.buffer;
|
|
130
|
+
const cOffset = candidate8.byteOffset;
|
|
131
|
+
const dBuffer = diff8.buffer;
|
|
132
|
+
const dOffset = diff8.byteOffset;
|
|
133
|
+
const baseline32 = new Uint32Array(bBuffer, bOffset, area);
|
|
134
|
+
const candidate32 = new Uint32Array(cBuffer, cOffset, area);
|
|
135
|
+
const diff32 = new Uint32Array(dBuffer, dOffset, area);
|
|
136
|
+
let b8i = 0;
|
|
137
|
+
let b32i = 0;
|
|
138
|
+
let d32i = 0;
|
|
139
|
+
let diffCount = 0;
|
|
140
|
+
let hash = 0;
|
|
141
|
+
let hashStart = 0;
|
|
142
|
+
let cumulatedDiff = 0;
|
|
143
|
+
// Quick approx of color theme to figure if "new" pixels should be dark or light
|
|
144
|
+
let averageBrightness = 0;
|
|
145
|
+
const brightnessSamples = Math.ceil(Math.sqrt(area) / 128);
|
|
146
|
+
const b8iStep = (b8l / brightnessSamples) & -4;
|
|
147
|
+
let sampleIdx = 0;
|
|
148
|
+
for (let i = 0; i < brightnessSamples; i++) {
|
|
149
|
+
averageBrightness +=
|
|
150
|
+
(0.299 * (baseline8[sampleIdx] + candidate8[sampleIdx]) +
|
|
151
|
+
0.587 * (baseline8[sampleIdx + 1] + candidate8[sampleIdx + 1]) +
|
|
152
|
+
0.114 * (baseline8[sampleIdx + 2] + candidate8[sampleIdx + 2])) /
|
|
153
|
+
brightnessSamples /
|
|
154
|
+
2;
|
|
155
|
+
sampleIdx += b8iStep;
|
|
156
|
+
}
|
|
157
|
+
const isDarkTheme = averageBrightness < 128;
|
|
158
|
+
const color32Added = isDarkTheme ? COLOR32_ADDED : COLOR32_REMOVED;
|
|
159
|
+
const color32Removed = isDarkTheme ? COLOR32_REMOVED : COLOR32_ADDED;
|
|
160
|
+
// Minimap tracking
|
|
161
|
+
const miniHeight = Math.ceil(height / MINIMAP_SCALE);
|
|
162
|
+
const miniWidth = Math.ceil(width / MINIMAP_SCALE);
|
|
163
|
+
const miniMap = new Uint8ClampedArray(miniWidth * miniHeight);
|
|
164
|
+
const maxDimension = Math.max(width, height);
|
|
165
|
+
const maxMiniDimension = Math.max(miniWidth, miniHeight);
|
|
166
|
+
const axisMiniIndex = new Uint32Array(maxDimension);
|
|
167
|
+
let miniIndex = 0;
|
|
168
|
+
for (let i = 0; i < maxMiniDimension; i++) {
|
|
169
|
+
axisMiniIndex.fill(i, miniIndex, Math.min(miniIndex + MINIMAP_SCALE, maxDimension));
|
|
170
|
+
miniIndex += MINIMAP_SCALE;
|
|
171
|
+
}
|
|
172
|
+
// ── Per-pixel comparison ────────────────────────────────────────────────
|
|
173
|
+
for (let y = 0; y < height; y++) {
|
|
174
|
+
const miniIndexY = axisMiniIndex[y] * miniWidth;
|
|
175
|
+
let hashIndex = (y ^ HASH_SPREAD) * HASH_SPREAD;
|
|
176
|
+
for (let x = 0; x < width; x++, d32i++, b32i++, b8i += 4, hashIndex++) {
|
|
177
|
+
// Quick check against the Uint32
|
|
178
|
+
if (baseline32[b32i] === candidate32[b32i]) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Get the r,g,b -> y,i,q => YIQ square delta
|
|
182
|
+
const dr = candidate8[b8i] - baseline8[b8i];
|
|
183
|
+
const dg = candidate8[b8i + 1] - baseline8[b8i + 1];
|
|
184
|
+
const db = candidate8[b8i + 2] - baseline8[b8i + 2];
|
|
185
|
+
const dy = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
|
|
186
|
+
const di = dr * 0.59597799 - dg * 0.27417610 - db * 0.32180189;
|
|
187
|
+
const dq = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;
|
|
188
|
+
const delta = dy * dy * 0.5053 + di * di * 0.299 + dq * dq * 0.1957;
|
|
189
|
+
if (delta > deltaThreshold) {
|
|
190
|
+
miniMap[miniIndexY + axisMiniIndex[x]]++;
|
|
191
|
+
diffCount++;
|
|
192
|
+
const dyAbs = Math.abs(dy);
|
|
193
|
+
cumulatedDiff += dyAbs;
|
|
194
|
+
diff32[d32i] =
|
|
195
|
+
(dy > 0 ? color32Added : color32Removed) +
|
|
196
|
+
(Math.min(192, dyAbs * 8) << 24);
|
|
197
|
+
if (hash === 0) {
|
|
198
|
+
hashStart = hashIndex;
|
|
199
|
+
}
|
|
200
|
+
hash += hashIndex;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
hash -= hashStart;
|
|
205
|
+
cumulatedDiff /= 256;
|
|
206
|
+
// Apply minimap overlay
|
|
207
|
+
if (enableMinimap) {
|
|
208
|
+
(0, minimap_1.applyMinimap)(diff32, miniMap, miniWidth, miniHeight, width, height, MINIMAP_SCALE, COLOR32_MINIMAP);
|
|
209
|
+
}
|
|
210
|
+
// If cumulated diff is within threshold, treat as identical
|
|
211
|
+
if (cumulatedDiff <= cumulatedThreshold) {
|
|
212
|
+
return { diff: 0, cumulatedDiff: 0, hash: 0 };
|
|
213
|
+
}
|
|
214
|
+
return { diff: diffCount, cumulatedDiff, hash };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestivAI DOM Diff — lightweight noise-hint comparison
|
|
3
|
+
*
|
|
4
|
+
* Compares two HTML strings (typically `document.documentElement.outerHTML`
|
|
5
|
+
* captured at snapshot time) and returns a small structural-change summary.
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally NOT a full DOM tree differ — those bring 30 KB+ of
|
|
8
|
+
* dependencies and complex semantics we don't need. The job here is to
|
|
9
|
+
* answer one question per snapshot:
|
|
10
|
+
*
|
|
11
|
+
* "When pixels differ, is the underlying DOM also different?"
|
|
12
|
+
*
|
|
13
|
+
* If pixels differ but DOM is unchanged, the difference is almost certainly
|
|
14
|
+
* render noise (font rendering, anti-aliasing, sub-pixel layout). The HTML
|
|
15
|
+
* report uses this as a noise hint.
|
|
16
|
+
*
|
|
17
|
+
* Approach (zero-dep, ~120 LOC of tokenizer):
|
|
18
|
+
* 1. Stream-tokenize each HTML into a flat list of open events
|
|
19
|
+
* (`{ tag, attrs: Record<string, string> }`).
|
|
20
|
+
* 2. Compare via multiset operations:
|
|
21
|
+
* - tags only in candidate → added
|
|
22
|
+
* - tags only in baseline → removed
|
|
23
|
+
* - same tag pairs that differ in attributes → attributeChanges
|
|
24
|
+
*
|
|
25
|
+
* Tradeoffs:
|
|
26
|
+
* - We treat element ORDER as soft. Two pages with the same elements in
|
|
27
|
+
* a different order register as zero diff in tag counts but may show up
|
|
28
|
+
* as attribute changes if reordering changes attributes (uncommon).
|
|
29
|
+
* This is acceptable because the SAME page captured twice should
|
|
30
|
+
* normally produce identical token lists.
|
|
31
|
+
* - Text content is ignored. Visual regression on text wording is what
|
|
32
|
+
* pixels are for — we don't want to flag every dynamic timestamp.
|
|
33
|
+
* - Comments and processing instructions are ignored.
|
|
34
|
+
*
|
|
35
|
+
* Self-closing void elements per HTML spec are handled.
|
|
36
|
+
*/
|
|
37
|
+
/** Result of a DOM diff comparison. */
|
|
38
|
+
export interface DomDiffResult {
|
|
39
|
+
/** True if the two HTML strings are structurally different. */
|
|
40
|
+
domChanged: boolean;
|
|
41
|
+
/** Per-bucket counts; null when domChanged is false. */
|
|
42
|
+
summary: DomDiffSummary | null;
|
|
43
|
+
}
|
|
44
|
+
export interface DomDiffSummary {
|
|
45
|
+
/** Tag occurrences present in candidate but not baseline. */
|
|
46
|
+
added: number;
|
|
47
|
+
/** Tag occurrences present in baseline but not candidate. */
|
|
48
|
+
removed: number;
|
|
49
|
+
/** Tag pairs present in both but with different attributes. */
|
|
50
|
+
attributeChanges: number;
|
|
51
|
+
}
|
|
52
|
+
interface OpenEvent {
|
|
53
|
+
tag: string;
|
|
54
|
+
/** Attributes serialized as `key=value` pairs, sorted, joined by '\0'. */
|
|
55
|
+
attrSig: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compare two HTML strings.
|
|
59
|
+
*
|
|
60
|
+
* Returns `{ domChanged: false, summary: null }` for fast paths:
|
|
61
|
+
* - Both inputs identical bytes
|
|
62
|
+
* - Both inputs missing/empty (e.g., DOM capture wasn't enabled)
|
|
63
|
+
*
|
|
64
|
+
* Otherwise tokenizes both and produces a summary.
|
|
65
|
+
*/
|
|
66
|
+
export declare function domDiff(baselineHtml: string | null | undefined, candidateHtml: string | null | undefined): DomDiffResult;
|
|
67
|
+
/**
|
|
68
|
+
* Stream-tokenize HTML into open events.
|
|
69
|
+
*
|
|
70
|
+
* Skips: doctype, comments, processing instructions, CDATA, text nodes,
|
|
71
|
+
* script/style content (handled as opaque). Close tags are dropped.
|
|
72
|
+
*
|
|
73
|
+
* Robust against malformed HTML — we never throw; on weird input we
|
|
74
|
+
* just stop emitting events. Worst case = false "no DOM signal".
|
|
75
|
+
*/
|
|
76
|
+
declare function tokenize(html: string): OpenEvent[];
|
|
77
|
+
/**
|
|
78
|
+
* Internal export only used in tests; exposes the tokenizer for white-box
|
|
79
|
+
* verification. Not part of the package public API.
|
|
80
|
+
*/
|
|
81
|
+
export declare const __internals__: {
|
|
82
|
+
tokenize: typeof tokenize;
|
|
83
|
+
};
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=dom-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-diff.d.ts","sourceRoot":"","sources":["../../src/diff/dom-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAOH,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,CAyDxH;AAuBD;;;;;;;;GAQG;AACH,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAwE3C;AAsFD;;;GAGG;AACH,eAAO,MAAM,aAAa;;CAAe,CAAC"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TestivAI DOM Diff — lightweight noise-hint comparison
|
|
4
|
+
*
|
|
5
|
+
* Compares two HTML strings (typically `document.documentElement.outerHTML`
|
|
6
|
+
* captured at snapshot time) and returns a small structural-change summary.
|
|
7
|
+
*
|
|
8
|
+
* This is intentionally NOT a full DOM tree differ — those bring 30 KB+ of
|
|
9
|
+
* dependencies and complex semantics we don't need. The job here is to
|
|
10
|
+
* answer one question per snapshot:
|
|
11
|
+
*
|
|
12
|
+
* "When pixels differ, is the underlying DOM also different?"
|
|
13
|
+
*
|
|
14
|
+
* If pixels differ but DOM is unchanged, the difference is almost certainly
|
|
15
|
+
* render noise (font rendering, anti-aliasing, sub-pixel layout). The HTML
|
|
16
|
+
* report uses this as a noise hint.
|
|
17
|
+
*
|
|
18
|
+
* Approach (zero-dep, ~120 LOC of tokenizer):
|
|
19
|
+
* 1. Stream-tokenize each HTML into a flat list of open events
|
|
20
|
+
* (`{ tag, attrs: Record<string, string> }`).
|
|
21
|
+
* 2. Compare via multiset operations:
|
|
22
|
+
* - tags only in candidate → added
|
|
23
|
+
* - tags only in baseline → removed
|
|
24
|
+
* - same tag pairs that differ in attributes → attributeChanges
|
|
25
|
+
*
|
|
26
|
+
* Tradeoffs:
|
|
27
|
+
* - We treat element ORDER as soft. Two pages with the same elements in
|
|
28
|
+
* a different order register as zero diff in tag counts but may show up
|
|
29
|
+
* as attribute changes if reordering changes attributes (uncommon).
|
|
30
|
+
* This is acceptable because the SAME page captured twice should
|
|
31
|
+
* normally produce identical token lists.
|
|
32
|
+
* - Text content is ignored. Visual regression on text wording is what
|
|
33
|
+
* pixels are for — we don't want to flag every dynamic timestamp.
|
|
34
|
+
* - Comments and processing instructions are ignored.
|
|
35
|
+
*
|
|
36
|
+
* Self-closing void elements per HTML spec are handled.
|
|
37
|
+
*/
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.__internals__ = void 0;
|
|
40
|
+
exports.domDiff = domDiff;
|
|
41
|
+
const VOID_ELEMENTS = new Set([
|
|
42
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
43
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* Compare two HTML strings.
|
|
47
|
+
*
|
|
48
|
+
* Returns `{ domChanged: false, summary: null }` for fast paths:
|
|
49
|
+
* - Both inputs identical bytes
|
|
50
|
+
* - Both inputs missing/empty (e.g., DOM capture wasn't enabled)
|
|
51
|
+
*
|
|
52
|
+
* Otherwise tokenizes both and produces a summary.
|
|
53
|
+
*/
|
|
54
|
+
function domDiff(baselineHtml, candidateHtml) {
|
|
55
|
+
// Fast path: missing data on either side → no signal
|
|
56
|
+
if (!baselineHtml || !candidateHtml) {
|
|
57
|
+
return { domChanged: false, summary: null };
|
|
58
|
+
}
|
|
59
|
+
// Fast path: byte-identical
|
|
60
|
+
if (baselineHtml === candidateHtml) {
|
|
61
|
+
return { domChanged: false, summary: null };
|
|
62
|
+
}
|
|
63
|
+
const baselineEvents = tokenize(baselineHtml);
|
|
64
|
+
const candidateEvents = tokenize(candidateHtml);
|
|
65
|
+
// Bucket by tag name; within each bucket, count attrSig multisets
|
|
66
|
+
const baselineBuckets = bucketize(baselineEvents);
|
|
67
|
+
const candidateBuckets = bucketize(candidateEvents);
|
|
68
|
+
let added = 0;
|
|
69
|
+
let removed = 0;
|
|
70
|
+
let attributeChanges = 0;
|
|
71
|
+
const allTags = new Set([...baselineBuckets.keys(), ...candidateBuckets.keys()]);
|
|
72
|
+
for (const tag of allTags) {
|
|
73
|
+
const baselineForTag = baselineBuckets.get(tag) ?? new Map();
|
|
74
|
+
const candidateForTag = candidateBuckets.get(tag) ?? new Map();
|
|
75
|
+
const baselineCount = sumValues(baselineForTag);
|
|
76
|
+
const candidateCount = sumValues(candidateForTag);
|
|
77
|
+
if (candidateCount > baselineCount) {
|
|
78
|
+
added += candidateCount - baselineCount;
|
|
79
|
+
}
|
|
80
|
+
else if (baselineCount > candidateCount) {
|
|
81
|
+
removed += baselineCount - candidateCount;
|
|
82
|
+
}
|
|
83
|
+
// Attribute changes = matching tag pairs whose attrSig differs.
|
|
84
|
+
// We compute by intersecting the two attrSig multisets and counting
|
|
85
|
+
// anything that doesn't match. Each unmatched-but-paired pair counts
|
|
86
|
+
// once toward attributeChanges; raw added/removed of the tag itself
|
|
87
|
+
// is already counted above, so we subtract that delta.
|
|
88
|
+
const overlap = Math.min(baselineCount, candidateCount);
|
|
89
|
+
let matchingPairs = 0;
|
|
90
|
+
for (const [sig, candCount] of candidateForTag) {
|
|
91
|
+
const baseCount = baselineForTag.get(sig) ?? 0;
|
|
92
|
+
matchingPairs += Math.min(baseCount, candCount);
|
|
93
|
+
}
|
|
94
|
+
attributeChanges += overlap - matchingPairs;
|
|
95
|
+
}
|
|
96
|
+
const domChanged = added > 0 || removed > 0 || attributeChanges > 0;
|
|
97
|
+
return {
|
|
98
|
+
domChanged,
|
|
99
|
+
summary: domChanged ? { added, removed, attributeChanges } : null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ── internals ──────────────────────────────────────────────────────────────
|
|
103
|
+
function bucketize(events) {
|
|
104
|
+
const buckets = new Map();
|
|
105
|
+
for (const ev of events) {
|
|
106
|
+
let inner = buckets.get(ev.tag);
|
|
107
|
+
if (!inner) {
|
|
108
|
+
inner = new Map();
|
|
109
|
+
buckets.set(ev.tag, inner);
|
|
110
|
+
}
|
|
111
|
+
inner.set(ev.attrSig, (inner.get(ev.attrSig) ?? 0) + 1);
|
|
112
|
+
}
|
|
113
|
+
return buckets;
|
|
114
|
+
}
|
|
115
|
+
function sumValues(m) {
|
|
116
|
+
let total = 0;
|
|
117
|
+
for (const v of m.values())
|
|
118
|
+
total += v;
|
|
119
|
+
return total;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stream-tokenize HTML into open events.
|
|
123
|
+
*
|
|
124
|
+
* Skips: doctype, comments, processing instructions, CDATA, text nodes,
|
|
125
|
+
* script/style content (handled as opaque). Close tags are dropped.
|
|
126
|
+
*
|
|
127
|
+
* Robust against malformed HTML — we never throw; on weird input we
|
|
128
|
+
* just stop emitting events. Worst case = false "no DOM signal".
|
|
129
|
+
*/
|
|
130
|
+
function tokenize(html) {
|
|
131
|
+
const events = [];
|
|
132
|
+
const len = html.length;
|
|
133
|
+
let i = 0;
|
|
134
|
+
while (i < len) {
|
|
135
|
+
// Find next '<'
|
|
136
|
+
const lt = html.indexOf('<', i);
|
|
137
|
+
if (lt < 0)
|
|
138
|
+
break;
|
|
139
|
+
i = lt + 1;
|
|
140
|
+
if (i >= len)
|
|
141
|
+
break;
|
|
142
|
+
const c = html[i];
|
|
143
|
+
// Comment <!-- ... -->
|
|
144
|
+
if (c === '!' && html.startsWith('!--', i)) {
|
|
145
|
+
const end = html.indexOf('-->', i + 3);
|
|
146
|
+
if (end < 0)
|
|
147
|
+
break;
|
|
148
|
+
i = end + 3;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Doctype <!doctype ...>
|
|
152
|
+
if (c === '!') {
|
|
153
|
+
const end = html.indexOf('>', i + 1);
|
|
154
|
+
if (end < 0)
|
|
155
|
+
break;
|
|
156
|
+
i = end + 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// CDATA <![CDATA[ ... ]]> (rare in HTML; skip)
|
|
160
|
+
// Already handled by the c === '!' branch above.
|
|
161
|
+
// Processing instruction <? ... ?>
|
|
162
|
+
if (c === '?') {
|
|
163
|
+
const end = html.indexOf('?>', i + 1);
|
|
164
|
+
if (end < 0)
|
|
165
|
+
break;
|
|
166
|
+
i = end + 2;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Close tag </tagname>
|
|
170
|
+
if (c === '/') {
|
|
171
|
+
const end = html.indexOf('>', i + 1);
|
|
172
|
+
if (end < 0)
|
|
173
|
+
break;
|
|
174
|
+
i = end + 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// Open tag <tagname ...>
|
|
178
|
+
const tagEnd = findTagEnd(html, i);
|
|
179
|
+
if (tagEnd < 0)
|
|
180
|
+
break;
|
|
181
|
+
const raw = html.slice(i, tagEnd);
|
|
182
|
+
const ev = parseOpenTag(raw);
|
|
183
|
+
if (ev) {
|
|
184
|
+
events.push(ev);
|
|
185
|
+
// For script/style, skip body opaquely until matching close
|
|
186
|
+
if (ev.tag === 'script' || ev.tag === 'style') {
|
|
187
|
+
const closeNeedle = `</${ev.tag}`;
|
|
188
|
+
const closeIdx = indexOfCaseInsensitive(html, closeNeedle, tagEnd + 1);
|
|
189
|
+
if (closeIdx < 0)
|
|
190
|
+
break;
|
|
191
|
+
const gtAfter = html.indexOf('>', closeIdx);
|
|
192
|
+
i = gtAfter < 0 ? len : gtAfter + 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
i = tagEnd + 1;
|
|
197
|
+
}
|
|
198
|
+
return events;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Find the index of '>' that closes the open tag starting at `start`,
|
|
202
|
+
* handling quoted attribute values which may contain '>'.
|
|
203
|
+
*/
|
|
204
|
+
function findTagEnd(html, start) {
|
|
205
|
+
let i = start;
|
|
206
|
+
let inQuote = null;
|
|
207
|
+
const len = html.length;
|
|
208
|
+
while (i < len) {
|
|
209
|
+
const c = html[i];
|
|
210
|
+
if (inQuote) {
|
|
211
|
+
if (c === inQuote)
|
|
212
|
+
inQuote = null;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
if (c === '"' || c === "'")
|
|
216
|
+
inQuote = c;
|
|
217
|
+
else if (c === '>')
|
|
218
|
+
return i;
|
|
219
|
+
}
|
|
220
|
+
i++;
|
|
221
|
+
}
|
|
222
|
+
return -1;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Parse the raw inside of a `<...>` (excluding the angle brackets) into
|
|
226
|
+
* an OpenEvent. Returns null for the empty / malformed case.
|
|
227
|
+
*/
|
|
228
|
+
function parseOpenTag(raw) {
|
|
229
|
+
// Strip trailing '/' for self-closing forms <br/>
|
|
230
|
+
const trimmed = raw.trim().replace(/\/$/, '').trim();
|
|
231
|
+
if (!trimmed)
|
|
232
|
+
return null;
|
|
233
|
+
// First whitespace-or-end → tag name
|
|
234
|
+
const m = /^([a-zA-Z][a-zA-Z0-9-]*)/.exec(trimmed);
|
|
235
|
+
if (!m)
|
|
236
|
+
return null;
|
|
237
|
+
const tag = m[1].toLowerCase();
|
|
238
|
+
let rest = trimmed.slice(m[0].length).trim();
|
|
239
|
+
const attrs = {};
|
|
240
|
+
while (rest.length > 0) {
|
|
241
|
+
const am = /^([a-zA-Z_:][a-zA-Z0-9_:.-]*)/.exec(rest);
|
|
242
|
+
if (!am)
|
|
243
|
+
break;
|
|
244
|
+
const name = am[1].toLowerCase();
|
|
245
|
+
rest = rest.slice(am[0].length);
|
|
246
|
+
let value = '';
|
|
247
|
+
if (rest.startsWith('=')) {
|
|
248
|
+
rest = rest.slice(1).trimStart();
|
|
249
|
+
if (rest.startsWith('"') || rest.startsWith("'")) {
|
|
250
|
+
const quote = rest[0];
|
|
251
|
+
const end = rest.indexOf(quote, 1);
|
|
252
|
+
if (end < 0)
|
|
253
|
+
break;
|
|
254
|
+
value = rest.slice(1, end);
|
|
255
|
+
rest = rest.slice(end + 1);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const ws = rest.search(/\s/);
|
|
259
|
+
if (ws < 0) {
|
|
260
|
+
value = rest;
|
|
261
|
+
rest = '';
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
value = rest.slice(0, ws);
|
|
265
|
+
rest = rest.slice(ws);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
attrs[name] = value;
|
|
270
|
+
rest = rest.trimStart();
|
|
271
|
+
}
|
|
272
|
+
// Build a stable, sorted attribute signature.
|
|
273
|
+
// Some attributes are inherently dynamic (id like 'react-aria-:r1:'); we
|
|
274
|
+
// don't try to be smart about that here — the signature is stable across
|
|
275
|
+
// identical pages but flags any attr-value drift.
|
|
276
|
+
const keys = Object.keys(attrs).sort();
|
|
277
|
+
const attrSig = keys.map((k) => `${k}=${attrs[k]}`).join('\0');
|
|
278
|
+
return { tag, attrSig };
|
|
279
|
+
}
|
|
280
|
+
function indexOfCaseInsensitive(haystack, needle, fromIndex) {
|
|
281
|
+
// Cheap case-insensitive search; needle is short ('</script' or '</style')
|
|
282
|
+
const h = haystack.toLowerCase();
|
|
283
|
+
const n = needle.toLowerCase();
|
|
284
|
+
return h.indexOf(n, fromIndex);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Internal export only used in tests; exposes the tokenizer for white-box
|
|
288
|
+
* verification. Not part of the package public API.
|
|
289
|
+
*/
|
|
290
|
+
exports.__internals__ = { tokenize };
|
|
291
|
+
//# sourceMappingURL=dom-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-diff.js","sourceRoot":"","sources":["../../src/diff/dom-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestivAI Diff Engine — Ignore regions
|
|
3
|
+
*
|
|
4
|
+
* Before the diff loop runs, copy candidate pixel values into the
|
|
5
|
+
* baseline buffer for ignored regions. This makes ignored areas
|
|
6
|
+
* appear identical to the differ.
|
|
7
|
+
*/
|
|
8
|
+
import { IgnoreRegion } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Mask ignored regions by copying candidate pixels into baseline.
|
|
11
|
+
* After this, the diff loop will see no difference in those areas.
|
|
12
|
+
*
|
|
13
|
+
* Mutates `baseline` in place.
|
|
14
|
+
*
|
|
15
|
+
* @param baseline - Baseline pixel buffer (will be mutated)
|
|
16
|
+
* @param candidate - Candidate pixel buffer
|
|
17
|
+
* @param width - Image width
|
|
18
|
+
* @param height - Image height
|
|
19
|
+
* @param regions - Regions to ignore
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyIgnoreRegions(baseline: Uint8Array | Uint8ClampedArray, candidate: Uint8Array | Uint8ClampedArray, width: number, height: number, regions: IgnoreRegion[]): void;
|
|
22
|
+
//# sourceMappingURL=ignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore.d.ts","sourceRoot":"","sources":["../../src/diff/ignore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,UAAU,GAAG,iBAAiB,EACxC,SAAS,EAAE,UAAU,GAAG,iBAAiB,EACzC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EAAE,GACtB,IAAI,CAoBN"}
|