@vizzly-testing/cli 0.12.0 → 0.13.1
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 +87 -544
- package/dist/client/index.js +7 -5
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +20 -13
- package/dist/server/handlers/api-handler.js +53 -1
- package/dist/server/handlers/tdd-handler.js +225 -24
- package/dist/server/http-server.js +9 -9
- package/dist/services/tdd-service.js +143 -38
- package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +2 -1
- package/dist/types/reporter/src/components/comparison/comparison-group.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +9 -0
- package/dist/types/reporter/src/services/api-client.d.ts +1 -1
- package/dist/types/server/handlers/api-handler.d.ts +48 -0
- package/dist/types/server/handlers/tdd-handler.d.ts +69 -12
- package/dist/types/services/server-manager.d.ts +117 -12
- package/dist/types/services/tdd-service.d.ts +4 -2
- package/dist/types/utils/image-input-detector.d.ts +71 -0
- package/dist/utils/image-input-detector.js +150 -0
- package/package.json +3 -3
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
2
4
|
import { createServiceLogger } from '../../utils/logger-factory.js';
|
|
5
|
+
import { detectImageInputType } from '../../utils/image-input-detector.js';
|
|
3
6
|
const logger = createServiceLogger('API-HANDLER');
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -60,7 +63,56 @@ export const createApiHandler = apiService => {
|
|
|
60
63
|
}
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
|
|
67
|
+
// Support both base64 encoded images and file paths
|
|
68
|
+
let imageBuffer;
|
|
69
|
+
const inputType = detectImageInputType(image);
|
|
70
|
+
if (inputType === 'file-path') {
|
|
71
|
+
// It's a file path - resolve and read the file
|
|
72
|
+
const filePath = resolve(image.replace('file://', ''));
|
|
73
|
+
if (!existsSync(filePath)) {
|
|
74
|
+
return {
|
|
75
|
+
statusCode: 400,
|
|
76
|
+
body: {
|
|
77
|
+
error: `Screenshot file not found: ${filePath}`,
|
|
78
|
+
originalPath: image
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
imageBuffer = readFileSync(filePath);
|
|
84
|
+
logger.debug(`Loaded screenshot from file: ${filePath}`);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
statusCode: 500,
|
|
88
|
+
body: {
|
|
89
|
+
error: `Failed to read screenshot file: ${error.message}`,
|
|
90
|
+
filePath
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
} else if (inputType === 'base64') {
|
|
95
|
+
// It's base64 encoded
|
|
96
|
+
try {
|
|
97
|
+
imageBuffer = Buffer.from(image, 'base64');
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
statusCode: 400,
|
|
101
|
+
body: {
|
|
102
|
+
error: `Invalid base64 image data: ${error.message}`
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Unknown input type
|
|
108
|
+
return {
|
|
109
|
+
statusCode: 400,
|
|
110
|
+
body: {
|
|
111
|
+
error: 'Invalid image input: must be a file path or base64 encoded image data',
|
|
112
|
+
receivedType: typeof image
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
64
116
|
screenshotCount++;
|
|
65
117
|
|
|
66
118
|
// Fire upload in background - DON'T AWAIT!
|
|
@@ -1,10 +1,96 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
2
|
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
import honeydiff from '@vizzly-testing/honeydiff';
|
|
4
5
|
import { createServiceLogger } from '../../utils/logger-factory.js';
|
|
5
6
|
import { TddService } from '../../services/tdd-service.js';
|
|
6
7
|
import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
|
|
8
|
+
import { detectImageInputType } from '../../utils/image-input-detector.js';
|
|
9
|
+
let {
|
|
10
|
+
getDimensionsSync
|
|
11
|
+
} = honeydiff;
|
|
7
12
|
const logger = createServiceLogger('TDD-HANDLER');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Group comparisons by screenshot name with variant structure
|
|
16
|
+
* Matches cloud product's grouping logic from comparison.js
|
|
17
|
+
*/
|
|
18
|
+
const groupComparisons = comparisons => {
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
|
|
21
|
+
// Group by screenshot name
|
|
22
|
+
for (const comp of comparisons) {
|
|
23
|
+
if (!groups.has(comp.name)) {
|
|
24
|
+
groups.set(comp.name, {
|
|
25
|
+
name: comp.name,
|
|
26
|
+
comparisons: [],
|
|
27
|
+
browsers: new Set(),
|
|
28
|
+
viewports: new Set(),
|
|
29
|
+
devices: new Set(),
|
|
30
|
+
totalVariants: 0
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const group = groups.get(comp.name);
|
|
34
|
+
group.comparisons.push(comp);
|
|
35
|
+
group.totalVariants++;
|
|
36
|
+
|
|
37
|
+
// Track unique browsers, viewports, devices
|
|
38
|
+
if (comp.properties?.browser) {
|
|
39
|
+
group.browsers.add(comp.properties.browser);
|
|
40
|
+
}
|
|
41
|
+
if (comp.properties?.viewport_width && comp.properties?.viewport_height) {
|
|
42
|
+
group.viewports.add(`${comp.properties.viewport_width}x${comp.properties.viewport_height}`);
|
|
43
|
+
}
|
|
44
|
+
if (comp.properties?.device) {
|
|
45
|
+
group.devices.add(comp.properties.device);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Convert to final structure
|
|
50
|
+
return Array.from(groups.values()).map(group => {
|
|
51
|
+
const browsers = Array.from(group.browsers);
|
|
52
|
+
const viewports = Array.from(group.viewports);
|
|
53
|
+
const devices = Array.from(group.devices);
|
|
54
|
+
|
|
55
|
+
// Build variants structure (browser -> viewport -> comparisons)
|
|
56
|
+
const variants = {};
|
|
57
|
+
group.comparisons.forEach(comp => {
|
|
58
|
+
const browser = comp.properties?.browser || null;
|
|
59
|
+
const viewport = comp.properties?.viewport_width && comp.properties?.viewport_height ? `${comp.properties.viewport_width}x${comp.properties.viewport_height}` : null;
|
|
60
|
+
if (!variants[browser]) variants[browser] = {};
|
|
61
|
+
if (!variants[browser][viewport]) variants[browser][viewport] = [];
|
|
62
|
+
variants[browser][viewport].push(comp);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Determine grouping strategy
|
|
66
|
+
let groupingStrategy = 'flat';
|
|
67
|
+
if (browsers.length > 1) groupingStrategy = 'browser';else if (viewports.length > 1) groupingStrategy = 'viewport';
|
|
68
|
+
|
|
69
|
+
// Sort comparisons by viewport area (largest first)
|
|
70
|
+
group.comparisons.sort((a, b) => {
|
|
71
|
+
const aArea = (a.properties?.viewport_width || 0) * (a.properties?.viewport_height || 0);
|
|
72
|
+
const bArea = (b.properties?.viewport_width || 0) * (b.properties?.viewport_height || 0);
|
|
73
|
+
if (bArea !== aArea) return bArea - aArea;
|
|
74
|
+
return (b.properties?.viewport_width || 0) - (a.properties?.viewport_width || 0);
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
...group,
|
|
78
|
+
browsers,
|
|
79
|
+
viewports,
|
|
80
|
+
devices: Array.from(devices),
|
|
81
|
+
variants,
|
|
82
|
+
groupingStrategy
|
|
83
|
+
};
|
|
84
|
+
}).sort((a, b) => {
|
|
85
|
+
// Sort groups: multi-variant first (by variant count), then singles alphabetically
|
|
86
|
+
if (a.totalVariants > 1 && b.totalVariants === 1) return -1;
|
|
87
|
+
if (a.totalVariants === 1 && b.totalVariants > 1) return 1;
|
|
88
|
+
if (a.totalVariants > 1 && b.totalVariants > 1) {
|
|
89
|
+
return b.totalVariants - a.totalVariants;
|
|
90
|
+
}
|
|
91
|
+
return a.name.localeCompare(b.name);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
8
94
|
export const createTddHandler = (config, workingDir, baselineBuild, baselineComparison, setBaseline = false) => {
|
|
9
95
|
const tddService = new TddService(config, workingDir, setBaseline);
|
|
10
96
|
const reportPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
@@ -14,8 +100,12 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
14
100
|
return {
|
|
15
101
|
timestamp: Date.now(),
|
|
16
102
|
comparisons: [],
|
|
103
|
+
// Internal flat list for easy updates
|
|
104
|
+
groups: [],
|
|
105
|
+
// Grouped structure for UI
|
|
17
106
|
summary: {
|
|
18
107
|
total: 0,
|
|
108
|
+
groups: 0,
|
|
19
109
|
passed: 0,
|
|
20
110
|
failed: 0,
|
|
21
111
|
errors: 0
|
|
@@ -29,8 +119,10 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
29
119
|
return {
|
|
30
120
|
timestamp: Date.now(),
|
|
31
121
|
comparisons: [],
|
|
122
|
+
groups: [],
|
|
32
123
|
summary: {
|
|
33
124
|
total: 0,
|
|
125
|
+
groups: 0,
|
|
34
126
|
passed: 0,
|
|
35
127
|
failed: 0,
|
|
36
128
|
errors: 0
|
|
@@ -42,26 +134,36 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
42
134
|
try {
|
|
43
135
|
const reportData = readReportData();
|
|
44
136
|
|
|
45
|
-
//
|
|
46
|
-
|
|
137
|
+
// Ensure comparisons array exists (backward compatibility)
|
|
138
|
+
if (!reportData.comparisons) {
|
|
139
|
+
reportData.comparisons = [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Find existing comparison by unique ID
|
|
143
|
+
// This ensures we update the correct variant even with same name
|
|
144
|
+
const existingIndex = reportData.comparisons.findIndex(c => c.id === newComparison.id);
|
|
47
145
|
if (existingIndex >= 0) {
|
|
48
146
|
reportData.comparisons[existingIndex] = newComparison;
|
|
49
|
-
logger.debug(`Updated comparison for ${newComparison.name}`);
|
|
147
|
+
logger.debug(`Updated comparison for ${newComparison.name} (${newComparison.properties?.viewport_width}x${newComparison.properties?.viewport_height})`);
|
|
50
148
|
} else {
|
|
51
149
|
reportData.comparisons.push(newComparison);
|
|
52
|
-
logger.debug(`Added new comparison for ${newComparison.name}`);
|
|
150
|
+
logger.debug(`Added new comparison for ${newComparison.name} (${newComparison.properties?.viewport_width}x${newComparison.properties?.viewport_height})`);
|
|
53
151
|
}
|
|
54
152
|
|
|
153
|
+
// Generate grouped structure from flat comparisons
|
|
154
|
+
reportData.groups = groupComparisons(reportData.comparisons);
|
|
155
|
+
|
|
55
156
|
// Update summary
|
|
56
157
|
reportData.timestamp = Date.now();
|
|
57
158
|
reportData.summary = {
|
|
58
159
|
total: reportData.comparisons.length,
|
|
59
|
-
|
|
160
|
+
groups: reportData.groups.length,
|
|
161
|
+
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created' || c.status === 'new').length,
|
|
60
162
|
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
61
163
|
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
62
164
|
};
|
|
63
165
|
writeFileSync(reportPath, JSON.stringify(reportData, null, 2));
|
|
64
|
-
logger.debug('Report data saved
|
|
166
|
+
logger.debug('Report data saved with grouped structure');
|
|
65
167
|
} catch (error) {
|
|
66
168
|
logger.error('Failed to update comparison:', error);
|
|
67
169
|
}
|
|
@@ -111,10 +213,24 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
111
213
|
};
|
|
112
214
|
}
|
|
113
215
|
|
|
216
|
+
// Unwrap double-nested properties if needed (client SDK wraps options in properties field)
|
|
217
|
+
// This happens when test helper passes { properties: {...}, threshold: 0.1 }
|
|
218
|
+
// and client SDK wraps it as { properties: options }
|
|
219
|
+
let unwrappedProperties = properties;
|
|
220
|
+
if (properties.properties && typeof properties.properties === 'object') {
|
|
221
|
+
// Merge top-level properties with nested properties
|
|
222
|
+
unwrappedProperties = {
|
|
223
|
+
...properties,
|
|
224
|
+
...properties.properties
|
|
225
|
+
};
|
|
226
|
+
// Remove the nested properties field to avoid confusion
|
|
227
|
+
delete unwrappedProperties.properties;
|
|
228
|
+
}
|
|
229
|
+
|
|
114
230
|
// Validate and sanitize properties
|
|
115
231
|
let validatedProperties;
|
|
116
232
|
try {
|
|
117
|
-
validatedProperties = validateScreenshotProperties(
|
|
233
|
+
validatedProperties = validateScreenshotProperties(unwrappedProperties);
|
|
118
234
|
} catch (error) {
|
|
119
235
|
return {
|
|
120
236
|
statusCode: 400,
|
|
@@ -125,14 +241,95 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
125
241
|
}
|
|
126
242
|
};
|
|
127
243
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
244
|
+
|
|
245
|
+
// Extract viewport/browser to top-level properties (matching cloud API behavior)
|
|
246
|
+
// This ensures signature generation works correctly with: name|viewport_width|browser
|
|
247
|
+
const extractedProperties = {
|
|
248
|
+
viewport_width: validatedProperties.viewport?.width || null,
|
|
249
|
+
viewport_height: validatedProperties.viewport?.height || null,
|
|
250
|
+
browser: validatedProperties.browser || null,
|
|
251
|
+
device: validatedProperties.device || null,
|
|
252
|
+
url: validatedProperties.url || null,
|
|
253
|
+
selector: validatedProperties.selector || null,
|
|
254
|
+
threshold: validatedProperties.threshold,
|
|
255
|
+
// Preserve full nested structure in metadata for compatibility
|
|
256
|
+
metadata: validatedProperties
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Support both base64 encoded images and file paths
|
|
260
|
+
// Vitest browser mode returns file paths, so we need to handle both
|
|
261
|
+
let imageBuffer;
|
|
262
|
+
const inputType = detectImageInputType(image);
|
|
263
|
+
if (inputType === 'file-path') {
|
|
264
|
+
// It's a file path - resolve and read the file
|
|
265
|
+
const filePath = resolve(image.replace('file://', ''));
|
|
266
|
+
if (!existsSync(filePath)) {
|
|
267
|
+
return {
|
|
268
|
+
statusCode: 400,
|
|
269
|
+
body: {
|
|
270
|
+
error: `Screenshot file not found: ${filePath}`,
|
|
271
|
+
originalPath: image,
|
|
272
|
+
tddMode: true
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
imageBuffer = readFileSync(filePath);
|
|
278
|
+
logger.debug(`Loaded screenshot from file: ${filePath}`);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return {
|
|
281
|
+
statusCode: 500,
|
|
282
|
+
body: {
|
|
283
|
+
error: `Failed to read screenshot file: ${error.message}`,
|
|
284
|
+
filePath,
|
|
285
|
+
tddMode: true
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
} else if (inputType === 'base64') {
|
|
290
|
+
// It's base64 encoded
|
|
291
|
+
try {
|
|
292
|
+
imageBuffer = Buffer.from(image, 'base64');
|
|
293
|
+
} catch (error) {
|
|
294
|
+
return {
|
|
295
|
+
statusCode: 400,
|
|
296
|
+
body: {
|
|
297
|
+
error: `Invalid base64 image data: ${error.message}`,
|
|
298
|
+
tddMode: true
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// Unknown input type
|
|
304
|
+
return {
|
|
305
|
+
statusCode: 400,
|
|
306
|
+
body: {
|
|
307
|
+
error: 'Invalid image input: must be a file path or base64 encoded image data',
|
|
308
|
+
receivedType: typeof image,
|
|
309
|
+
tddMode: true
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Auto-detect image dimensions if viewport not provided
|
|
315
|
+
if (!extractedProperties.viewport_width || !extractedProperties.viewport_height) {
|
|
316
|
+
try {
|
|
317
|
+
const dimensions = getDimensionsSync(imageBuffer);
|
|
318
|
+
if (!extractedProperties.viewport_width) {
|
|
319
|
+
extractedProperties.viewport_width = dimensions.width;
|
|
320
|
+
}
|
|
321
|
+
if (!extractedProperties.viewport_height) {
|
|
322
|
+
extractedProperties.viewport_height = dimensions.height;
|
|
323
|
+
}
|
|
324
|
+
logger.debug(`Auto-detected dimensions: ${dimensions.width}x${dimensions.height}`);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
logger.debug(`Failed to auto-detect dimensions: ${err.message}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
132
329
|
|
|
133
330
|
// Use the sanitized name as-is (no modification with browser/viewport)
|
|
134
331
|
// Baseline matching uses signature logic (name + viewport_width + browser)
|
|
135
|
-
const comparison = await tddService.compareScreenshot(sanitizedName, imageBuffer,
|
|
332
|
+
const comparison = await tddService.compareScreenshot(sanitizedName, imageBuffer, extractedProperties);
|
|
136
333
|
logger.debug(`Comparison result: ${comparison.status}`);
|
|
137
334
|
|
|
138
335
|
// Convert absolute file paths to web-accessible URLs
|
|
@@ -149,6 +346,8 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
149
346
|
|
|
150
347
|
// Record the comparison for the dashboard
|
|
151
348
|
const newComparison = {
|
|
349
|
+
id: comparison.id,
|
|
350
|
+
// Include unique ID for variant identification
|
|
152
351
|
name: comparison.name,
|
|
153
352
|
originalName: name,
|
|
154
353
|
status: comparison.status,
|
|
@@ -157,7 +356,10 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
157
356
|
diff: convertPathToUrl(comparison.diff),
|
|
158
357
|
diffPercentage: comparison.diffPercentage,
|
|
159
358
|
threshold: comparison.threshold,
|
|
160
|
-
properties:
|
|
359
|
+
properties: extractedProperties,
|
|
360
|
+
// Use extracted properties with top-level viewport_width/browser
|
|
361
|
+
signature: comparison.signature,
|
|
362
|
+
// Include signature for debugging
|
|
161
363
|
timestamp: Date.now()
|
|
162
364
|
};
|
|
163
365
|
|
|
@@ -223,16 +425,14 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
223
425
|
const getResults = async () => {
|
|
224
426
|
return await tddService.printResults();
|
|
225
427
|
};
|
|
226
|
-
const acceptBaseline = async
|
|
428
|
+
const acceptBaseline = async comparisonId => {
|
|
227
429
|
try {
|
|
228
|
-
logger.debug(`Accepting baseline for screenshot: ${screenshotName}`);
|
|
229
|
-
|
|
230
430
|
// Use TDD service to accept the baseline
|
|
231
|
-
const result = await tddService.acceptBaseline(
|
|
431
|
+
const result = await tddService.acceptBaseline(comparisonId);
|
|
232
432
|
|
|
233
433
|
// Read current report data and update the comparison status
|
|
234
434
|
const reportData = readReportData();
|
|
235
|
-
const comparison = reportData.comparisons.find(c => c.
|
|
435
|
+
const comparison = reportData.comparisons.find(c => c.id === comparisonId);
|
|
236
436
|
if (comparison) {
|
|
237
437
|
// Update the comparison to passed status
|
|
238
438
|
const updatedComparison = {
|
|
@@ -242,14 +442,13 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
242
442
|
diff: null
|
|
243
443
|
};
|
|
244
444
|
updateComparison(updatedComparison);
|
|
245
|
-
logger.debug('Comparison updated in report-data.json');
|
|
246
445
|
} else {
|
|
247
|
-
logger.error(`Comparison not found in report data for: ${
|
|
446
|
+
logger.error(`Comparison not found in report data for ID: ${comparisonId}`);
|
|
248
447
|
}
|
|
249
|
-
logger.info(`Baseline accepted for ${
|
|
448
|
+
logger.info(`Baseline accepted for comparison ${comparisonId}`);
|
|
250
449
|
return result;
|
|
251
450
|
} catch (error) {
|
|
252
|
-
logger.error(`Failed to accept baseline for ${
|
|
451
|
+
logger.error(`Failed to accept baseline for ${comparisonId}:`, error);
|
|
253
452
|
throw error;
|
|
254
453
|
}
|
|
255
454
|
};
|
|
@@ -262,7 +461,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
262
461
|
// Accept all failed or new comparisons
|
|
263
462
|
for (const comparison of reportData.comparisons) {
|
|
264
463
|
if (comparison.status === 'failed' || comparison.status === 'new') {
|
|
265
|
-
await tddService.acceptBaseline(comparison.
|
|
464
|
+
await tddService.acceptBaseline(comparison.id);
|
|
266
465
|
|
|
267
466
|
// Update the comparison to passed status
|
|
268
467
|
updateComparison({
|
|
@@ -363,8 +562,10 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
363
562
|
const freshReportData = {
|
|
364
563
|
timestamp: Date.now(),
|
|
365
564
|
comparisons: [],
|
|
565
|
+
groups: [],
|
|
366
566
|
summary: {
|
|
367
567
|
total: 0,
|
|
568
|
+
groups: 0,
|
|
368
569
|
passed: 0,
|
|
369
570
|
failed: 0,
|
|
370
571
|
errors: 0
|
|
@@ -233,21 +233,21 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
233
233
|
}
|
|
234
234
|
try {
|
|
235
235
|
const {
|
|
236
|
-
|
|
236
|
+
id
|
|
237
237
|
} = await parseRequestBody(req);
|
|
238
|
-
if (!
|
|
238
|
+
if (!id) {
|
|
239
239
|
res.statusCode = 400;
|
|
240
240
|
res.end(JSON.stringify({
|
|
241
|
-
error: '
|
|
241
|
+
error: 'Comparison ID required'
|
|
242
242
|
}));
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
|
-
await screenshotHandler.acceptBaseline(
|
|
245
|
+
await screenshotHandler.acceptBaseline(id);
|
|
246
246
|
res.setHeader('Content-Type', 'application/json');
|
|
247
247
|
res.statusCode = 200;
|
|
248
248
|
res.end(JSON.stringify({
|
|
249
249
|
success: true,
|
|
250
|
-
message: `Baseline accepted for ${
|
|
250
|
+
message: `Baseline accepted for comparison ${id}`
|
|
251
251
|
}));
|
|
252
252
|
} catch (error) {
|
|
253
253
|
logger.error('Error accepting baseline:', error);
|
|
@@ -368,19 +368,19 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
368
368
|
try {
|
|
369
369
|
const body = await parseRequestBody(req);
|
|
370
370
|
const {
|
|
371
|
-
|
|
371
|
+
id
|
|
372
372
|
} = body;
|
|
373
|
-
if (!
|
|
373
|
+
if (!id) {
|
|
374
374
|
res.statusCode = 400;
|
|
375
375
|
res.end(JSON.stringify({
|
|
376
|
-
error: '
|
|
376
|
+
error: 'comparison ID is required'
|
|
377
377
|
}));
|
|
378
378
|
return;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
// Call the screenshot handler's accept baseline method if it exists
|
|
382
382
|
if (screenshotHandler.acceptBaseline) {
|
|
383
|
-
const result = await screenshotHandler.acceptBaseline(
|
|
383
|
+
const result = await screenshotHandler.acceptBaseline(id);
|
|
384
384
|
res.statusCode = 200;
|
|
385
385
|
res.end(JSON.stringify({
|
|
386
386
|
success: true,
|