@testivai/witness 1.0.2 → 1.0.4
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 +25 -4
- package/dist/baselines/store.d.ts.map +1 -1
- package/dist/baselines/store.js +68 -4
- package/dist/baselines/store.js.map +1 -1
- 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/index.d.ts +2 -0
- package/dist/diff/index.d.ts.map +1 -1
- package/dist/diff/index.js +3 -1
- package/dist/diff/index.js.map +1 -1
- package/dist/report/compare.d.ts.map +1 -1
- package/dist/report/compare.js +63 -23
- package/dist/report/compare.js.map +1 -1
- package/dist/report/index.d.ts +1 -1
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/results.d.ts +21 -0
- package/dist/report/results.d.ts.map +1 -1
- package/dist/report/template.d.ts.map +1 -1
- package/dist/report/template.js +46 -0
- package/dist/report/template.js.map +1 -1
- package/package.json +6 -4
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Directory structure:
|
|
7
7
|
* .testivai/
|
|
8
|
-
* ├── baselines/{name}/screenshot.png, metadata.json, .previous/
|
|
9
|
-
* └── temp/{name}/screenshot.png
|
|
8
|
+
* ├── baselines/{name}/screenshot.png, metadata.json, dom.html?, .previous/
|
|
9
|
+
* └── temp/{name}/screenshot.png, dom.html? (gitignored)
|
|
10
|
+
*
|
|
11
|
+
* `dom.html` is optional — written when the adapter captures DOM alongside
|
|
12
|
+
* the screenshot. Used by the DOM-diff noise hint in the local report.
|
|
10
13
|
*/
|
|
11
14
|
export interface BaselineMetadata {
|
|
12
15
|
name: string;
|
|
@@ -38,8 +41,16 @@ export declare class BaselineStore {
|
|
|
38
41
|
/**
|
|
39
42
|
* Write a screenshot and metadata to the baseline directory.
|
|
40
43
|
* Creates the directory if it doesn't exist.
|
|
44
|
+
*
|
|
45
|
+
* @param dom - Optional DOM HTML captured at the same time as the
|
|
46
|
+
* screenshot. Stored as `dom.html` for the noise-hint compare.
|
|
47
|
+
*/
|
|
48
|
+
write(name: string, screenshot: Buffer, metadata?: Partial<BaselineMetadata>, dom?: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Read the baseline DOM HTML if one was captured.
|
|
51
|
+
* Returns null when no DOM file exists for this snapshot.
|
|
41
52
|
*/
|
|
42
|
-
|
|
53
|
+
readDom(name: string): string | null;
|
|
43
54
|
/**
|
|
44
55
|
* Approve a temp screenshot as the new baseline.
|
|
45
56
|
* Backs up the previous baseline to `.previous/` if one exists.
|
|
@@ -55,12 +66,20 @@ export declare class BaselineStore {
|
|
|
55
66
|
list(): string[];
|
|
56
67
|
/**
|
|
57
68
|
* Write a temp screenshot (captured during a test run).
|
|
69
|
+
*
|
|
70
|
+
* @param dom - Optional DOM HTML captured at the same time as the
|
|
71
|
+
* screenshot. Stored as `dom.html` next to the screenshot.
|
|
58
72
|
*/
|
|
59
|
-
writeTemp(name: string, screenshot: Buffer): void;
|
|
73
|
+
writeTemp(name: string, screenshot: Buffer, dom?: string): void;
|
|
60
74
|
/**
|
|
61
75
|
* Read a temp screenshot.
|
|
62
76
|
*/
|
|
63
77
|
readTemp(name: string): Buffer | null;
|
|
78
|
+
/**
|
|
79
|
+
* Read the temp DOM HTML if one was captured.
|
|
80
|
+
* Returns null when no temp DOM file exists for this snapshot.
|
|
81
|
+
*/
|
|
82
|
+
readTempDom(name: string): string | null;
|
|
64
83
|
/**
|
|
65
84
|
* List all temp snapshot names.
|
|
66
85
|
*/
|
|
@@ -72,7 +91,9 @@ export declare class BaselineStore {
|
|
|
72
91
|
getBaselineDir(name: string): string;
|
|
73
92
|
getBaselineScreenshotPath(name: string): string;
|
|
74
93
|
getBaselineMetadataPath(name: string): string;
|
|
94
|
+
getBaselineDomPath(name: string): string;
|
|
75
95
|
getTempScreenshotPath(name: string): string;
|
|
96
|
+
getTempDomPath(name: string): string;
|
|
76
97
|
getBaselinesDir(): string;
|
|
77
98
|
getTempDir(): string;
|
|
78
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/baselines/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/baselines/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAa;IAIZ,OAAO,CAAC,QAAQ,CAAC,WAAW;IAHxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEJ,WAAW,EAAE,MAAM;IAKhD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAK7B;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQjC;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IASnD;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAuBjG;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMpC;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAkD3B;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAsCxB;;OAEG;IACH,IAAI,IAAI,MAAM,EAAE;IAUhB;;;;;OAKG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAS/D;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQrC;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMxC;;OAEG;IACH,QAAQ,IAAI,MAAM,EAAE;IAUpB;;OAEG;IACH,SAAS,IAAI,IAAI;IAQjB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIpC,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI/C,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI7C,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIxC,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI3C,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIpC,eAAe,IAAI,MAAM;IAIzB,UAAU,IAAI,MAAM;CAGrB"}
|
package/dist/baselines/store.js
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Directory structure:
|
|
8
8
|
* .testivai/
|
|
9
|
-
* ├── baselines/{name}/screenshot.png, metadata.json, .previous/
|
|
10
|
-
* └── temp/{name}/screenshot.png
|
|
9
|
+
* ├── baselines/{name}/screenshot.png, metadata.json, dom.html?, .previous/
|
|
10
|
+
* └── temp/{name}/screenshot.png, dom.html? (gitignored)
|
|
11
|
+
*
|
|
12
|
+
* `dom.html` is optional — written when the adapter captures DOM alongside
|
|
13
|
+
* the screenshot. Used by the DOM-diff noise hint in the local report.
|
|
11
14
|
*/
|
|
12
15
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
16
|
if (k2 === undefined) k2 = k;
|
|
@@ -85,12 +88,19 @@ class BaselineStore {
|
|
|
85
88
|
/**
|
|
86
89
|
* Write a screenshot and metadata to the baseline directory.
|
|
87
90
|
* Creates the directory if it doesn't exist.
|
|
91
|
+
*
|
|
92
|
+
* @param dom - Optional DOM HTML captured at the same time as the
|
|
93
|
+
* screenshot. Stored as `dom.html` for the noise-hint compare.
|
|
88
94
|
*/
|
|
89
|
-
write(name, screenshot, metadata) {
|
|
95
|
+
write(name, screenshot, metadata, dom) {
|
|
90
96
|
const baselineDir = this.getBaselineDir(name);
|
|
91
97
|
fs.mkdirSync(baselineDir, { recursive: true });
|
|
92
98
|
// Write screenshot
|
|
93
99
|
fs.writeFileSync(this.getBaselineScreenshotPath(name), screenshot);
|
|
100
|
+
// Write DOM if provided
|
|
101
|
+
if (dom !== undefined) {
|
|
102
|
+
fs.writeFileSync(this.getBaselineDomPath(name), dom);
|
|
103
|
+
}
|
|
94
104
|
// Write metadata
|
|
95
105
|
const now = new Date().toISOString();
|
|
96
106
|
const meta = {
|
|
@@ -101,6 +111,16 @@ class BaselineStore {
|
|
|
101
111
|
};
|
|
102
112
|
fs.writeFileSync(this.getBaselineMetadataPath(name), JSON.stringify(meta, null, 2));
|
|
103
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Read the baseline DOM HTML if one was captured.
|
|
116
|
+
* Returns null when no DOM file exists for this snapshot.
|
|
117
|
+
*/
|
|
118
|
+
readDom(name) {
|
|
119
|
+
const domPath = this.getBaselineDomPath(name);
|
|
120
|
+
if (!fs.existsSync(domPath))
|
|
121
|
+
return null;
|
|
122
|
+
return fs.readFileSync(domPath, 'utf-8');
|
|
123
|
+
}
|
|
104
124
|
/**
|
|
105
125
|
* Approve a temp screenshot as the new baseline.
|
|
106
126
|
* Backs up the previous baseline to `.previous/` if one exists.
|
|
@@ -114,6 +134,8 @@ class BaselineStore {
|
|
|
114
134
|
const previousDir = path.join(baselineDir, '.previous');
|
|
115
135
|
const currentScreenshot = this.getBaselineScreenshotPath(name);
|
|
116
136
|
const currentMetadata = this.getBaselineMetadataPath(name);
|
|
137
|
+
const currentDom = this.getBaselineDomPath(name);
|
|
138
|
+
const tempDom = this.getTempDomPath(name);
|
|
117
139
|
// Backup current baseline if it exists
|
|
118
140
|
if (fs.existsSync(currentScreenshot)) {
|
|
119
141
|
fs.mkdirSync(previousDir, { recursive: true });
|
|
@@ -121,10 +143,21 @@ class BaselineStore {
|
|
|
121
143
|
if (fs.existsSync(currentMetadata)) {
|
|
122
144
|
fs.copyFileSync(currentMetadata, path.join(previousDir, 'metadata.json'));
|
|
123
145
|
}
|
|
146
|
+
if (fs.existsSync(currentDom)) {
|
|
147
|
+
fs.copyFileSync(currentDom, path.join(previousDir, 'dom.html'));
|
|
148
|
+
}
|
|
124
149
|
}
|
|
125
150
|
// Copy temp to baseline
|
|
126
151
|
fs.mkdirSync(baselineDir, { recursive: true });
|
|
127
152
|
fs.copyFileSync(tempScreenshot, currentScreenshot);
|
|
153
|
+
if (fs.existsSync(tempDom)) {
|
|
154
|
+
fs.copyFileSync(tempDom, currentDom);
|
|
155
|
+
}
|
|
156
|
+
else if (fs.existsSync(currentDom)) {
|
|
157
|
+
// Temp had no DOM but baseline did — drop the stale DOM rather
|
|
158
|
+
// than keep an inconsistent pair.
|
|
159
|
+
fs.rmSync(currentDom);
|
|
160
|
+
}
|
|
128
161
|
// Update metadata
|
|
129
162
|
const now = new Date().toISOString();
|
|
130
163
|
const existingMeta = this.readMetadata(name);
|
|
@@ -147,8 +180,10 @@ class BaselineStore {
|
|
|
147
180
|
}
|
|
148
181
|
const previousScreenshot = path.join(previousDir, 'screenshot.png');
|
|
149
182
|
const previousMetadata = path.join(previousDir, 'metadata.json');
|
|
183
|
+
const previousDom = path.join(previousDir, 'dom.html');
|
|
150
184
|
const currentScreenshot = this.getBaselineScreenshotPath(name);
|
|
151
185
|
const currentMetadata = this.getBaselineMetadataPath(name);
|
|
186
|
+
const currentDom = this.getBaselineDomPath(name);
|
|
152
187
|
// Restore previous screenshot
|
|
153
188
|
if (fs.existsSync(previousScreenshot)) {
|
|
154
189
|
fs.copyFileSync(previousScreenshot, currentScreenshot);
|
|
@@ -157,6 +192,13 @@ class BaselineStore {
|
|
|
157
192
|
if (fs.existsSync(previousMetadata)) {
|
|
158
193
|
fs.copyFileSync(previousMetadata, currentMetadata);
|
|
159
194
|
}
|
|
195
|
+
// Restore previous DOM (or remove the current one if no previous existed)
|
|
196
|
+
if (fs.existsSync(previousDom)) {
|
|
197
|
+
fs.copyFileSync(previousDom, currentDom);
|
|
198
|
+
}
|
|
199
|
+
else if (fs.existsSync(currentDom)) {
|
|
200
|
+
fs.rmSync(currentDom);
|
|
201
|
+
}
|
|
160
202
|
// Remove .previous directory
|
|
161
203
|
fs.rmSync(previousDir, { recursive: true, force: true });
|
|
162
204
|
}
|
|
@@ -174,11 +216,17 @@ class BaselineStore {
|
|
|
174
216
|
}
|
|
175
217
|
/**
|
|
176
218
|
* Write a temp screenshot (captured during a test run).
|
|
219
|
+
*
|
|
220
|
+
* @param dom - Optional DOM HTML captured at the same time as the
|
|
221
|
+
* screenshot. Stored as `dom.html` next to the screenshot.
|
|
177
222
|
*/
|
|
178
|
-
writeTemp(name, screenshot) {
|
|
223
|
+
writeTemp(name, screenshot, dom) {
|
|
179
224
|
const tempDir = path.join(this.tempDir, name);
|
|
180
225
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
181
226
|
fs.writeFileSync(this.getTempScreenshotPath(name), screenshot);
|
|
227
|
+
if (dom !== undefined) {
|
|
228
|
+
fs.writeFileSync(this.getTempDomPath(name), dom);
|
|
229
|
+
}
|
|
182
230
|
}
|
|
183
231
|
/**
|
|
184
232
|
* Read a temp screenshot.
|
|
@@ -190,6 +238,16 @@ class BaselineStore {
|
|
|
190
238
|
}
|
|
191
239
|
return fs.readFileSync(tempPath);
|
|
192
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Read the temp DOM HTML if one was captured.
|
|
243
|
+
* Returns null when no temp DOM file exists for this snapshot.
|
|
244
|
+
*/
|
|
245
|
+
readTempDom(name) {
|
|
246
|
+
const domPath = this.getTempDomPath(name);
|
|
247
|
+
if (!fs.existsSync(domPath))
|
|
248
|
+
return null;
|
|
249
|
+
return fs.readFileSync(domPath, 'utf-8');
|
|
250
|
+
}
|
|
193
251
|
/**
|
|
194
252
|
* List all temp snapshot names.
|
|
195
253
|
*/
|
|
@@ -220,9 +278,15 @@ class BaselineStore {
|
|
|
220
278
|
getBaselineMetadataPath(name) {
|
|
221
279
|
return path.join(this.baselinesDir, name, 'metadata.json');
|
|
222
280
|
}
|
|
281
|
+
getBaselineDomPath(name) {
|
|
282
|
+
return path.join(this.baselinesDir, name, 'dom.html');
|
|
283
|
+
}
|
|
223
284
|
getTempScreenshotPath(name) {
|
|
224
285
|
return path.join(this.tempDir, name, 'screenshot.png');
|
|
225
286
|
}
|
|
287
|
+
getTempDomPath(name) {
|
|
288
|
+
return path.join(this.tempDir, name, 'dom.html');
|
|
289
|
+
}
|
|
226
290
|
getBaselinesDir() {
|
|
227
291
|
return this.baselinesDir;
|
|
228
292
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/baselines/store.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/baselines/store.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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"}
|
package/dist/diff/index.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ export { applyIgnoreRegions } from './ignore';
|
|
|
12
12
|
export { applyMinimap } from './minimap';
|
|
13
13
|
export { normalizeDimensions } from './resize';
|
|
14
14
|
export { detectRegions } from './regions';
|
|
15
|
+
export { domDiff } from './dom-diff';
|
|
15
16
|
export type { DiffOptions, DiffResult, DiffRegion, IgnoreRegion, SizeMismatch, PbdRawResult, } from './types';
|
|
17
|
+
export type { DomDiffResult, DomDiffSummary } from './dom-diff';
|
|
16
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/diff/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/diff/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @see https://github.com/p01/pixel-buffer-diff
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.detectRegions = exports.normalizeDimensions = exports.applyMinimap = exports.applyIgnoreRegions = exports.diff = void 0;
|
|
12
|
+
exports.domDiff = exports.detectRegions = exports.normalizeDimensions = exports.applyMinimap = exports.applyIgnoreRegions = exports.diff = void 0;
|
|
13
13
|
var diff_1 = require("./diff");
|
|
14
14
|
Object.defineProperty(exports, "diff", { enumerable: true, get: function () { return diff_1.diff; } });
|
|
15
15
|
var ignore_1 = require("./ignore");
|
|
@@ -20,4 +20,6 @@ var resize_1 = require("./resize");
|
|
|
20
20
|
Object.defineProperty(exports, "normalizeDimensions", { enumerable: true, get: function () { return resize_1.normalizeDimensions; } });
|
|
21
21
|
var regions_1 = require("./regions");
|
|
22
22
|
Object.defineProperty(exports, "detectRegions", { enumerable: true, get: function () { return regions_1.detectRegions; } });
|
|
23
|
+
var dom_diff_1 = require("./dom-diff");
|
|
24
|
+
Object.defineProperty(exports, "domDiff", { enumerable: true, get: function () { return dom_diff_1.domDiff; } });
|
|
23
25
|
//# sourceMappingURL=index.js.map
|
package/dist/diff/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,mCAA8C;AAArC,4GAAA,kBAAkB,OAAA;AAC3B,qCAAyC;AAAhC,uGAAA,YAAY,OAAA;AACrB,mCAA+C;AAAtC,6GAAA,mBAAmB,OAAA;AAC5B,qCAA0C;AAAjC,wGAAA,aAAa,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,mCAA8C;AAArC,4GAAA,kBAAkB,OAAA;AAC3B,qCAAyC;AAAhC,uGAAA,YAAY,OAAA;AACrB,mCAA+C;AAAtC,6GAAA,mBAAmB,OAAA;AAC5B,qCAA0C;AAAjC,wGAAA,aAAa,OAAA;AACtB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/report/compare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/report/compare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EAAqB,cAAc,EAAkB,MAAM,WAAW,CAAC;AAE9E,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CAmEpE"}
|
package/dist/report/compare.js
CHANGED
|
@@ -41,7 +41,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.compareAll = compareAll;
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
|
+
const pngjs_1 = require("pngjs");
|
|
44
45
|
const diff_1 = require("../diff");
|
|
46
|
+
const dom_diff_1 = require("../diff/dom-diff");
|
|
45
47
|
const store_1 = require("../baselines/store");
|
|
46
48
|
/**
|
|
47
49
|
* Compare all temp screenshots against their baselines.
|
|
@@ -86,18 +88,62 @@ function compareAll(options) {
|
|
|
86
88
|
// For simplicity and zero-dep, we do a byte-level comparison first,
|
|
87
89
|
// then fall back to diff engine for actual pixel data.
|
|
88
90
|
const result = compareBuffers(baselineBuffer, tempBuffer, name, snapshotImagesDir, threshold);
|
|
91
|
+
// DOM-level noise hint. Only meaningful when both sides captured DOM.
|
|
92
|
+
// For 'passed' (pixel-identical) we skip the work — it's already the
|
|
93
|
+
// strongest possible signal.
|
|
94
|
+
if (result.status === 'changed') {
|
|
95
|
+
const baselineDom = store.readDom(name);
|
|
96
|
+
const candidateDom = store.readTempDom(name);
|
|
97
|
+
const domSignal = computeDomSignal(baselineDom, candidateDom);
|
|
98
|
+
if (domSignal) {
|
|
99
|
+
result.dom = domSignal;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
89
102
|
results.push(result);
|
|
90
103
|
}
|
|
91
104
|
return results;
|
|
92
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Run DOM diff and turn it into a report-shaped signal.
|
|
108
|
+
*
|
|
109
|
+
* Returns null when DOM data isn't available on either side — we don't
|
|
110
|
+
* want to confuse the user with "DOM unchanged" hints when the adapter
|
|
111
|
+
* never captured DOM in the first place.
|
|
112
|
+
*/
|
|
113
|
+
function computeDomSignal(baselineDom, candidateDom) {
|
|
114
|
+
if (!baselineDom || !candidateDom)
|
|
115
|
+
return null;
|
|
116
|
+
const result = (0, dom_diff_1.domDiff)(baselineDom, candidateDom);
|
|
117
|
+
return {
|
|
118
|
+
changed: result.domChanged,
|
|
119
|
+
summary: result.summary,
|
|
120
|
+
// The whole point: pixels differ, DOM does not → likely render noise.
|
|
121
|
+
noiseHint: !result.domChanged,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Decode a PNG buffer to raw RGBA pixel data using pngjs.
|
|
126
|
+
*/
|
|
127
|
+
function decodePng(buffer) {
|
|
128
|
+
const png = pngjs_1.PNG.sync.read(buffer);
|
|
129
|
+
return { data: png.data, width: png.width, height: png.height };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Encode raw RGBA pixel data back to a PNG buffer.
|
|
133
|
+
*/
|
|
134
|
+
function encodePng(data, width, height) {
|
|
135
|
+
const png = new pngjs_1.PNG({ width, height });
|
|
136
|
+
png.data = data;
|
|
137
|
+
return pngjs_1.PNG.sync.write(png);
|
|
138
|
+
}
|
|
93
139
|
/**
|
|
94
140
|
* Compare two PNG buffers.
|
|
95
141
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
142
|
+
* Decodes both PNGs to raw RGBA, runs the pixel-level diff engine,
|
|
143
|
+
* writes a valid diff PNG, and returns a SnapshotResult.
|
|
98
144
|
*/
|
|
99
145
|
function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir, threshold) {
|
|
100
|
-
// Quick byte-level comparison
|
|
146
|
+
// Quick byte-level comparison — identical files need no decoding
|
|
101
147
|
if (baselineBuffer.equals(candidateBuffer)) {
|
|
102
148
|
return {
|
|
103
149
|
name,
|
|
@@ -109,31 +155,24 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
109
155
|
currentPath: `images/${name}/current.png`,
|
|
110
156
|
};
|
|
111
157
|
}
|
|
112
|
-
// Buffers differ — attempt pixel-level diff
|
|
113
|
-
// We assume raw RGBA pixel data with known dimensions
|
|
114
|
-
// In practice, we need to decode PNGs first. For now, treat as raw RGBA.
|
|
115
|
-
// The actual PNG decoding will be added when fast-png is available.
|
|
116
158
|
try {
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
159
|
+
// Decode PNG → raw RGBA so the diff engine gets real pixel data
|
|
160
|
+
const baseline = decodePng(baselineBuffer);
|
|
161
|
+
const candidate = decodePng(candidateBuffer);
|
|
162
|
+
// Use baseline dimensions for the diff canvas; pad/crop candidate if needed
|
|
163
|
+
const width = baseline.width;
|
|
164
|
+
const height = baseline.height;
|
|
122
165
|
const expectedLen = width * height * 4;
|
|
123
|
-
// Pad buffers if needed
|
|
124
166
|
const baseline8 = new Uint8ClampedArray(expectedLen);
|
|
125
|
-
baseline8.set(new Uint8ClampedArray(
|
|
167
|
+
baseline8.set(new Uint8ClampedArray(baseline.data.buffer, baseline.data.byteOffset, Math.min(baseline.data.length, expectedLen)));
|
|
126
168
|
const candidate8 = new Uint8ClampedArray(expectedLen);
|
|
127
|
-
candidate8.set(new Uint8ClampedArray(
|
|
169
|
+
candidate8.set(new Uint8ClampedArray(candidate.data.buffer, candidate.data.byteOffset, Math.min(candidate.data.length, expectedLen)));
|
|
128
170
|
const diffOutput = new Uint8ClampedArray(expectedLen);
|
|
129
171
|
const diffResult = (0, diff_1.diff)(baseline8, candidate8, diffOutput, width, height, { threshold });
|
|
130
|
-
//
|
|
172
|
+
// Encode the diff RGBA back to a valid PNG and write it
|
|
173
|
+
const diffPngBuffer = encodePng(Buffer.from(diffOutput.buffer), width, height);
|
|
131
174
|
const diffPath = path.join(snapshotImagesDir, 'diff.png');
|
|
132
|
-
fs.writeFileSync(diffPath,
|
|
133
|
-
// We already know the raw bytes differ (byte-equal returns early above).
|
|
134
|
-
// If the pixel diff still reports identical (sub-threshold), the images
|
|
135
|
-
// are visually equivalent but binary-different — still flag as "changed"
|
|
136
|
-
// so the user is aware.
|
|
175
|
+
fs.writeFileSync(diffPath, diffPngBuffer);
|
|
137
176
|
const status = 'changed';
|
|
138
177
|
const diffPercent = diffResult.diffPercent > 0 ? diffResult.diffPercent : 0.01;
|
|
139
178
|
return {
|
|
@@ -147,8 +186,9 @@ function compareBuffers(baselineBuffer, candidateBuffer, name, snapshotImagesDir
|
|
|
147
186
|
diffPath: `images/${name}/diff.png`,
|
|
148
187
|
};
|
|
149
188
|
}
|
|
150
|
-
catch {
|
|
151
|
-
//
|
|
189
|
+
catch (err) {
|
|
190
|
+
// PNG decode failed (corrupt file, unexpected format) — report as changed
|
|
191
|
+
// without a diff image so the user can investigate manually
|
|
152
192
|
return {
|
|
153
193
|
name,
|
|
154
194
|
status: '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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"}
|
package/dist/report/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export type { GenerateReportOptions } from './generator';
|
|
|
6
6
|
export { compareAll } from './compare';
|
|
7
7
|
export type { CompareOptions } from './compare';
|
|
8
8
|
export { renderHtml } from './template';
|
|
9
|
-
export type { ReportData, ReportSummary, SnapshotResult, SnapshotStatus, } from './results';
|
|
9
|
+
export type { ReportData, ReportSummary, SnapshotResult, SnapshotStatus, SnapshotDomSignal, } from './results';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
|
package/dist/report/results.d.ts
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
* Machine-readable output for the GitHub Action and other consumers.
|
|
5
5
|
*/
|
|
6
6
|
export type SnapshotStatus = 'passed' | 'changed' | 'new';
|
|
7
|
+
export interface SnapshotDomSignal {
|
|
8
|
+
/** True if the DOM differs structurally between baseline and candidate. */
|
|
9
|
+
changed: boolean;
|
|
10
|
+
/** Per-bucket counts; null when changed is false. */
|
|
11
|
+
summary: {
|
|
12
|
+
added: number;
|
|
13
|
+
removed: number;
|
|
14
|
+
attributeChanges: number;
|
|
15
|
+
} | null;
|
|
16
|
+
/**
|
|
17
|
+
* Noise hint: pixel diff is non-zero but DOM is structurally unchanged.
|
|
18
|
+
* Suggests render noise (anti-aliasing, font hinting, sub-pixel layout)
|
|
19
|
+
* rather than a real visual regression.
|
|
20
|
+
*/
|
|
21
|
+
noiseHint: boolean;
|
|
22
|
+
}
|
|
7
23
|
export interface SnapshotResult {
|
|
8
24
|
name: string;
|
|
9
25
|
status: SnapshotStatus;
|
|
@@ -19,6 +35,11 @@ export interface SnapshotResult {
|
|
|
19
35
|
currentPath?: string;
|
|
20
36
|
/** Relative path to diff image (if any) */
|
|
21
37
|
diffPath?: string;
|
|
38
|
+
/**
|
|
39
|
+
* DOM-level signal. Present only when both baseline and candidate
|
|
40
|
+
* captured DOM HTML alongside the screenshot.
|
|
41
|
+
*/
|
|
42
|
+
dom?: SnapshotDomSignal;
|
|
22
43
|
}
|
|
23
44
|
export interface ReportSummary {
|
|
24
45
|
total: number;
|
|
@@ -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,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;
|
|
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,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7E;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;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;CACzB;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,CAuLnD"}
|
package/dist/report/template.js
CHANGED
|
@@ -74,6 +74,13 @@ function renderHtml(data) {
|
|
|
74
74
|
.badge-passed { background: #3fb95022; color: #3fb950; }
|
|
75
75
|
.snapshot-stats { font-size: 12px; color: #8b949e; margin-bottom: 16px; }
|
|
76
76
|
|
|
77
|
+
/* DOM noise hint */
|
|
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
|
+
.dom-hint.noise { background: #1f6feb1a; border: 1px solid #1f6feb44; color: #79c0ff; }
|
|
80
|
+
.dom-hint.changed { background: #6e768166; border: 1px solid #8b949e44; color: #c9d1d9; }
|
|
81
|
+
.dom-hint .label { font-weight: 600; }
|
|
82
|
+
.dom-hint .summary { color: inherit; opacity: 0.85; }
|
|
83
|
+
|
|
77
84
|
/* 3-column diff view */
|
|
78
85
|
.diff-view { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
|
|
79
86
|
.diff-view.two-col { grid-template-columns: 1fr 1fr; }
|
|
@@ -206,6 +213,7 @@ function renderSnapshot(snapshot) {
|
|
|
206
213
|
<span class="snapshot-badge ${badgeClass}">${snapshot.status}</span>
|
|
207
214
|
</div>
|
|
208
215
|
${snapshot.status === 'changed' ? `<div class="snapshot-stats">Diff: ${snapshot.diffPercent.toFixed(2)}% (${snapshot.diffCount} pixels)</div>` : ''}
|
|
216
|
+
${renderDomHint(snapshot)}
|
|
209
217
|
<div class="${gridClass}">
|
|
210
218
|
${hasBaseline ? `<div class="diff-col"><label>Baseline</label><img src="${snapshot.baselinePath}" alt="Baseline"></div>` : ''}
|
|
211
219
|
${hasDiff ? `<div class="diff-col"><label>Diff</label><img src="${snapshot.diffPath}" alt="Diff"></div>` : ''}
|
|
@@ -218,6 +226,44 @@ function renderSnapshot(snapshot) {
|
|
|
218
226
|
</div>` : ''}
|
|
219
227
|
</div>`;
|
|
220
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Render the DOM noise-hint badge for a snapshot.
|
|
231
|
+
*
|
|
232
|
+
* Two states (DOM hint is only meaningful for `changed` snapshots that
|
|
233
|
+
* have DOM data captured on both sides):
|
|
234
|
+
* - noiseHint: pixels differ but DOM is unchanged → "likely render noise"
|
|
235
|
+
* - changed: pixels and DOM both differ → show counts so the reviewer
|
|
236
|
+
* can decide whether the change is intentional
|
|
237
|
+
*
|
|
238
|
+
* Returns empty string when no DOM data was captured (the adapter didn't
|
|
239
|
+
* record `dom.html`); we don't want to confuse the user with "no signal".
|
|
240
|
+
*/
|
|
241
|
+
function renderDomHint(snapshot) {
|
|
242
|
+
if (!snapshot.dom)
|
|
243
|
+
return '';
|
|
244
|
+
if (snapshot.dom.noiseHint) {
|
|
245
|
+
return `
|
|
246
|
+
<div class="dom-hint noise" title="DOM tree is identical between baseline and candidate; the pixel diff is likely render noise.">
|
|
247
|
+
<span class="label">DOM unchanged</span>
|
|
248
|
+
<span class="summary">— pixel diff is likely render noise (anti-aliasing, font hinting).</span>
|
|
249
|
+
</div>`;
|
|
250
|
+
}
|
|
251
|
+
const s = snapshot.dom.summary;
|
|
252
|
+
if (!s)
|
|
253
|
+
return '';
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (s.added > 0)
|
|
256
|
+
parts.push(`${s.added} added`);
|
|
257
|
+
if (s.removed > 0)
|
|
258
|
+
parts.push(`${s.removed} removed`);
|
|
259
|
+
if (s.attributeChanges > 0)
|
|
260
|
+
parts.push(`${s.attributeChanges} attribute change${s.attributeChanges === 1 ? '' : 's'}`);
|
|
261
|
+
return `
|
|
262
|
+
<div class="dom-hint changed" title="Structural DOM changes detected alongside the pixel diff.">
|
|
263
|
+
<span class="label">DOM changed</span>
|
|
264
|
+
<span class="summary">— ${escapeHtml(parts.join(', ') || 'structural difference')}.</span>
|
|
265
|
+
</div>`;
|
|
266
|
+
}
|
|
221
267
|
function escapeHtml(str) {
|
|
222
268
|
return str
|
|
223
269
|
.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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testivai/witness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Framework-agnostic visual regression testing CLI for TestivAI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -77,18 +77,20 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"chalk": "^4.1.2",
|
|
80
|
-
"chrome-remote-interface": "^0.
|
|
80
|
+
"chrome-remote-interface": "^0.34.0",
|
|
81
81
|
"commander": "^11.0.0",
|
|
82
82
|
"inquirer": "^8.2.6",
|
|
83
|
-
"ora": "^
|
|
83
|
+
"ora": "^9.4.0",
|
|
84
|
+
"pngjs": "^7.0.0",
|
|
84
85
|
"simple-git": "^3.20.0",
|
|
85
|
-
"ws": "^8.
|
|
86
|
+
"ws": "^8.20.1",
|
|
86
87
|
"@testivai/common": "0.2.2"
|
|
87
88
|
},
|
|
88
89
|
"devDependencies": {
|
|
89
90
|
"@types/inquirer": "^8.2.10",
|
|
90
91
|
"@types/jest": "^29.0.0",
|
|
91
92
|
"@types/node": "^20.0.0",
|
|
93
|
+
"@types/pngjs": "^6.0.5",
|
|
92
94
|
"@types/ws": "^8.0.0",
|
|
93
95
|
"jest": "^29.0.0",
|
|
94
96
|
"ts-jest": "^29.2.5",
|