@testivai/witness-playwright 1.0.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/__tests__/.gitkeep +0 -0
- package/__tests__/config-integration.spec.ts +102 -0
- package/__tests__/snapshot.spec.d.ts +1 -0
- package/__tests__/snapshot.spec.js +81 -0
- package/__tests__/snapshot.spec.ts +58 -0
- package/__tests__/unit/ci.spec.d.ts +1 -0
- package/__tests__/unit/ci.spec.js +35 -0
- package/__tests__/unit/ci.spec.ts +40 -0
- package/__tests__/unit/reporter.spec.d.ts +1 -0
- package/__tests__/unit/reporter.spec.js +37 -0
- package/__tests__/unit/reporter.spec.ts +43 -0
- package/__tests__/unit/structureAnalyzer.spec.js +212 -0
- package/__tests__/unit/types.spec.ts +179 -0
- package/dist/__tests__/unit/ci.spec.d.ts +1 -0
- package/dist/__tests__/unit/ci.spec.js +226 -0
- package/dist/__tests__/unit/compression.spec.d.ts +4 -0
- package/dist/__tests__/unit/compression.spec.js +46 -0
- package/dist/ci.d.ts +30 -0
- package/dist/ci.js +117 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +47 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.js +158 -0
- package/dist/config/loader.d.ts +29 -0
- package/dist/config/loader.js +251 -0
- package/dist/domAnalyzer.d.ts +10 -0
- package/dist/domAnalyzer.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +11 -0
- package/dist/reporter-entry.d.ts +2 -0
- package/dist/reporter-entry.js +5 -0
- package/dist/reporter-types.d.ts +2 -0
- package/dist/reporter-types.js +2 -0
- package/dist/reporter.d.ts +21 -0
- package/dist/reporter.js +249 -0
- package/dist/snapshot.d.ts +12 -0
- package/dist/snapshot.js +601 -0
- package/dist/structureAnalyzer.d.ts +12 -0
- package/dist/structureAnalyzer.js +288 -0
- package/dist/types.d.ts +368 -0
- package/dist/types.js +10 -0
- package/examples/structure-analysis-example.spec.ts +118 -0
- package/examples/structure-analysis.config.ts +159 -0
- package/jest.config.js +8 -0
- package/package.json +51 -0
- package/playwright.config.ts +11 -0
- package/src/__tests__/unit/ci.spec.ts +257 -0
- package/src/__tests__/unit/compression.spec.ts +52 -0
- package/src/ci.ts +140 -0
- package/src/cli/index.ts +49 -0
- package/src/cli/init.ts +131 -0
- package/src/config/loader.ts +238 -0
- package/src/index.ts +14 -0
- package/src/reporter-entry.ts +6 -0
- package/src/reporter-types.ts +5 -0
- package/src/reporter.ts +251 -0
- package/src/snapshot.ts +632 -0
- package/src/structureAnalyzer.ts +338 -0
- package/src/types.ts +388 -0
- package/tsconfig.jest.json +7 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeStructure = analyzeStructure;
|
|
4
|
+
exports.compareStructureAnalysis = compareStructureAnalysis;
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration for structure analysis
|
|
7
|
+
* @renamed Was DEFAULT_CONFIG in domAnalyzer.ts — renamed to conceal internal layer terminology (IP protection)
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
enableFingerprint: true,
|
|
11
|
+
enableStructure: true,
|
|
12
|
+
enableSemantic: true,
|
|
13
|
+
ignoreAttributes: [
|
|
14
|
+
'data-testid',
|
|
15
|
+
'data-reactid',
|
|
16
|
+
'data-reactroot',
|
|
17
|
+
'ng-version',
|
|
18
|
+
'ng-version',
|
|
19
|
+
'ng-reflect-router-outlet',
|
|
20
|
+
'data-ng-version',
|
|
21
|
+
'style', // Inline styles change often
|
|
22
|
+
'class', // CSS classes can be dynamic
|
|
23
|
+
],
|
|
24
|
+
ignoreElements: [
|
|
25
|
+
'script',
|
|
26
|
+
'style',
|
|
27
|
+
'noscript',
|
|
28
|
+
'meta',
|
|
29
|
+
'link',
|
|
30
|
+
'title',
|
|
31
|
+
],
|
|
32
|
+
ignoreContentPatterns: [
|
|
33
|
+
/\d{4}-\d{2}-\d{2}/, // Dates
|
|
34
|
+
/\d{1,2}:\d{2}(:\d{2})?/, // Times
|
|
35
|
+
/\b\d{4}\b/, // Years
|
|
36
|
+
/\b\d+\b/, // Pure numbers
|
|
37
|
+
/uuid-/i, // UUIDs
|
|
38
|
+
/_\d+/, // Number suffixes
|
|
39
|
+
/\$\d+\.?\d*/, // Currency
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Analyzes page structure and generates fingerprint
|
|
44
|
+
* @renamed Was `analyzeDOM` — renamed to conceal internal layer terminology (IP protection)
|
|
45
|
+
*/
|
|
46
|
+
async function analyzeStructure(page, config = {}) {
|
|
47
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
48
|
+
return await page.evaluate((cfg) => {
|
|
49
|
+
// Helper functions
|
|
50
|
+
const normalizeSelector = (el) => {
|
|
51
|
+
let selector = el.nodeName.toLowerCase();
|
|
52
|
+
// Add ID if present and not a dynamic ID
|
|
53
|
+
if (el.id && !cfg.ignoreAttributes.includes('id')) {
|
|
54
|
+
// Skip if ID looks dynamic
|
|
55
|
+
if (!/^(ember|react|ng|vue|_)/.test(el.id)) {
|
|
56
|
+
selector += `#${el.id}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Add meaningful classes only
|
|
60
|
+
if (el.className && !cfg.ignoreAttributes.includes('class')) {
|
|
61
|
+
const classes = el.className.toString()
|
|
62
|
+
.split(' ')
|
|
63
|
+
.filter((c) => c &&
|
|
64
|
+
!/^(ember|react|ng|vue|css|active|hover|focus)/.test(c) &&
|
|
65
|
+
!c.includes('_') &&
|
|
66
|
+
!/\d/.test(c))
|
|
67
|
+
.slice(0, 3); // Limit to 3 most meaningful classes
|
|
68
|
+
if (classes.length > 0) {
|
|
69
|
+
selector += `.${classes.join('.')}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return selector;
|
|
73
|
+
};
|
|
74
|
+
const getElementPath = (el, maxDepth = 10) => {
|
|
75
|
+
const path = [];
|
|
76
|
+
let current = el;
|
|
77
|
+
let depth = 0;
|
|
78
|
+
while (current && current.nodeType === Node.ELEMENT_NODE && depth < maxDepth) {
|
|
79
|
+
path.unshift(normalizeSelector(current));
|
|
80
|
+
current = current.parentElement;
|
|
81
|
+
depth++;
|
|
82
|
+
}
|
|
83
|
+
return path.join(' > ');
|
|
84
|
+
};
|
|
85
|
+
const shouldIgnoreElement = (el) => {
|
|
86
|
+
return cfg.ignoreElements.includes(el.nodeName.toLowerCase());
|
|
87
|
+
};
|
|
88
|
+
const shouldIgnoreAttribute = (attr) => {
|
|
89
|
+
return cfg.ignoreAttributes.includes(attr.toLowerCase());
|
|
90
|
+
};
|
|
91
|
+
const shouldIgnoreContent = (content) => {
|
|
92
|
+
return cfg.ignoreContentPatterns.some((pattern) => pattern.test(content));
|
|
93
|
+
};
|
|
94
|
+
const normalizeText = (text) => {
|
|
95
|
+
if (!text)
|
|
96
|
+
return '';
|
|
97
|
+
// Replace ignored patterns with placeholders
|
|
98
|
+
let normalized = text.trim();
|
|
99
|
+
cfg.ignoreContentPatterns.forEach((pattern) => {
|
|
100
|
+
normalized = normalized.replace(pattern, '[DYNAMIC]');
|
|
101
|
+
});
|
|
102
|
+
// Normalize whitespace
|
|
103
|
+
normalized = normalized.replace(/\s+/g, ' ');
|
|
104
|
+
return normalized;
|
|
105
|
+
};
|
|
106
|
+
// Get all meaningful elements
|
|
107
|
+
const allElements = Array.from(document.querySelectorAll('*'))
|
|
108
|
+
.filter(el => !shouldIgnoreElement(el));
|
|
109
|
+
const analysis = {};
|
|
110
|
+
// 1. Generate fingerprint
|
|
111
|
+
if (cfg.enableFingerprint) {
|
|
112
|
+
const paths = allElements.map((el) => {
|
|
113
|
+
const path = getElementPath(el);
|
|
114
|
+
const text = normalizeText(el.textContent || '');
|
|
115
|
+
const attrs = [];
|
|
116
|
+
// Add important attributes
|
|
117
|
+
for (const attr of el.attributes) {
|
|
118
|
+
if (!shouldIgnoreAttribute(attr.name) && attr.value) {
|
|
119
|
+
attrs.push(`${attr.name}="${attr.value}"`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return `${path}${text ? `|${text}` : ''}${attrs.length ? `|${attrs.join(',')}` : ''}`;
|
|
123
|
+
});
|
|
124
|
+
// Create hash from sorted paths
|
|
125
|
+
const sortedPaths = paths.sort().join('|||');
|
|
126
|
+
analysis.fingerprint = btoa(sortedPaths).substring(0, 32);
|
|
127
|
+
}
|
|
128
|
+
// 2. Structural analysis
|
|
129
|
+
if (cfg.enableStructure) {
|
|
130
|
+
const elementTypes = {};
|
|
131
|
+
let maxDepth = 0;
|
|
132
|
+
allElements.forEach((el) => {
|
|
133
|
+
const tag = el.nodeName.toLowerCase();
|
|
134
|
+
elementTypes[tag] = (elementTypes[tag] || 0) + 1;
|
|
135
|
+
// Calculate depth
|
|
136
|
+
let depth = 0;
|
|
137
|
+
let current = el;
|
|
138
|
+
while (current.parentElement) {
|
|
139
|
+
depth++;
|
|
140
|
+
current = current.parentElement;
|
|
141
|
+
}
|
|
142
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
143
|
+
});
|
|
144
|
+
analysis.structure = {
|
|
145
|
+
totalElements: allElements.length,
|
|
146
|
+
elementTypes,
|
|
147
|
+
maxDepth,
|
|
148
|
+
interactiveElements: {
|
|
149
|
+
buttons: document.querySelectorAll('button').length,
|
|
150
|
+
inputs: document.querySelectorAll('input').length,
|
|
151
|
+
links: document.querySelectorAll('a[href]').length,
|
|
152
|
+
forms: document.querySelectorAll('form').length,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// 3. Semantic analysis
|
|
157
|
+
if (cfg.enableSemantic) {
|
|
158
|
+
const headings = {};
|
|
159
|
+
for (let i = 1; i <= 6; i++) {
|
|
160
|
+
headings[`h${i}`] = document.querySelectorAll(`h${i}`).length;
|
|
161
|
+
}
|
|
162
|
+
analysis.semantic = {
|
|
163
|
+
headings,
|
|
164
|
+
landmarks: {
|
|
165
|
+
hasHeader: !!document.querySelector('header'),
|
|
166
|
+
hasNav: !!document.querySelector('nav'),
|
|
167
|
+
hasMain: !!document.querySelector('main'),
|
|
168
|
+
hasFooter: !!document.querySelector('footer'),
|
|
169
|
+
hasAside: !!document.querySelector('aside'),
|
|
170
|
+
},
|
|
171
|
+
lists: {
|
|
172
|
+
ordered: document.querySelectorAll('ol').length,
|
|
173
|
+
unordered: document.querySelectorAll('ul').length,
|
|
174
|
+
},
|
|
175
|
+
tables: document.querySelectorAll('table').length,
|
|
176
|
+
images: document.querySelectorAll('img').length,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// 4. Component analysis (framework-agnostic)
|
|
180
|
+
const components = {};
|
|
181
|
+
allElements.forEach((el) => {
|
|
182
|
+
// Try various component detection strategies
|
|
183
|
+
const componentName = el.getAttribute('data-testid')?.replace(/-/g, '') ||
|
|
184
|
+
el.getAttribute('data-component') ||
|
|
185
|
+
el.getAttribute('data-testid') ||
|
|
186
|
+
el.className?.toString().match(/(\w+Container|\w+Component|\w+Page|\w+Card|\w+Modal)/i)?.[1];
|
|
187
|
+
if (componentName && componentName.length > 2) {
|
|
188
|
+
if (!components[componentName]) {
|
|
189
|
+
components[componentName] = [];
|
|
190
|
+
}
|
|
191
|
+
const attrs = {};
|
|
192
|
+
for (const attr of el.attributes) {
|
|
193
|
+
if (!shouldIgnoreAttribute(attr.name) && attr.value) {
|
|
194
|
+
attrs[attr.name] = attr.value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
components[componentName].push({
|
|
198
|
+
selector: getElementPath(el),
|
|
199
|
+
text: normalizeText(el.textContent || '').substring(0, 100),
|
|
200
|
+
attributes: attrs,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (Object.keys(components).length > 0) {
|
|
205
|
+
analysis.components = components;
|
|
206
|
+
}
|
|
207
|
+
return analysis;
|
|
208
|
+
}, finalConfig);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Compare two structure analyses and identify changes
|
|
212
|
+
* @renamed Was `compareDOMAnalysis` — renamed to conceal internal layer terminology (IP protection)
|
|
213
|
+
*/
|
|
214
|
+
function compareStructureAnalysis(baseline, current) {
|
|
215
|
+
const changes = [];
|
|
216
|
+
// Compare fingerprints
|
|
217
|
+
if (baseline.fingerprint && current.fingerprint) {
|
|
218
|
+
if (baseline.fingerprint !== current.fingerprint) {
|
|
219
|
+
changes.push({
|
|
220
|
+
type: 'fingerprint',
|
|
221
|
+
severity: 'high',
|
|
222
|
+
description: 'Page structure has changed',
|
|
223
|
+
details: {
|
|
224
|
+
baseline: baseline.fingerprint,
|
|
225
|
+
current: current.fingerprint,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Compare structure
|
|
231
|
+
if (baseline.structure && current.structure) {
|
|
232
|
+
if (baseline.structure.totalElements !== current.structure.totalElements) {
|
|
233
|
+
changes.push({
|
|
234
|
+
type: 'structure',
|
|
235
|
+
severity: 'medium',
|
|
236
|
+
description: `Element count changed from ${baseline.structure.totalElements} to ${current.structure.totalElements}`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// Check for new/removed element types
|
|
240
|
+
const baselineTypes = Object.keys(baseline.structure.elementTypes);
|
|
241
|
+
const currentTypes = Object.keys(current.structure.elementTypes);
|
|
242
|
+
const removed = baselineTypes.filter(t => !currentTypes.includes(t));
|
|
243
|
+
const added = currentTypes.filter(t => !baselineTypes.includes(t));
|
|
244
|
+
if (removed.length > 0) {
|
|
245
|
+
changes.push({
|
|
246
|
+
type: 'structure',
|
|
247
|
+
severity: 'medium',
|
|
248
|
+
description: `Removed element types: ${removed.join(', ')}`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (added.length > 0) {
|
|
252
|
+
changes.push({
|
|
253
|
+
type: 'structure',
|
|
254
|
+
severity: 'medium',
|
|
255
|
+
description: `Added element types: ${added.join(', ')}`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Compare semantic structure
|
|
260
|
+
if (baseline.semantic && current.semantic) {
|
|
261
|
+
// Check heading changes
|
|
262
|
+
const baselineHeadings = baseline.semantic.headings;
|
|
263
|
+
const currentHeadings = current.semantic.headings;
|
|
264
|
+
for (const level in baselineHeadings) {
|
|
265
|
+
if (baselineHeadings[level] !== currentHeadings[level]) {
|
|
266
|
+
changes.push({
|
|
267
|
+
type: 'semantic',
|
|
268
|
+
severity: 'low',
|
|
269
|
+
description: `${level} count changed from ${baselineHeadings[level]} to ${currentHeadings[level]}`,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Check landmark changes
|
|
274
|
+
const baselineLandmarks = baseline.semantic.landmarks;
|
|
275
|
+
const currentLandmarks = current.semantic.landmarks;
|
|
276
|
+
for (const landmark in baselineLandmarks) {
|
|
277
|
+
if (baselineLandmarks[landmark] !==
|
|
278
|
+
currentLandmarks[landmark]) {
|
|
279
|
+
changes.push({
|
|
280
|
+
type: 'semantic',
|
|
281
|
+
severity: 'medium',
|
|
282
|
+
description: `Landmark ${landmark} ${baselineLandmarks[landmark] ? 'removed' : 'added'}`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return changes;
|
|
288
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Testivai Witness Playwright SDK
|
|
3
|
+
*
|
|
4
|
+
* Defines the data shapes for evidence collection:
|
|
5
|
+
* - SnapshotPayload: DOM + Layout data for a single snapshot
|
|
6
|
+
* - BatchPayload: Git + Browser info + collection of snapshots
|
|
7
|
+
* - TestivAIConfig: Configuration for visual analysis behavior
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Layout configuration for visual analysis
|
|
11
|
+
*/
|
|
12
|
+
export interface LayoutConfig {
|
|
13
|
+
/** Sensitivity level: 0-4 scale (0=strict/precise, 4=very lenient) */
|
|
14
|
+
sensitivity: number;
|
|
15
|
+
/** Base pixel tolerance for layout differences */
|
|
16
|
+
tolerance: number;
|
|
17
|
+
/** Per-selector tolerance overrides (optional) */
|
|
18
|
+
selectorTolerances?: Record<string, number>;
|
|
19
|
+
/** Use relative tolerance for large elements (optional) */
|
|
20
|
+
useRelativeTolerance?: boolean;
|
|
21
|
+
/** Percentage multiplier for relative tolerance (optional) */
|
|
22
|
+
relativeTolerance?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AI configuration for visual analysis
|
|
26
|
+
*/
|
|
27
|
+
export interface AIConfig {
|
|
28
|
+
/** Sensitivity level: 0-4 scale (0=conservative, 4=aggressive) */
|
|
29
|
+
sensitivity: number;
|
|
30
|
+
/** Minimum confidence threshold for AI_BUG verdict (0.0-1.0) */
|
|
31
|
+
confidence: number;
|
|
32
|
+
/** Include AI reasoning in results (optional) */
|
|
33
|
+
enableReasoning?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Performance metrics configuration
|
|
37
|
+
*/
|
|
38
|
+
export interface PerformanceMetricsConfig {
|
|
39
|
+
/** Enable performance metrics capture (default: true) */
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Structure analysis configuration
|
|
44
|
+
* Controls how page structure (HTML elements, hierarchy, semantics) is analyzed.
|
|
45
|
+
* @renamed Was `DOMAnalysisConfig` — renamed to conceal internal layer terminology (IP protection)
|
|
46
|
+
*/
|
|
47
|
+
export interface StructureAnalysisConfig {
|
|
48
|
+
/** Enable fingerprint generation (default: true) */
|
|
49
|
+
enableFingerprint?: boolean;
|
|
50
|
+
/** Enable structural analysis (default: true) */
|
|
51
|
+
enableStructure?: boolean;
|
|
52
|
+
/** Enable semantic analysis (default: true) */
|
|
53
|
+
enableSemantic?: boolean;
|
|
54
|
+
/** Attributes to ignore in analysis */
|
|
55
|
+
ignoreAttributes?: string[];
|
|
56
|
+
/** Elements to ignore completely */
|
|
57
|
+
ignoreElements?: string[];
|
|
58
|
+
/** Content patterns to ignore */
|
|
59
|
+
ignoreContentPatterns?: RegExp[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Structure fingerprint and analysis result
|
|
63
|
+
* Contains the structural analysis of a page: element fingerprint, hierarchy info, and semantic structure.
|
|
64
|
+
* @renamed Was `DOMAnalysis` — renamed to conceal internal layer terminology (IP protection)
|
|
65
|
+
*/
|
|
66
|
+
export interface StructureAnalysis {
|
|
67
|
+
/** Fast hash of normalized DOM structure */
|
|
68
|
+
fingerprint?: string;
|
|
69
|
+
/** Structural information about the DOM */
|
|
70
|
+
structure?: {
|
|
71
|
+
totalElements: number;
|
|
72
|
+
elementTypes: Record<string, number>;
|
|
73
|
+
maxDepth: number;
|
|
74
|
+
interactiveElements: {
|
|
75
|
+
buttons: number;
|
|
76
|
+
inputs: number;
|
|
77
|
+
links: number;
|
|
78
|
+
forms: number;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/** Semantic structure information */
|
|
82
|
+
semantic?: {
|
|
83
|
+
headings: Record<string, number>;
|
|
84
|
+
landmarks: {
|
|
85
|
+
hasHeader: boolean;
|
|
86
|
+
hasNav: boolean;
|
|
87
|
+
hasMain: boolean;
|
|
88
|
+
hasFooter: boolean;
|
|
89
|
+
hasAside: boolean;
|
|
90
|
+
};
|
|
91
|
+
lists: {
|
|
92
|
+
ordered: number;
|
|
93
|
+
unordered: number;
|
|
94
|
+
};
|
|
95
|
+
tables: number;
|
|
96
|
+
images: number;
|
|
97
|
+
};
|
|
98
|
+
/** Component analysis (if detectable) */
|
|
99
|
+
components?: Record<string, Array<{
|
|
100
|
+
selector: string;
|
|
101
|
+
text: string;
|
|
102
|
+
attributes: Record<string, string>;
|
|
103
|
+
}>>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Structure change information
|
|
107
|
+
* Describes a detected change in page structure between baseline and current.
|
|
108
|
+
* @renamed Was `DOMChange` — renamed to conceal internal layer terminology (IP protection)
|
|
109
|
+
*/
|
|
110
|
+
export interface StructureChange {
|
|
111
|
+
type: 'fingerprint' | 'structure' | 'semantic' | 'component';
|
|
112
|
+
severity: 'low' | 'medium' | 'high';
|
|
113
|
+
description: string;
|
|
114
|
+
details?: any;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Performance timing metrics
|
|
118
|
+
* @deprecated Use performanceMetrics with CDP Performance.getMetrics instead
|
|
119
|
+
*/
|
|
120
|
+
export interface PerformanceTimings {
|
|
121
|
+
/** Navigation start time */
|
|
122
|
+
navigationStart?: number;
|
|
123
|
+
/** DOM content loaded time */
|
|
124
|
+
domContentLoaded?: number;
|
|
125
|
+
/** Page load complete time */
|
|
126
|
+
loadComplete?: number;
|
|
127
|
+
/** First contentful paint */
|
|
128
|
+
firstContentfulPaint?: number;
|
|
129
|
+
/** Largest contentful paint */
|
|
130
|
+
largestContentfulPaint?: number;
|
|
131
|
+
/** Time to interactive */
|
|
132
|
+
timeToInteractive?: number;
|
|
133
|
+
/** Total blocking time */
|
|
134
|
+
totalBlockingTime?: number;
|
|
135
|
+
/** Cumulative layout shift */
|
|
136
|
+
cumulativeLayoutShift?: number;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Lighthouse performance results
|
|
140
|
+
* @deprecated Lighthouse has been removed. Use performanceMetrics with CDP Performance.getMetrics instead
|
|
141
|
+
*/
|
|
142
|
+
export interface LighthouseResults {
|
|
143
|
+
/** Performance score (0-100) */
|
|
144
|
+
performance?: number;
|
|
145
|
+
/** Accessibility score (0-100) */
|
|
146
|
+
accessibility?: number;
|
|
147
|
+
/** Best practices score (0-100) */
|
|
148
|
+
bestPractices?: number;
|
|
149
|
+
/** SEO score (0-100) */
|
|
150
|
+
seo?: number;
|
|
151
|
+
/** Core Web Vitals */
|
|
152
|
+
coreWebVitals?: {
|
|
153
|
+
lcp?: number;
|
|
154
|
+
fid?: number;
|
|
155
|
+
cls?: number;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Environment-specific configuration overrides
|
|
160
|
+
*/
|
|
161
|
+
export interface EnvironmentConfig {
|
|
162
|
+
/** Configuration for CI environments */
|
|
163
|
+
ci?: {
|
|
164
|
+
layout?: Partial<LayoutConfig>;
|
|
165
|
+
ai?: Partial<AIConfig>;
|
|
166
|
+
};
|
|
167
|
+
/** Configuration for development environments */
|
|
168
|
+
development?: {
|
|
169
|
+
layout?: Partial<LayoutConfig>;
|
|
170
|
+
ai?: Partial<AIConfig>;
|
|
171
|
+
};
|
|
172
|
+
/** Configuration for production environments */
|
|
173
|
+
production?: {
|
|
174
|
+
layout?: Partial<LayoutConfig>;
|
|
175
|
+
ai?: Partial<AIConfig>;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Complete TestivAI project configuration
|
|
180
|
+
*/
|
|
181
|
+
export interface TestivAIProjectConfig {
|
|
182
|
+
/** API key for authentication */
|
|
183
|
+
apiKey?: string;
|
|
184
|
+
/** API endpoint URL (defaults to production) */
|
|
185
|
+
apiUrl?: string;
|
|
186
|
+
/** Layout analysis settings */
|
|
187
|
+
layout: LayoutConfig;
|
|
188
|
+
/** AI analysis settings */
|
|
189
|
+
ai: AIConfig;
|
|
190
|
+
/** Performance metrics settings (optional) */
|
|
191
|
+
performanceMetrics?: PerformanceMetricsConfig;
|
|
192
|
+
/**
|
|
193
|
+
* Structure analysis settings (optional)
|
|
194
|
+
* @renamed Was `dom` — renamed to conceal internal layer terminology (IP protection)
|
|
195
|
+
*/
|
|
196
|
+
structure?: StructureAnalysisConfig;
|
|
197
|
+
/** Environment-specific overrides (optional) */
|
|
198
|
+
environments?: EnvironmentConfig;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Per-test configuration overrides
|
|
202
|
+
*/
|
|
203
|
+
export interface TestivAIConfig {
|
|
204
|
+
/** Layout settings (optional - overrides project defaults) */
|
|
205
|
+
layout?: Partial<LayoutConfig>;
|
|
206
|
+
/** AI settings (optional - overrides project defaults) */
|
|
207
|
+
ai?: Partial<AIConfig>;
|
|
208
|
+
/** Performance settings (optional - overrides project defaults) */
|
|
209
|
+
performanceMetrics?: Partial<PerformanceMetricsConfig>;
|
|
210
|
+
/**
|
|
211
|
+
* Structure analysis settings (optional - overrides project defaults)
|
|
212
|
+
* @renamed Was `dom` — renamed to conceal internal layer terminology (IP protection)
|
|
213
|
+
*/
|
|
214
|
+
structure?: Partial<StructureAnalysisConfig>;
|
|
215
|
+
/** Element selectors to capture (existing option) */
|
|
216
|
+
selectors?: string[];
|
|
217
|
+
/** Use Chrome DevTools Protocol for full-page capture (default: true - use CDP, set to false for scroll-and-stitch) */
|
|
218
|
+
useCDP?: boolean;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Layout/Bounding box data for an element
|
|
222
|
+
*/
|
|
223
|
+
export interface LayoutData {
|
|
224
|
+
/** X coordinate */
|
|
225
|
+
x: number;
|
|
226
|
+
/** Y coordinate */
|
|
227
|
+
y: number;
|
|
228
|
+
/** Width */
|
|
229
|
+
width: number;
|
|
230
|
+
/** Height */
|
|
231
|
+
height: number;
|
|
232
|
+
/** Top position */
|
|
233
|
+
top: number;
|
|
234
|
+
/** Left position */
|
|
235
|
+
left: number;
|
|
236
|
+
/** Right position */
|
|
237
|
+
right: number;
|
|
238
|
+
/** Bottom position */
|
|
239
|
+
bottom: number;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Page structure snapshot data (HTML content)
|
|
243
|
+
* @renamed Was `DOMData` — renamed to conceal internal layer terminology (IP protection)
|
|
244
|
+
*/
|
|
245
|
+
export interface StructureData {
|
|
246
|
+
/** Serialized HTML of the element */
|
|
247
|
+
html: string;
|
|
248
|
+
/** Computed styles (optional) */
|
|
249
|
+
styles?: Record<string, string>;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Computed styles data for visual comparison
|
|
253
|
+
* @renamed Was `CSSData` — renamed to conceal internal layer terminology (IP protection)
|
|
254
|
+
*/
|
|
255
|
+
export interface StylesData {
|
|
256
|
+
/** Computed styles keyed by element selector path */
|
|
257
|
+
computed_styles: Record<string, Record<string, string>>;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Snapshot payload for a single evidence capture
|
|
261
|
+
*/
|
|
262
|
+
export interface SnapshotPayload {
|
|
263
|
+
/**
|
|
264
|
+
* Page structure data (HTML content)
|
|
265
|
+
* @renamed Was `dom` — renamed to conceal internal layer terminology (IP protection)
|
|
266
|
+
*/
|
|
267
|
+
structure: StructureData;
|
|
268
|
+
/**
|
|
269
|
+
* Computed styles data (optional)
|
|
270
|
+
* @renamed Was `css` — renamed to conceal internal layer terminology (IP protection)
|
|
271
|
+
*/
|
|
272
|
+
styles?: StylesData;
|
|
273
|
+
/** Layout/bounding box data */
|
|
274
|
+
layout: LayoutData;
|
|
275
|
+
/** Timestamp when snapshot was taken */
|
|
276
|
+
timestamp: number;
|
|
277
|
+
/** Test name or identifier */
|
|
278
|
+
testName: string;
|
|
279
|
+
/** Snapshot name or identifier */
|
|
280
|
+
snapshotName: string;
|
|
281
|
+
/** URL of the page when snapshot was taken */
|
|
282
|
+
url?: string;
|
|
283
|
+
/** Viewport dimensions */
|
|
284
|
+
viewport?: {
|
|
285
|
+
width: number;
|
|
286
|
+
height: number;
|
|
287
|
+
};
|
|
288
|
+
/** TestivAI configuration for this snapshot */
|
|
289
|
+
testivaiConfig?: TestivAIConfig;
|
|
290
|
+
/** Base64-encoded screenshot data (PNG) */
|
|
291
|
+
screenshotData?: string;
|
|
292
|
+
/** Performance timing metrics (optional) */
|
|
293
|
+
performanceTimings?: PerformanceTimings;
|
|
294
|
+
/** Lighthouse results (optional) */
|
|
295
|
+
lighthouseResults?: LighthouseResults;
|
|
296
|
+
/**
|
|
297
|
+
* Structure analysis results (optional)
|
|
298
|
+
* @renamed Was `domAnalysis` — renamed to conceal internal layer terminology (IP protection)
|
|
299
|
+
*/
|
|
300
|
+
structureAnalysis?: StructureAnalysis;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Git information for batch context
|
|
304
|
+
*/
|
|
305
|
+
export interface GitInfo {
|
|
306
|
+
/** Current branch name */
|
|
307
|
+
branch: string;
|
|
308
|
+
/** Current commit hash */
|
|
309
|
+
commit: string;
|
|
310
|
+
/** Repository URL (optional) */
|
|
311
|
+
repository?: string;
|
|
312
|
+
/** Commit message (optional) */
|
|
313
|
+
message?: string;
|
|
314
|
+
/** Author name (optional) */
|
|
315
|
+
author?: string;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Browser context information
|
|
319
|
+
*/
|
|
320
|
+
export interface BrowserInfo {
|
|
321
|
+
/** Browser name (chromium, firefox, webkit) */
|
|
322
|
+
name: string;
|
|
323
|
+
/** Browser version */
|
|
324
|
+
version: string;
|
|
325
|
+
/** Viewport width */
|
|
326
|
+
viewportWidth: number;
|
|
327
|
+
/** Viewport height */
|
|
328
|
+
viewportHeight: number;
|
|
329
|
+
/** User agent string */
|
|
330
|
+
userAgent: string;
|
|
331
|
+
/** Operating system */
|
|
332
|
+
os?: string;
|
|
333
|
+
/** Device type (desktop, mobile, tablet) */
|
|
334
|
+
device?: string;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* CI environment information for integration feedback (e.g., GitHub commit statuses, PR comments).
|
|
338
|
+
* Mirrors CiInfo from ci.ts for payload serialization.
|
|
339
|
+
*/
|
|
340
|
+
export interface CiInfoPayload {
|
|
341
|
+
/** CI provider name (e.g., 'github_actions', 'gitlab_ci', 'circleci') */
|
|
342
|
+
provider: string;
|
|
343
|
+
/** Pull/Merge request number (if available) */
|
|
344
|
+
prNumber?: number;
|
|
345
|
+
/** URL to the CI run (if available) */
|
|
346
|
+
runUrl?: string;
|
|
347
|
+
/** CI build/run identifier */
|
|
348
|
+
buildId?: string;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Batch payload containing all evidence for a test run
|
|
352
|
+
*/
|
|
353
|
+
export interface BatchPayload {
|
|
354
|
+
/** Git information */
|
|
355
|
+
git: GitInfo;
|
|
356
|
+
/** Browser context information */
|
|
357
|
+
browser: BrowserInfo;
|
|
358
|
+
/** Collection of snapshots */
|
|
359
|
+
snapshots: SnapshotPayload[];
|
|
360
|
+
/** Batch ID (generated) */
|
|
361
|
+
batchId: string;
|
|
362
|
+
/** Timestamp when batch was created */
|
|
363
|
+
timestamp: number;
|
|
364
|
+
/** Unique identifier for a CI/CD run, to group sharded jobs */
|
|
365
|
+
runId?: string | null;
|
|
366
|
+
/** CI environment information for integration feedback (optional) */
|
|
367
|
+
ci?: CiInfoPayload | null;
|
|
368
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for Testivai Witness Playwright SDK
|
|
4
|
+
*
|
|
5
|
+
* Defines the data shapes for evidence collection:
|
|
6
|
+
* - SnapshotPayload: DOM + Layout data for a single snapshot
|
|
7
|
+
* - BatchPayload: Git + Browser info + collection of snapshots
|
|
8
|
+
* - TestivAIConfig: Configuration for visual analysis behavior
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|