@vizzly-testing/cli 0.7.2 → 0.9.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/README.md +27 -14
- package/dist/cli.js +25 -1
- package/dist/client/index.js +77 -11
- package/dist/commands/init.js +23 -17
- package/dist/commands/tdd-daemon.js +312 -0
- package/dist/commands/tdd.js +45 -14
- package/dist/commands/upload.js +3 -1
- package/dist/reporter/reporter-bundle.css +1 -0
- package/dist/reporter/reporter-bundle.iife.js +57 -0
- package/dist/sdk/index.js +1 -1
- package/dist/server/handlers/api-handler.js +98 -30
- package/dist/server/handlers/tdd-handler.js +264 -77
- package/dist/server/http-server.js +358 -15
- package/dist/services/api-service.js +6 -1
- package/dist/services/html-report-generator.js +77 -0
- package/dist/services/report-generator/report.css +56 -0
- package/dist/services/screenshot-server.js +6 -3
- package/dist/services/server-manager.js +2 -9
- package/dist/services/tdd-service.js +188 -25
- package/dist/services/test-runner.js +43 -1
- package/dist/types/commands/tdd-daemon.d.ts +18 -0
- package/dist/types/container/index.d.ts +1 -3
- package/dist/types/reporter/src/components/app-router.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/comparison-actions.d.ts +5 -0
- package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-list.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/view-mode-selector.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-filters.d.ts +16 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +5 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +4 -0
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +8 -0
- package/dist/types/reporter/src/components/ui/smart-image.d.ts +7 -0
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +5 -0
- package/dist/types/reporter/src/components/ui/toast.d.ts +4 -0
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +6 -0
- package/dist/types/reporter/src/components/views/stats-view.d.ts +6 -0
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +5 -0
- package/dist/types/reporter/src/hooks/use-comparison-filters.d.ts +20 -0
- package/dist/types/reporter/src/hooks/use-image-loader.d.ts +1 -0
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +7 -0
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +9 -0
- package/dist/types/reporter/src/main.d.ts +1 -0
- package/dist/types/reporter/src/services/api-client.d.ts +4 -0
- package/dist/types/reporter/src/utils/comparison-helpers.d.ts +16 -0
- package/dist/types/reporter/src/utils/constants.d.ts +37 -0
- package/dist/types/reporter/vite.config.d.ts +2 -0
- package/dist/types/reporter/vite.dev.config.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +2 -3
- package/dist/types/server/handlers/api-handler.d.ts +5 -14
- package/dist/types/server/handlers/tdd-handler.d.ts +18 -17
- package/dist/types/server/http-server.d.ts +2 -1
- package/dist/types/services/base-service.d.ts +1 -2
- package/dist/types/services/html-report-generator.d.ts +3 -3
- package/dist/types/services/screenshot-server.d.ts +1 -1
- package/dist/types/services/server-manager.d.ts +25 -35
- package/dist/types/services/tdd-service.d.ts +7 -1
- package/dist/types/services/test-runner.d.ts +6 -1
- package/dist/types/utils/build-history.d.ts +16 -0
- package/dist/types/utils/config-loader.d.ts +1 -1
- package/dist/types/utils/console-ui.d.ts +1 -1
- package/dist/types/utils/git.d.ts +4 -4
- package/dist/types/utils/security.d.ts +2 -1
- package/dist/utils/build-history.js +103 -0
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/console-ui.js +2 -1
- package/dist/utils/environment-config.js +1 -1
- package/dist/utils/security.js +14 -5
- package/docs/api-reference.md +2 -4
- package/docs/doctor-command.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/tdd-mode.md +176 -112
- package/package.json +17 -4
package/dist/sdk/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import { VizzlyError } from '../errors/vizzly-error.js';
|
|
|
30
30
|
*
|
|
31
31
|
* const vizzly = await createVizzly({
|
|
32
32
|
* apiKey: process.env.VIZZLY_TOKEN,
|
|
33
|
-
* apiUrl: 'https://vizzly.dev',
|
|
33
|
+
* apiUrl: 'https://app.vizzly.dev',
|
|
34
34
|
* server: {
|
|
35
35
|
* port: 3003,
|
|
36
36
|
* enabled: true
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
2
|
import { createServiceLogger } from '../../utils/logger-factory.js';
|
|
3
3
|
const logger = createServiceLogger('API-HANDLER');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* API Handler - Non-blocking screenshot upload
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
10
|
+
* │ Test Suite │
|
|
11
|
+
* │ ↓ vizzlyScreenshot() │
|
|
12
|
+
* │ ↓ HTTP POST to localhost │
|
|
13
|
+
* │ ↓ │
|
|
14
|
+
* │ Screenshot Server │
|
|
15
|
+
* │ ↓ handleScreenshot() │
|
|
16
|
+
* │ ├─→ Convert base64 to Buffer │
|
|
17
|
+
* │ ├─→ Fire upload promise (NO AWAIT) ─────┐ │
|
|
18
|
+
* │ └─→ Return 200 immediately │ │
|
|
19
|
+
* │ │ │
|
|
20
|
+
* │ Test continues (NO BLOCKING) ✓ │ │
|
|
21
|
+
* │ ↓ │
|
|
22
|
+
* │ Background Upload │
|
|
23
|
+
* │ (to Vizzly API) │
|
|
24
|
+
* │ ↓ │
|
|
25
|
+
* │ Promise resolves/rejects │
|
|
26
|
+
* │ │
|
|
27
|
+
* │ Build Finalization │
|
|
28
|
+
* │ ↓ flush() │
|
|
29
|
+
* │ └─→ await Promise.allSettled(uploadPromises) │
|
|
30
|
+
* │ ↓ │
|
|
31
|
+
* │ All uploads complete ✓ │
|
|
32
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
33
|
+
*/
|
|
34
|
+
|
|
4
35
|
export const createApiHandler = apiService => {
|
|
5
36
|
let vizzlyDisabled = false;
|
|
6
37
|
let screenshotCount = 0;
|
|
38
|
+
let uploadPromises = [];
|
|
7
39
|
const handleScreenshot = async (buildId, name, image, properties = {}) => {
|
|
8
40
|
if (vizzlyDisabled) {
|
|
9
41
|
logger.debug(`Screenshot captured (Vizzly disabled): ${name}`);
|
|
@@ -17,14 +49,9 @@ export const createApiHandler = apiService => {
|
|
|
17
49
|
}
|
|
18
50
|
};
|
|
19
51
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
body: {
|
|
24
|
-
error: 'Build ID is required for screenshot upload'
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
}
|
|
52
|
+
|
|
53
|
+
// buildId is optional - API service will handle it appropriately
|
|
54
|
+
|
|
28
55
|
if (!apiService) {
|
|
29
56
|
return {
|
|
30
57
|
statusCode: 500,
|
|
@@ -33,51 +60,92 @@ export const createApiHandler = apiService => {
|
|
|
33
60
|
}
|
|
34
61
|
};
|
|
35
62
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
const imageBuffer = Buffer.from(image, 'base64');
|
|
64
|
+
screenshotCount++;
|
|
65
|
+
|
|
66
|
+
// Fire upload in background - DON'T AWAIT!
|
|
67
|
+
const uploadPromise = apiService.uploadScreenshot(buildId, name, imageBuffer, properties ?? {}).then(result => {
|
|
39
68
|
if (result.skipped) {
|
|
40
69
|
logger.debug(`Screenshot already exists, skipped: ${name}`);
|
|
41
70
|
} else {
|
|
42
71
|
logger.debug(`Screenshot uploaded: ${name}`);
|
|
43
72
|
}
|
|
44
|
-
if (!result.skipped) {
|
|
45
|
-
screenshotCount++;
|
|
46
|
-
}
|
|
47
73
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
name,
|
|
52
|
-
skipped: result.skipped,
|
|
53
|
-
count: screenshotCount
|
|
54
|
-
}
|
|
74
|
+
success: true,
|
|
75
|
+
name,
|
|
76
|
+
result
|
|
55
77
|
};
|
|
56
|
-
}
|
|
78
|
+
}).catch(uploadError => {
|
|
57
79
|
logger.error(`❌ Failed to upload screenshot ${name}:`, uploadError.message);
|
|
58
80
|
vizzlyDisabled = true;
|
|
59
81
|
const disabledMessage = '⚠️ Vizzly disabled due to upload error - continuing tests without visual testing';
|
|
60
82
|
logger.warn(disabledMessage);
|
|
61
83
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
name,
|
|
66
|
-
disabled: true,
|
|
67
|
-
message: disabledMessage
|
|
68
|
-
}
|
|
84
|
+
success: false,
|
|
85
|
+
name,
|
|
86
|
+
error: uploadError
|
|
69
87
|
};
|
|
70
|
-
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Collect promise for later flushing
|
|
91
|
+
uploadPromises.push(uploadPromise);
|
|
92
|
+
|
|
93
|
+
// Return immediately - test continues without waiting!
|
|
94
|
+
return {
|
|
95
|
+
statusCode: 200,
|
|
96
|
+
body: {
|
|
97
|
+
success: true,
|
|
98
|
+
name,
|
|
99
|
+
count: screenshotCount
|
|
100
|
+
}
|
|
101
|
+
};
|
|
71
102
|
};
|
|
72
103
|
const getScreenshotCount = () => screenshotCount;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Wait for all background uploads to complete
|
|
107
|
+
* Call this before build finalization to ensure all uploads finish
|
|
108
|
+
*/
|
|
109
|
+
const flush = async () => {
|
|
110
|
+
if (uploadPromises.length === 0) {
|
|
111
|
+
logger.debug('No uploads to flush');
|
|
112
|
+
return {
|
|
113
|
+
uploaded: 0,
|
|
114
|
+
failed: 0,
|
|
115
|
+
total: 0
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
logger.debug(`Flushing ${uploadPromises.length} background uploads...`);
|
|
119
|
+
const results = await Promise.allSettled(uploadPromises);
|
|
120
|
+
let uploaded = 0;
|
|
121
|
+
let failed = 0;
|
|
122
|
+
results.forEach(result => {
|
|
123
|
+
if (result.status === 'fulfilled' && result.value.success) {
|
|
124
|
+
uploaded++;
|
|
125
|
+
} else {
|
|
126
|
+
failed++;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
logger.debug(`Upload flush complete: ${uploaded} uploaded, ${failed} failed`);
|
|
130
|
+
|
|
131
|
+
// Clear promises array
|
|
132
|
+
uploadPromises = [];
|
|
133
|
+
return {
|
|
134
|
+
uploaded,
|
|
135
|
+
failed,
|
|
136
|
+
total: results.length
|
|
137
|
+
};
|
|
138
|
+
};
|
|
73
139
|
const cleanup = () => {
|
|
74
140
|
vizzlyDisabled = false;
|
|
75
141
|
screenshotCount = 0;
|
|
142
|
+
uploadPromises = [];
|
|
76
143
|
logger.debug('API handler cleanup completed');
|
|
77
144
|
};
|
|
78
145
|
return {
|
|
79
146
|
handleScreenshot,
|
|
80
147
|
getScreenshotCount,
|
|
148
|
+
flush,
|
|
81
149
|
cleanup
|
|
82
150
|
};
|
|
83
151
|
};
|
|
@@ -1,57 +1,101 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
2
4
|
import { createServiceLogger } from '../../utils/logger-factory.js';
|
|
3
5
|
import { TddService } from '../../services/tdd-service.js';
|
|
4
|
-
import { colors } from '../../utils/colors.js';
|
|
5
6
|
import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
|
|
6
7
|
const logger = createServiceLogger('TDD-HANDLER');
|
|
7
8
|
export const createTddHandler = (config, workingDir, baselineBuild, baselineComparison, setBaseline = false) => {
|
|
8
9
|
const tddService = new TddService(config, workingDir, setBaseline);
|
|
9
|
-
const
|
|
10
|
+
const reportPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
11
|
+
const readReportData = () => {
|
|
12
|
+
try {
|
|
13
|
+
if (!existsSync(reportPath)) {
|
|
14
|
+
return {
|
|
15
|
+
timestamp: Date.now(),
|
|
16
|
+
comparisons: [],
|
|
17
|
+
summary: {
|
|
18
|
+
total: 0,
|
|
19
|
+
passed: 0,
|
|
20
|
+
failed: 0,
|
|
21
|
+
errors: 0
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const data = readFileSync(reportPath, 'utf8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error('Failed to read report data:', error);
|
|
29
|
+
return {
|
|
30
|
+
timestamp: Date.now(),
|
|
31
|
+
comparisons: [],
|
|
32
|
+
summary: {
|
|
33
|
+
total: 0,
|
|
34
|
+
passed: 0,
|
|
35
|
+
failed: 0,
|
|
36
|
+
errors: 0
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const updateComparison = newComparison => {
|
|
42
|
+
try {
|
|
43
|
+
const reportData = readReportData();
|
|
44
|
+
|
|
45
|
+
// Find existing comparison with same name and replace it, or add new one
|
|
46
|
+
const existingIndex = reportData.comparisons.findIndex(c => c.name === newComparison.name);
|
|
47
|
+
if (existingIndex >= 0) {
|
|
48
|
+
reportData.comparisons[existingIndex] = newComparison;
|
|
49
|
+
logger.debug(`Updated comparison for ${newComparison.name}`);
|
|
50
|
+
} else {
|
|
51
|
+
reportData.comparisons.push(newComparison);
|
|
52
|
+
logger.debug(`Added new comparison for ${newComparison.name}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update summary
|
|
56
|
+
reportData.timestamp = Date.now();
|
|
57
|
+
reportData.summary = {
|
|
58
|
+
total: reportData.comparisons.length,
|
|
59
|
+
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created').length,
|
|
60
|
+
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
61
|
+
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
62
|
+
};
|
|
63
|
+
writeFileSync(reportPath, JSON.stringify(reportData, null, 2));
|
|
64
|
+
logger.debug('Report data saved to report-data.json');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
logger.error('Failed to update comparison:', error);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
10
69
|
const initialize = async () => {
|
|
11
|
-
logger.
|
|
70
|
+
logger.debug('TDD mode enabled - setting up local comparison');
|
|
12
71
|
|
|
13
72
|
// In baseline update mode, skip all baseline loading/downloading
|
|
14
73
|
if (setBaseline) {
|
|
15
|
-
logger.
|
|
74
|
+
logger.debug('Ready for new baseline creation - all screenshots will be treated as new baselines');
|
|
16
75
|
return;
|
|
17
76
|
}
|
|
18
77
|
|
|
19
78
|
// Check if we have baseline override flags that should force a fresh download
|
|
20
79
|
const shouldForceDownload = (baselineBuild || baselineComparison) && config.apiKey;
|
|
21
80
|
if (shouldForceDownload) {
|
|
22
|
-
logger.
|
|
81
|
+
logger.debug('Baseline override specified, downloading fresh baselines from Vizzly');
|
|
23
82
|
await tddService.downloadBaselines(config.build?.environment || 'test', config.build?.branch || null, baselineBuild, baselineComparison);
|
|
24
83
|
return;
|
|
25
84
|
}
|
|
26
85
|
const baseline = await tddService.loadBaseline();
|
|
27
86
|
if (!baseline) {
|
|
28
|
-
if
|
|
29
|
-
|
|
87
|
+
// Only download baselines if explicitly requested via baseline flags
|
|
88
|
+
if ((baselineBuild || baselineComparison) && config.apiKey) {
|
|
89
|
+
logger.debug('No local baseline found, downloading from Vizzly');
|
|
30
90
|
await tddService.downloadBaselines(config.build?.environment || 'test', config.build?.branch || null, baselineBuild, baselineComparison);
|
|
31
91
|
} else {
|
|
32
|
-
logger.
|
|
92
|
+
logger.debug('No local baseline found - will create new baselines from first screenshots');
|
|
33
93
|
}
|
|
34
94
|
} else {
|
|
35
|
-
logger.
|
|
95
|
+
logger.debug(`Using existing baseline: ${baseline.buildName}`);
|
|
36
96
|
}
|
|
37
97
|
};
|
|
38
|
-
const registerBuild = buildId => {
|
|
39
|
-
builds.set(buildId, {
|
|
40
|
-
id: buildId,
|
|
41
|
-
name: `TDD Build ${buildId}`,
|
|
42
|
-
branch: 'current',
|
|
43
|
-
environment: 'test',
|
|
44
|
-
screenshots: [],
|
|
45
|
-
createdAt: Date.now()
|
|
46
|
-
});
|
|
47
|
-
logger.debug(`Registered TDD build: ${buildId}`);
|
|
48
|
-
};
|
|
49
98
|
const handleScreenshot = async (buildId, name, image, properties = {}) => {
|
|
50
|
-
const build = builds.get(buildId);
|
|
51
|
-
if (!build) {
|
|
52
|
-
throw new Error(`Build ${buildId} not found`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
99
|
// Validate and sanitize screenshot name
|
|
56
100
|
let sanitizedName;
|
|
57
101
|
try {
|
|
@@ -81,42 +125,44 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
81
125
|
}
|
|
82
126
|
};
|
|
83
127
|
}
|
|
128
|
+
const imageBuffer = Buffer.from(image, 'base64');
|
|
129
|
+
logger.debug(`Received screenshot: ${name}`);
|
|
130
|
+
logger.debug(`Image size: ${imageBuffer.length} bytes`);
|
|
131
|
+
logger.debug(`Properties: ${JSON.stringify(validatedProperties)}`);
|
|
84
132
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
// Add browser to name if provided (already validated)
|
|
90
|
-
if (validatedProperties.browser) {
|
|
91
|
-
relevantProps.push(validatedProperties.browser);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Add viewport info if provided (already validated)
|
|
95
|
-
if (validatedProperties.viewport && validatedProperties.viewport.width && validatedProperties.viewport.height) {
|
|
96
|
-
relevantProps.push(`${validatedProperties.viewport.width}x${validatedProperties.viewport.height}`);
|
|
97
|
-
}
|
|
133
|
+
// Use the sanitized name as-is (no modification with browser/viewport)
|
|
134
|
+
// Baseline matching uses signature logic (name + viewport_width + browser)
|
|
135
|
+
const comparison = await tddService.compareScreenshot(sanitizedName, imageBuffer, validatedProperties);
|
|
136
|
+
logger.debug(`Comparison result: ${comparison.status}`);
|
|
98
137
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
logger.warn(`Combined screenshot name invalid (${error.message}), using base name: ${uniqueName}`);
|
|
138
|
+
// Convert absolute file paths to web-accessible URLs
|
|
139
|
+
const convertPathToUrl = filePath => {
|
|
140
|
+
if (!filePath) return null;
|
|
141
|
+
// Convert absolute path to relative path from .vizzly directory
|
|
142
|
+
const vizzlyDir = join(workingDir, '.vizzly');
|
|
143
|
+
if (filePath.startsWith(vizzlyDir)) {
|
|
144
|
+
const relativePath = filePath.substring(vizzlyDir.length + 1);
|
|
145
|
+
return `/images/${relativePath}`;
|
|
108
146
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
return filePath;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Record the comparison for the dashboard
|
|
151
|
+
const newComparison = {
|
|
152
|
+
name: comparison.name,
|
|
112
153
|
originalName: name,
|
|
113
|
-
|
|
154
|
+
status: comparison.status,
|
|
155
|
+
baseline: convertPathToUrl(comparison.baseline),
|
|
156
|
+
current: convertPathToUrl(comparison.current),
|
|
157
|
+
diff: convertPathToUrl(comparison.diff),
|
|
158
|
+
diffPercentage: comparison.diffPercentage,
|
|
159
|
+
threshold: comparison.threshold,
|
|
114
160
|
properties: validatedProperties,
|
|
115
161
|
timestamp: Date.now()
|
|
116
162
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
163
|
+
|
|
164
|
+
// Update comparison in report data file
|
|
165
|
+
updateComparison(newComparison);
|
|
120
166
|
if (comparison.status === 'failed') {
|
|
121
167
|
return {
|
|
122
168
|
statusCode: 422,
|
|
@@ -174,39 +220,180 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
174
220
|
}
|
|
175
221
|
};
|
|
176
222
|
};
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
return build ? build.screenshots.length : 0;
|
|
223
|
+
const getResults = async () => {
|
|
224
|
+
return await tddService.printResults();
|
|
180
225
|
};
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
226
|
+
const acceptBaseline = async screenshotName => {
|
|
227
|
+
try {
|
|
228
|
+
logger.debug(`Accepting baseline for screenshot: ${screenshotName}`);
|
|
229
|
+
|
|
230
|
+
// Use TDD service to accept the baseline
|
|
231
|
+
const result = await tddService.acceptBaseline(screenshotName);
|
|
232
|
+
|
|
233
|
+
// Read current report data and update the comparison status
|
|
234
|
+
const reportData = readReportData();
|
|
235
|
+
const comparison = reportData.comparisons.find(c => c.name === screenshotName);
|
|
236
|
+
if (comparison) {
|
|
237
|
+
// Update the comparison to passed status
|
|
238
|
+
const updatedComparison = {
|
|
239
|
+
...comparison,
|
|
240
|
+
status: 'passed',
|
|
241
|
+
diffPercentage: 0,
|
|
242
|
+
diff: null
|
|
243
|
+
};
|
|
244
|
+
updateComparison(updatedComparison);
|
|
245
|
+
logger.debug('Comparison updated in report-data.json');
|
|
246
|
+
} else {
|
|
247
|
+
logger.error(`Comparison not found in report data for: ${screenshotName}`);
|
|
248
|
+
}
|
|
249
|
+
logger.info(`Baseline accepted for ${screenshotName}`);
|
|
250
|
+
return result;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.error(`Failed to accept baseline for ${screenshotName}:`, error);
|
|
253
|
+
throw error;
|
|
185
254
|
}
|
|
186
|
-
|
|
187
|
-
|
|
255
|
+
};
|
|
256
|
+
const acceptAllBaselines = async () => {
|
|
257
|
+
try {
|
|
258
|
+
logger.debug('Accepting all baselines');
|
|
259
|
+
const reportData = readReportData();
|
|
260
|
+
let acceptedCount = 0;
|
|
261
|
+
|
|
262
|
+
// Accept all failed or new comparisons
|
|
263
|
+
for (const comparison of reportData.comparisons) {
|
|
264
|
+
if (comparison.status === 'failed' || comparison.status === 'new') {
|
|
265
|
+
await tddService.acceptBaseline(comparison.name);
|
|
266
|
+
|
|
267
|
+
// Update the comparison to passed status
|
|
268
|
+
updateComparison({
|
|
269
|
+
...comparison,
|
|
270
|
+
status: 'passed',
|
|
271
|
+
diffPercentage: 0,
|
|
272
|
+
diff: null
|
|
273
|
+
});
|
|
274
|
+
acceptedCount++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
logger.info(`Accepted ${acceptedCount} baselines`);
|
|
278
|
+
return {
|
|
279
|
+
count: acceptedCount
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
logger.error('Failed to accept all baselines:', error);
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const resetBaselines = async () => {
|
|
287
|
+
try {
|
|
288
|
+
logger.debug('Resetting baselines');
|
|
289
|
+
const reportData = readReportData();
|
|
290
|
+
let deletedBaselines = 0;
|
|
291
|
+
let deletedCurrents = 0;
|
|
292
|
+
let deletedDiffs = 0;
|
|
293
|
+
|
|
294
|
+
// Delete all baseline, current, and diff images
|
|
295
|
+
for (const comparison of reportData.comparisons) {
|
|
296
|
+
// Delete baseline image if it exists
|
|
297
|
+
if (comparison.baseline) {
|
|
298
|
+
const baselinePath = join(workingDir, '.vizzly', comparison.baseline.replace('/images/', ''));
|
|
299
|
+
if (existsSync(baselinePath)) {
|
|
300
|
+
try {
|
|
301
|
+
const {
|
|
302
|
+
unlinkSync
|
|
303
|
+
} = await import('fs');
|
|
304
|
+
unlinkSync(baselinePath);
|
|
305
|
+
deletedBaselines++;
|
|
306
|
+
logger.debug(`Deleted baseline for ${comparison.name}`);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
logger.warn(`Failed to delete baseline for ${comparison.name}: ${error.message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Delete current screenshot if it exists
|
|
314
|
+
if (comparison.current) {
|
|
315
|
+
const currentPath = join(workingDir, '.vizzly', comparison.current.replace('/images/', ''));
|
|
316
|
+
if (existsSync(currentPath)) {
|
|
317
|
+
try {
|
|
318
|
+
const {
|
|
319
|
+
unlinkSync
|
|
320
|
+
} = await import('fs');
|
|
321
|
+
unlinkSync(currentPath);
|
|
322
|
+
deletedCurrents++;
|
|
323
|
+
logger.debug(`Deleted current screenshot for ${comparison.name}`);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.warn(`Failed to delete current screenshot for ${comparison.name}: ${error.message}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Delete diff image if it exists
|
|
331
|
+
if (comparison.diff) {
|
|
332
|
+
const diffPath = join(workingDir, '.vizzly', comparison.diff.replace('/images/', ''));
|
|
333
|
+
if (existsSync(diffPath)) {
|
|
334
|
+
try {
|
|
335
|
+
const {
|
|
336
|
+
unlinkSync
|
|
337
|
+
} = await import('fs');
|
|
338
|
+
unlinkSync(diffPath);
|
|
339
|
+
deletedDiffs++;
|
|
340
|
+
logger.debug(`Deleted diff for ${comparison.name}`);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
logger.warn(`Failed to delete diff for ${comparison.name}: ${error.message}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Delete baseline metadata
|
|
349
|
+
const metadataPath = join(workingDir, '.vizzly', 'baselines', 'metadata.json');
|
|
350
|
+
if (existsSync(metadataPath)) {
|
|
351
|
+
try {
|
|
352
|
+
const {
|
|
353
|
+
unlinkSync
|
|
354
|
+
} = await import('fs');
|
|
355
|
+
unlinkSync(metadataPath);
|
|
356
|
+
logger.debug('Deleted baseline metadata');
|
|
357
|
+
} catch (error) {
|
|
358
|
+
logger.warn(`Failed to delete baseline metadata: ${error.message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Clear the report data entirely - fresh start
|
|
363
|
+
const freshReportData = {
|
|
364
|
+
timestamp: Date.now(),
|
|
365
|
+
comparisons: [],
|
|
366
|
+
summary: {
|
|
367
|
+
total: 0,
|
|
368
|
+
passed: 0,
|
|
369
|
+
failed: 0,
|
|
370
|
+
errors: 0
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
writeFileSync(reportPath, JSON.stringify(freshReportData, null, 2));
|
|
374
|
+
logger.info(`Baselines reset - ${deletedBaselines} baselines deleted, ${deletedCurrents} current screenshots deleted, ${deletedDiffs} diffs deleted`);
|
|
375
|
+
return {
|
|
376
|
+
success: true,
|
|
377
|
+
deletedBaselines,
|
|
378
|
+
deletedCurrents,
|
|
379
|
+
deletedDiffs
|
|
380
|
+
};
|
|
381
|
+
} catch (error) {
|
|
382
|
+
logger.error('Failed to reset baselines:', error);
|
|
383
|
+
throw error;
|
|
188
384
|
}
|
|
189
|
-
const results = await tddService.printResults();
|
|
190
|
-
builds.delete(buildId);
|
|
191
|
-
return {
|
|
192
|
-
id: buildId,
|
|
193
|
-
name: build.name,
|
|
194
|
-
tddMode: true,
|
|
195
|
-
results,
|
|
196
|
-
url: null,
|
|
197
|
-
passed: results.failed === 0 && results.errors === 0
|
|
198
|
-
};
|
|
199
385
|
};
|
|
200
386
|
const cleanup = () => {
|
|
201
|
-
|
|
387
|
+
// Report data is persisted to file, no in-memory cleanup needed
|
|
202
388
|
logger.debug('TDD handler cleanup completed');
|
|
203
389
|
};
|
|
204
390
|
return {
|
|
205
391
|
initialize,
|
|
206
|
-
registerBuild,
|
|
207
392
|
handleScreenshot,
|
|
208
|
-
|
|
209
|
-
|
|
393
|
+
getResults,
|
|
394
|
+
acceptBaseline,
|
|
395
|
+
acceptAllBaselines,
|
|
396
|
+
resetBaselines,
|
|
210
397
|
cleanup
|
|
211
398
|
};
|
|
212
399
|
};
|