@vizzly-testing/cli 0.35.3-beta.2 → 0.35.3-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/endpoints.js +1 -46
- package/dist/api/index.js +1 -1
- package/dist/cli.js +4 -4
- package/dist/commands/api.js +3 -3
- package/dist/commands/builds.js +12 -15
- package/dist/commands/comparisons.js +7 -9
- package/dist/commands/context.js +3 -24
- package/dist/commands/review.js +122 -35
- package/dist/commands/run.js +1 -3
- package/dist/commands/status.js +35 -79
- package/dist/commands/upload.js +1 -1
- package/dist/context/local-workspace-provider.js +36 -126
- package/dist/reporter/reporter-bundle.iife.js +49 -81
- package/dist/server/handlers/tdd-handler.js +2 -7
- package/dist/server/routers/dashboard.js +5 -1
- package/dist/tdd/runtime-context.js +0 -2
- package/dist/tdd/services/comparison-service.js +4 -68
- package/dist/tdd/tdd-service.js +7 -205
- package/dist/types/index.d.ts +1 -1
- package/dist/uploader/core.js +7 -8
- package/dist/utils/visual-context-normalizers.js +86 -139
- package/package.json +1 -1
- package/skills/vizzly/SKILL.md +4 -4
- package/skills/vizzly/references/dynamic-content.md +2 -2
- package/dist/tdd/core/hotspot-coverage.js +0 -112
- package/dist/tdd/core/region-coverage.js +0 -203
- package/dist/tdd/metadata/hotspot-metadata.js +0 -93
- package/dist/tdd/metadata/region-metadata.js +0 -56
|
@@ -450,9 +450,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
450
450
|
warnings,
|
|
451
451
|
originalName: name,
|
|
452
452
|
timestamp: Date.now(),
|
|
453
|
-
|
|
454
|
-
hasDiffClusters: comparison.diffClusters?.length > 0,
|
|
455
|
-
hasConfirmedRegions: comparison.confirmedRegions?.length > 0
|
|
453
|
+
hasDiffClusters: comparison.diffClusters?.length > 0
|
|
456
454
|
};
|
|
457
455
|
|
|
458
456
|
// Update lightweight comparison in report-data.json (triggers SSE broadcast)
|
|
@@ -462,10 +460,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
462
460
|
updateComparisonDetails(comparison.id, {
|
|
463
461
|
diffClusters: comparison.diffClusters,
|
|
464
462
|
intensityStats: comparison.intensityStats,
|
|
465
|
-
boundingBox: comparison.boundingBox
|
|
466
|
-
regionAnalysis: comparison.regionAnalysis,
|
|
467
|
-
hotspotAnalysis: comparison.hotspotAnalysis,
|
|
468
|
-
confirmedRegions: comparison.confirmedRegions
|
|
463
|
+
boundingBox: comparison.boundingBox
|
|
469
464
|
});
|
|
470
465
|
|
|
471
466
|
// Log screenshot event for menubar
|
|
@@ -10,6 +10,10 @@ import { sendError, sendHtml, sendSuccess } from '../middleware/response.js';
|
|
|
10
10
|
|
|
11
11
|
// SPA routes that should serve the dashboard HTML
|
|
12
12
|
const SPA_ROUTES = ['/', '/stats', '/settings', '/projects', '/builds'];
|
|
13
|
+
let COMPARISON_DETAIL_FIELDS = new Set(['boundingBox', 'diffClusters', 'intensityStats']);
|
|
14
|
+
function presentComparisonDetails(details = {}) {
|
|
15
|
+
return Object.fromEntries(Object.entries(details).filter(([field]) => COMPARISON_DETAIL_FIELDS.has(field)));
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Create dashboard router
|
|
@@ -99,7 +103,7 @@ export function createDashboardRouter(context) {
|
|
|
99
103
|
if (heavy) {
|
|
100
104
|
comparison = {
|
|
101
105
|
...comparison,
|
|
102
|
-
...heavy
|
|
106
|
+
...presentComparisonDetails(heavy)
|
|
103
107
|
};
|
|
104
108
|
}
|
|
105
109
|
} catch (error) {
|
|
@@ -9,7 +9,6 @@ export function buildTddDependencyOps(deps = {}, defaults = {}) {
|
|
|
9
9
|
colors = defaults.colors,
|
|
10
10
|
validatePathSecurity = defaults.validatePathSecurity,
|
|
11
11
|
initializeDirectories = defaults.initializeDirectories,
|
|
12
|
-
calculateHotspotCoverage = defaults.calculateHotspotCoverage,
|
|
13
12
|
fs = {},
|
|
14
13
|
api = {},
|
|
15
14
|
metadata = {},
|
|
@@ -51,7 +50,6 @@ export function buildTddDependencyOps(deps = {}, defaults = {}) {
|
|
|
51
50
|
colors,
|
|
52
51
|
validatePathSecurity,
|
|
53
52
|
initializeDirectories,
|
|
54
|
-
calculateHotspotCoverage,
|
|
55
53
|
...fsOps,
|
|
56
54
|
...apiOps,
|
|
57
55
|
...metadataOps,
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { analyze } from '@vizzly-testing/honeydiff';
|
|
8
|
-
import { calculateHotspotCoverage } from '../core/hotspot-coverage.js';
|
|
9
|
-
import { calculateRegionCoverage, shouldAutoApproveFromRegions } from '../core/region-coverage.js';
|
|
10
8
|
import { generateComparisonId } from '../core/signature.js';
|
|
11
9
|
|
|
12
10
|
/**
|
|
@@ -149,8 +147,6 @@ export function buildNewComparison(params) {
|
|
|
149
147
|
* @param {number} params.threshold - Effective threshold used
|
|
150
148
|
* @param {number} params.minClusterSize - Effective minClusterSize used
|
|
151
149
|
* @param {Object} params.honeydiffResult - Result from honeydiff
|
|
152
|
-
* @param {Object} params.hotspotAnalysis - Hotspot data for this screenshot (optional)
|
|
153
|
-
* @param {Object} params.regionData - User-defined region data { confirmed: [], candidates: [] } (optional)
|
|
154
150
|
* @returns {Object} Comparison result
|
|
155
151
|
*/
|
|
156
152
|
export function buildFailedComparison(params) {
|
|
@@ -163,49 +159,13 @@ export function buildFailedComparison(params) {
|
|
|
163
159
|
properties,
|
|
164
160
|
threshold,
|
|
165
161
|
minClusterSize,
|
|
166
|
-
honeydiffResult
|
|
167
|
-
hotspotAnalysis,
|
|
168
|
-
regionData
|
|
162
|
+
honeydiffResult
|
|
169
163
|
} = params;
|
|
170
164
|
let diffClusters = honeydiffResult.diffClusters || [];
|
|
171
|
-
let ssimScore = honeydiffResult.perceptualScore ?? honeydiffResult.ssimScore ?? honeydiffResult.ssim_score ?? null;
|
|
172
|
-
let isFiltered = false;
|
|
173
|
-
let filterReason = 'pixel-diff';
|
|
174
|
-
|
|
175
|
-
// Region analysis (user-confirmed 2D boxes) - check FIRST (takes priority)
|
|
176
|
-
let regionCoverage = null;
|
|
177
|
-
let isRegionFiltered = false;
|
|
178
|
-
let confirmedRegions = regionData?.confirmed || [];
|
|
179
|
-
if (confirmedRegions.length > 0 && diffClusters.length > 0) {
|
|
180
|
-
regionCoverage = calculateRegionCoverage(diffClusters, confirmedRegions);
|
|
181
|
-
if (shouldAutoApproveFromRegions(confirmedRegions, regionCoverage, {
|
|
182
|
-
ssimScore
|
|
183
|
-
})) {
|
|
184
|
-
isRegionFiltered = true;
|
|
185
|
-
isFiltered = true;
|
|
186
|
-
filterReason = 'region-filtered';
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Hotspot analysis (1D Y-bands from historical data) - check SECOND
|
|
191
|
-
let hotspotCoverage = null;
|
|
192
|
-
let isHotspotFiltered = false;
|
|
193
|
-
if (!isFiltered && hotspotAnalysis && diffClusters.length > 0) {
|
|
194
|
-
hotspotCoverage = calculateHotspotCoverage(diffClusters, hotspotAnalysis);
|
|
195
|
-
|
|
196
|
-
// Check if diff should be filtered as hotspot noise
|
|
197
|
-
// (cloud uses confidence_score >= 70 which is >0.7 when normalized)
|
|
198
|
-
let isHighConfidence = hotspotAnalysis.confidence === 'high' || hotspotAnalysis.confidence_score !== undefined && hotspotAnalysis.confidence_score >= 70;
|
|
199
|
-
if (isHighConfidence && hotspotCoverage.coverage >= 0.8) {
|
|
200
|
-
isHotspotFiltered = true;
|
|
201
|
-
isFiltered = true;
|
|
202
|
-
filterReason = 'hotspot-filtered';
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
165
|
return {
|
|
206
166
|
id: generateComparisonId(signature),
|
|
207
167
|
name,
|
|
208
|
-
status:
|
|
168
|
+
status: 'failed',
|
|
209
169
|
baseline: baselinePath,
|
|
210
170
|
current: currentPath,
|
|
211
171
|
diff: diffPath,
|
|
@@ -215,38 +175,14 @@ export function buildFailedComparison(params) {
|
|
|
215
175
|
minClusterSize,
|
|
216
176
|
diffPercentage: honeydiffResult.diffPercentage,
|
|
217
177
|
diffCount: honeydiffResult.diffPixels,
|
|
218
|
-
reason:
|
|
178
|
+
reason: 'pixel-diff',
|
|
219
179
|
totalPixels: honeydiffResult.totalPixels,
|
|
220
180
|
aaPixelsIgnored: honeydiffResult.aaPixelsIgnored,
|
|
221
181
|
aaPercentage: honeydiffResult.aaPercentage,
|
|
222
182
|
boundingBox: honeydiffResult.boundingBox,
|
|
223
183
|
heightDiff: honeydiffResult.heightDiff,
|
|
224
184
|
intensityStats: honeydiffResult.intensityStats,
|
|
225
|
-
diffClusters
|
|
226
|
-
// User-defined region analysis (2D boxes)
|
|
227
|
-
regionAnalysis: regionCoverage ? {
|
|
228
|
-
coverage: regionCoverage.coverage,
|
|
229
|
-
clustersInRegions: regionCoverage.clustersInRegions,
|
|
230
|
-
totalClusters: regionCoverage.totalClusters,
|
|
231
|
-
pixelsInRegions: regionCoverage.pixelsInRegions,
|
|
232
|
-
totalPixels: regionCoverage.totalPixels,
|
|
233
|
-
matchedRegions: regionCoverage.matchedRegions,
|
|
234
|
-
confirmedCount: confirmedRegions.length,
|
|
235
|
-
ssimScore,
|
|
236
|
-
isFiltered: isRegionFiltered
|
|
237
|
-
} : null,
|
|
238
|
-
// Include confirmed regions for visualization in UI
|
|
239
|
-
confirmedRegions: confirmedRegions.length > 0 ? confirmedRegions : null,
|
|
240
|
-
// Historical hotspot analysis (1D Y-bands)
|
|
241
|
-
hotspotAnalysis: hotspotCoverage ? {
|
|
242
|
-
coverage: hotspotCoverage.coverage,
|
|
243
|
-
linesInHotspots: hotspotCoverage.linesInHotspots,
|
|
244
|
-
totalLines: hotspotCoverage.totalLines,
|
|
245
|
-
confidence: hotspotAnalysis?.confidence,
|
|
246
|
-
confidenceScore: hotspotAnalysis?.confidence_score,
|
|
247
|
-
regionCount: hotspotAnalysis?.regions?.length || 0,
|
|
248
|
-
isFiltered: isHotspotFiltered
|
|
249
|
-
} : null
|
|
185
|
+
diffClusters
|
|
250
186
|
};
|
|
251
187
|
}
|
|
252
188
|
|
package/dist/tdd/tdd-service.js
CHANGED
|
@@ -9,19 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync as defaultExistsSync, mkdirSync as defaultMkdirSync, readFileSync as defaultReadFileSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
12
|
-
import { createApiClient as defaultCreateApiClient,
|
|
12
|
+
import { createApiClient as defaultCreateApiClient, getBuilds as defaultGetBuilds, getComparison as defaultGetComparison, getTddBaselines as defaultGetTddBaselines } from '../api/index.js';
|
|
13
13
|
import { NetworkError } from '../errors/vizzly-error.js';
|
|
14
14
|
import { colors as defaultColors } from '../utils/colors.js';
|
|
15
15
|
import { fetchWithTimeout as defaultFetchWithTimeout } from '../utils/fetch-utils.js';
|
|
16
16
|
import { getDefaultBranch as defaultGetDefaultBranch } from '../utils/git.js';
|
|
17
17
|
import * as defaultOutput from '../utils/output.js';
|
|
18
18
|
import { safePath as defaultSafePath, sanitizeScreenshotName as defaultSanitizeScreenshotName, validatePathSecurity as defaultValidatePathSecurity, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../utils/security.js';
|
|
19
|
-
import { calculateHotspotCoverage as defaultCalculateHotspotCoverage } from './core/hotspot-coverage.js';
|
|
20
19
|
// Import from extracted modules
|
|
21
20
|
import { generateBaselineFilename as defaultGenerateBaselineFilename, generateComparisonId as defaultGenerateComparisonId, generateScreenshotSignature as defaultGenerateScreenshotSignature } from './core/signature.js';
|
|
22
21
|
import { createEmptyBaselineMetadata as defaultCreateEmptyBaselineMetadata, loadBaselineMetadata as defaultLoadBaselineMetadata, saveBaselineMetadata as defaultSaveBaselineMetadata, upsertScreenshotInMetadata as defaultUpsertScreenshotInMetadata } from './metadata/baseline-metadata.js';
|
|
23
|
-
import { loadHotspotMetadata as defaultLoadHotspotMetadata, saveHotspotMetadata as defaultSaveHotspotMetadata } from './metadata/hotspot-metadata.js';
|
|
24
|
-
import { loadRegionMetadata as defaultLoadRegionMetadata, saveRegionMetadata as defaultSaveRegionMetadata } from './metadata/region-metadata.js';
|
|
25
22
|
import { buildTddDependencyOps, resolveTddPaths, resolveTddWorkingDirectory } from './runtime-context.js';
|
|
26
23
|
import { baselineExists as defaultBaselineExists, clearBaselineData as defaultClearBaselineData, clearRunData as defaultClearRunData, getBaselinePath as defaultGetBaselinePath, getCurrentPath as defaultGetCurrentPath, getDiffPath as defaultGetDiffPath, initializeDirectories as defaultInitializeDirectories, saveBaseline as defaultSaveBaseline, saveCurrent as defaultSaveCurrent } from './services/baseline-manager.js';
|
|
27
24
|
import { buildErrorComparison as defaultBuildErrorComparison, buildFailedComparison as defaultBuildFailedComparison, buildNewComparison as defaultBuildNewComparison, buildPassedComparison as defaultBuildPassedComparison, compareImages as defaultCompareImages, isDimensionMismatchError as defaultIsDimensionMismatchError } from './services/comparison-service.js';
|
|
@@ -46,7 +43,6 @@ export class TddService {
|
|
|
46
43
|
colors: defaultColors,
|
|
47
44
|
validatePathSecurity: defaultValidatePathSecurity,
|
|
48
45
|
initializeDirectories: defaultInitializeDirectories,
|
|
49
|
-
calculateHotspotCoverage: defaultCalculateHotspotCoverage,
|
|
50
46
|
fs: {
|
|
51
47
|
existsSync: defaultExistsSync,
|
|
52
48
|
mkdirSync: defaultMkdirSync,
|
|
@@ -58,8 +54,6 @@ export class TddService {
|
|
|
58
54
|
getTddBaselines: defaultGetTddBaselines,
|
|
59
55
|
getBuilds: defaultGetBuilds,
|
|
60
56
|
getComparison: defaultGetComparison,
|
|
61
|
-
getComparisonContext: defaultGetComparisonContext,
|
|
62
|
-
getBatchHotspots: defaultGetBatchHotspots,
|
|
63
57
|
fetchWithTimeout: defaultFetchWithTimeout,
|
|
64
58
|
getDefaultBranch: defaultGetDefaultBranch
|
|
65
59
|
},
|
|
@@ -67,11 +61,7 @@ export class TddService {
|
|
|
67
61
|
loadBaselineMetadata: defaultLoadBaselineMetadata,
|
|
68
62
|
saveBaselineMetadata: defaultSaveBaselineMetadata,
|
|
69
63
|
createEmptyBaselineMetadata: defaultCreateEmptyBaselineMetadata,
|
|
70
|
-
upsertScreenshotInMetadata: defaultUpsertScreenshotInMetadata
|
|
71
|
-
loadHotspotMetadata: defaultLoadHotspotMetadata,
|
|
72
|
-
saveHotspotMetadata: defaultSaveHotspotMetadata,
|
|
73
|
-
loadRegionMetadata: defaultLoadRegionMetadata,
|
|
74
|
-
saveRegionMetadata: defaultSaveRegionMetadata
|
|
64
|
+
upsertScreenshotInMetadata: defaultUpsertScreenshotInMetadata
|
|
75
65
|
},
|
|
76
66
|
baseline: {
|
|
77
67
|
baselineExists: defaultBaselineExists,
|
|
@@ -139,12 +129,6 @@ export class TddService {
|
|
|
139
129
|
this.minClusterSize = config.comparison?.minClusterSize ?? 2;
|
|
140
130
|
this.signatureProperties = config.signatureProperties ?? [];
|
|
141
131
|
|
|
142
|
-
// Hotspot data (loaded lazily from disk or downloaded from cloud)
|
|
143
|
-
this.hotspotData = null;
|
|
144
|
-
|
|
145
|
-
// Region data (user-defined 2D bounding boxes, loaded lazily)
|
|
146
|
-
this.regionData = null;
|
|
147
|
-
|
|
148
132
|
// Track whether results have been printed (to avoid duplicate output)
|
|
149
133
|
this._resultsPrinted = false;
|
|
150
134
|
if (this.setBaseline) {
|
|
@@ -170,7 +154,6 @@ export class TddService {
|
|
|
170
154
|
getTddBaselines,
|
|
171
155
|
getBuilds,
|
|
172
156
|
getComparison,
|
|
173
|
-
getComparisonContext,
|
|
174
157
|
clearBaselineData,
|
|
175
158
|
generateScreenshotSignature,
|
|
176
159
|
generateBaselineFilename,
|
|
@@ -223,19 +206,10 @@ export class TddService {
|
|
|
223
206
|
output.warn(`Build ${buildId} has status: ${baselineBuild.status} (expected: completed)`);
|
|
224
207
|
}
|
|
225
208
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
226
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
227
209
|
} else if (comparisonId) {
|
|
228
210
|
// Handle specific comparison download
|
|
229
211
|
output.info(`Using comparison: ${comparisonId}`);
|
|
230
212
|
let comparison = await getComparison(this.client, comparisonId);
|
|
231
|
-
let comparisonContext = null;
|
|
232
|
-
if (!comparison.dynamic_content && getComparisonContext) {
|
|
233
|
-
try {
|
|
234
|
-
comparisonContext = await getComparisonContext(this.client, comparisonId);
|
|
235
|
-
} catch (error) {
|
|
236
|
-
output.debug('tdd', `comparison context unavailable for ${comparisonId}: ${error.message}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
213
|
if (!comparison.baseline_screenshot) {
|
|
240
214
|
throw new Error(`Comparison ${comparisonId} has no baseline screenshot. This comparison may be a "new" screenshot.`);
|
|
241
215
|
}
|
|
@@ -266,11 +240,6 @@ export class TddService {
|
|
|
266
240
|
}
|
|
267
241
|
}
|
|
268
242
|
let screenshotName = comparison.baseline_name || comparison.current_name;
|
|
269
|
-
let dynamicContent = comparison.dynamic_content || {
|
|
270
|
-
hotspot_analysis: comparisonContext?.history?.hotspot_analysis,
|
|
271
|
-
confirmed_regions: comparisonContext?.history?.confirmed_regions
|
|
272
|
-
};
|
|
273
|
-
this.saveDynamicMetadataForScreenshot(screenshotName, dynamicContent);
|
|
274
243
|
let signature = generateScreenshotSignature(screenshotName, screenshotProperties, this.signatureProperties);
|
|
275
244
|
let filename = generateBaselineFilename(screenshotName, signature);
|
|
276
245
|
baselineBuild = {
|
|
@@ -310,7 +279,6 @@ export class TddService {
|
|
|
310
279
|
}
|
|
311
280
|
baselineBuild = apiResponse.build;
|
|
312
281
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
313
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
314
282
|
}
|
|
315
283
|
let buildDetails = baselineBuild;
|
|
316
284
|
if (!buildDetails.screenshots || buildDetails.screenshots.length === 0) {
|
|
@@ -436,7 +404,7 @@ export class TddService {
|
|
|
436
404
|
buildInfo: {
|
|
437
405
|
commitSha: baselineBuild.commit_sha,
|
|
438
406
|
commitMessage: baselineBuild.commit_message,
|
|
439
|
-
|
|
407
|
+
visual_review: baselineBuild.visual_review || null,
|
|
440
408
|
completedAt: baselineBuild.completed_at
|
|
441
409
|
},
|
|
442
410
|
screenshots: buildDetails.screenshots.filter(s => s.filename).map(s => ({
|
|
@@ -458,9 +426,6 @@ export class TddService {
|
|
|
458
426
|
};
|
|
459
427
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
460
428
|
|
|
461
|
-
// Hotspots and regions are now bundled in the tdd-baselines API response
|
|
462
|
-
// and saved earlier when processing the API response
|
|
463
|
-
|
|
464
429
|
// Save baseline build metadata for MCP plugin
|
|
465
430
|
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
466
431
|
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
@@ -470,7 +435,7 @@ export class TddService {
|
|
|
470
435
|
environment,
|
|
471
436
|
commitSha: baselineBuild.commit_sha,
|
|
472
437
|
commitMessage: baselineBuild.commit_message,
|
|
473
|
-
|
|
438
|
+
visual_review: baselineBuild.visual_review || null,
|
|
474
439
|
completedAt: baselineBuild.completed_at,
|
|
475
440
|
downloadedAt: new Date().toISOString()
|
|
476
441
|
}, null, 2));
|
|
@@ -495,49 +460,6 @@ export class TddService {
|
|
|
495
460
|
throw error;
|
|
496
461
|
}
|
|
497
462
|
}
|
|
498
|
-
saveDynamicMetadataFromBaselineResponse(apiResponse = {}) {
|
|
499
|
-
let {
|
|
500
|
-
saveHotspotMetadata,
|
|
501
|
-
saveRegionMetadata
|
|
502
|
-
} = this._deps;
|
|
503
|
-
let hasHotspots = Object.keys(apiResponse.hotspots || {}).length > 0;
|
|
504
|
-
let hasRegions = Object.keys(apiResponse.regions || {}).length > 0;
|
|
505
|
-
if (hasHotspots) {
|
|
506
|
-
this.hotspotData = apiResponse.hotspots;
|
|
507
|
-
saveHotspotMetadata(this.workingDir, apiResponse.hotspots, apiResponse.summary);
|
|
508
|
-
}
|
|
509
|
-
if (hasRegions) {
|
|
510
|
-
this.regionData = apiResponse.regions;
|
|
511
|
-
saveRegionMetadata(this.workingDir, apiResponse.regions, apiResponse.summary);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
saveDynamicMetadataForScreenshot(screenshotName, dynamicContent = {}) {
|
|
515
|
-
if (!screenshotName) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
let hotspotAnalysis = dynamicContent.hotspot_analysis;
|
|
519
|
-
let confirmedRegions = dynamicContent.confirmed_regions || [];
|
|
520
|
-
let hasHotspotAnalysis = hotspotAnalysis?.total_builds_analyzed > 0;
|
|
521
|
-
let hasConfirmedRegions = confirmedRegions.length > 0;
|
|
522
|
-
let summary = {
|
|
523
|
-
screenshots: 1,
|
|
524
|
-
hotspots: hasHotspotAnalysis ? 1 : 0,
|
|
525
|
-
regions: hasConfirmedRegions ? 1 : 0,
|
|
526
|
-
total_regions: confirmedRegions.length
|
|
527
|
-
};
|
|
528
|
-
this.saveDynamicMetadataFromBaselineResponse({
|
|
529
|
-
hotspots: hasHotspotAnalysis ? {
|
|
530
|
-
[screenshotName]: hotspotAnalysis
|
|
531
|
-
} : {},
|
|
532
|
-
regions: hasConfirmedRegions ? {
|
|
533
|
-
[screenshotName]: {
|
|
534
|
-
confirmed: confirmedRegions,
|
|
535
|
-
candidates: []
|
|
536
|
-
}
|
|
537
|
-
} : {},
|
|
538
|
-
summary
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
463
|
|
|
542
464
|
/**
|
|
543
465
|
* Process already-fetched baseline data (for use when caller handles auth)
|
|
@@ -576,7 +498,6 @@ export class TddService {
|
|
|
576
498
|
}
|
|
577
499
|
}
|
|
578
500
|
let baselineBuild = apiResponse.build;
|
|
579
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
580
501
|
if (baselineBuild.status === 'failed') {
|
|
581
502
|
output.warn(`Build ${buildId} is marked as FAILED - falling back to local baselines`);
|
|
582
503
|
return await this.handleLocalBaselines();
|
|
@@ -708,7 +629,7 @@ export class TddService {
|
|
|
708
629
|
buildInfo: {
|
|
709
630
|
commitSha: baselineBuild.commit_sha,
|
|
710
631
|
commitMessage: baselineBuild.commit_message,
|
|
711
|
-
|
|
632
|
+
visual_review: baselineBuild.visual_review || null,
|
|
712
633
|
completedAt: baselineBuild.completed_at
|
|
713
634
|
},
|
|
714
635
|
screenshots: buildDetails.screenshots.filter(s => s.filename).map(s => ({
|
|
@@ -730,9 +651,6 @@ export class TddService {
|
|
|
730
651
|
};
|
|
731
652
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
732
653
|
|
|
733
|
-
// Hotspots and regions are now bundled in the tdd-baselines API response
|
|
734
|
-
// and saved earlier when processing the API response
|
|
735
|
-
|
|
736
654
|
// Save baseline build metadata for MCP plugin
|
|
737
655
|
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
738
656
|
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
@@ -742,7 +660,7 @@ export class TddService {
|
|
|
742
660
|
environment: 'test',
|
|
743
661
|
commitSha: baselineBuild.commit_sha,
|
|
744
662
|
commitMessage: baselineBuild.commit_message,
|
|
745
|
-
|
|
663
|
+
visual_review: baselineBuild.visual_review || null,
|
|
746
664
|
completedAt: baselineBuild.completed_at,
|
|
747
665
|
downloadedAt: new Date().toISOString()
|
|
748
666
|
}, null, 2));
|
|
@@ -764,118 +682,6 @@ export class TddService {
|
|
|
764
682
|
return this.baselineData;
|
|
765
683
|
}
|
|
766
684
|
|
|
767
|
-
/**
|
|
768
|
-
* Download hotspot data for screenshots
|
|
769
|
-
*/
|
|
770
|
-
async downloadHotspots(screenshots) {
|
|
771
|
-
let {
|
|
772
|
-
output,
|
|
773
|
-
getBatchHotspots,
|
|
774
|
-
saveHotspotMetadata
|
|
775
|
-
} = this._deps;
|
|
776
|
-
if (!this.config.apiKey) {
|
|
777
|
-
output.debug('tdd', 'Skipping hotspot download - no API token configured');
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
try {
|
|
781
|
-
let screenshotNames = [...new Set(screenshots.map(s => s.name))];
|
|
782
|
-
if (screenshotNames.length === 0) {
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
output.info(`🔥 Fetching hotspot data for ${screenshotNames.length} screenshots...`);
|
|
786
|
-
let response = await getBatchHotspots(this.client, screenshotNames);
|
|
787
|
-
if (!response.hotspots || Object.keys(response.hotspots).length === 0) {
|
|
788
|
-
output.debug('tdd', 'No hotspot data available from cloud');
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Update memory cache
|
|
793
|
-
this.hotspotData = response.hotspots;
|
|
794
|
-
|
|
795
|
-
// Save to disk using extracted module
|
|
796
|
-
saveHotspotMetadata(this.workingDir, response.hotspots, response.summary);
|
|
797
|
-
let hotspotCount = Object.keys(response.hotspots).length;
|
|
798
|
-
let totalRegions = Object.values(response.hotspots).reduce((sum, h) => sum + (h.regions?.length || 0), 0);
|
|
799
|
-
output.info(`Downloaded hotspot data for ${hotspotCount} screenshots (${totalRegions} regions total)`);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
output.debug('tdd', `Hotspot download failed: ${error.message}`);
|
|
802
|
-
output.warn('Could not fetch hotspot data - comparisons will run without noise filtering');
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* Load hotspot data from disk
|
|
808
|
-
*/
|
|
809
|
-
loadHotspots() {
|
|
810
|
-
let {
|
|
811
|
-
loadHotspotMetadata
|
|
812
|
-
} = this._deps;
|
|
813
|
-
return loadHotspotMetadata(this.workingDir);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Get hotspot for a specific screenshot
|
|
818
|
-
*
|
|
819
|
-
* Note: Once hotspotData is loaded (from disk or cloud), we don't reload.
|
|
820
|
-
* This is intentional - hotspots are downloaded once per session and cached.
|
|
821
|
-
* If a screenshot isn't in the cache, it means no hotspot data exists for it.
|
|
822
|
-
*/
|
|
823
|
-
getHotspotForScreenshot(screenshotName) {
|
|
824
|
-
// Check memory cache first
|
|
825
|
-
if (this.hotspotData?.[screenshotName]) {
|
|
826
|
-
return this.hotspotData[screenshotName];
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Try loading from disk (only if we haven't loaded yet)
|
|
830
|
-
if (!this.hotspotData) {
|
|
831
|
-
this.hotspotData = this.loadHotspots();
|
|
832
|
-
}
|
|
833
|
-
return this.hotspotData?.[screenshotName] || null;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Load region data from disk
|
|
838
|
-
*/
|
|
839
|
-
loadRegions() {
|
|
840
|
-
let {
|
|
841
|
-
loadRegionMetadata
|
|
842
|
-
} = this._deps;
|
|
843
|
-
return loadRegionMetadata(this.workingDir);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Get user-defined regions for a specific screenshot
|
|
848
|
-
*
|
|
849
|
-
* Note: Once regionData is loaded (from disk or cloud), we don't reload.
|
|
850
|
-
* This is intentional - regions are downloaded once per session and cached.
|
|
851
|
-
* If a screenshot isn't in the cache, it means no region data exists for it.
|
|
852
|
-
*
|
|
853
|
-
* @param {string} screenshotName - Name of the screenshot
|
|
854
|
-
* @returns {Object|null} Region data { confirmed: [], candidates: [] } or null
|
|
855
|
-
*/
|
|
856
|
-
getRegionsForScreenshot(screenshotName) {
|
|
857
|
-
// Check memory cache first
|
|
858
|
-
if (this.regionData?.[screenshotName]) {
|
|
859
|
-
return this.regionData[screenshotName];
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Try loading from disk (only if we haven't loaded yet)
|
|
863
|
-
if (!this.regionData) {
|
|
864
|
-
this.regionData = this.loadRegions();
|
|
865
|
-
}
|
|
866
|
-
return this.regionData?.[screenshotName] || null;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* Calculate hotspot coverage (delegating to pure function)
|
|
871
|
-
*/
|
|
872
|
-
calculateHotspotCoverage(diffClusters, hotspotAnalysis) {
|
|
873
|
-
let {
|
|
874
|
-
calculateHotspotCoverage
|
|
875
|
-
} = this._deps;
|
|
876
|
-
return calculateHotspotCoverage(diffClusters, hotspotAnalysis);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
685
|
/**
|
|
880
686
|
* Handle local baselines logic
|
|
881
687
|
*/
|
|
@@ -1065,8 +871,6 @@ export class TddService {
|
|
|
1065
871
|
this._upsertComparison(result);
|
|
1066
872
|
return result;
|
|
1067
873
|
} else {
|
|
1068
|
-
let hotspotAnalysis = this.getHotspotForScreenshot(name);
|
|
1069
|
-
let regionData = this.getRegionsForScreenshot(name);
|
|
1070
874
|
let result = buildFailedComparison({
|
|
1071
875
|
name: sanitizedName,
|
|
1072
876
|
signature,
|
|
@@ -1076,9 +880,7 @@ export class TddService {
|
|
|
1076
880
|
properties: validatedProperties,
|
|
1077
881
|
threshold: effectiveThreshold,
|
|
1078
882
|
minClusterSize: effectiveMinClusterSize,
|
|
1079
|
-
honeydiffResult
|
|
1080
|
-
hotspotAnalysis,
|
|
1081
|
-
regionData
|
|
883
|
+
honeydiffResult
|
|
1082
884
|
});
|
|
1083
885
|
|
|
1084
886
|
// Log at debug level only (shown with --verbose)
|
package/dist/types/index.d.ts
CHANGED
package/dist/uploader/core.js
CHANGED
|
@@ -340,13 +340,12 @@ export function buildUploadResult({
|
|
|
340
340
|
* @returns {Object} Wait result
|
|
341
341
|
*/
|
|
342
342
|
export function buildWaitResult(build) {
|
|
343
|
-
let totalComparisons = build.
|
|
344
|
-
let passedComparisons = build.
|
|
345
|
-
let failedComparisons = build.
|
|
346
|
-
let newComparisons = build.
|
|
347
|
-
let identicalComparisons = build.
|
|
348
|
-
let
|
|
349
|
-
let hasComparisonCounts = build.comparisonsTotal !== undefined || build.total_comparisons !== undefined || build.comparisonsPassed !== undefined || build.passed_comparisons !== undefined || build.comparisonsFailed !== undefined || build.failed_comparisons !== undefined || build.comparisonsNew !== undefined || build.new_comparisons !== undefined || build.identicalComparisons !== undefined || build.identical_comparisons !== undefined;
|
|
343
|
+
let totalComparisons = build.total_comparisons;
|
|
344
|
+
let passedComparisons = build.identical_comparisons;
|
|
345
|
+
let failedComparisons = build.changed_comparisons;
|
|
346
|
+
let newComparisons = build.new_comparisons;
|
|
347
|
+
let identicalComparisons = build.identical_comparisons;
|
|
348
|
+
let hasComparisonCounts = build.total_comparisons !== undefined || build.new_comparisons !== undefined || build.changed_comparisons !== undefined || build.identical_comparisons !== undefined;
|
|
350
349
|
let result = {
|
|
351
350
|
status: build.status,
|
|
352
351
|
build,
|
|
@@ -354,7 +353,7 @@ export function buildWaitResult(build) {
|
|
|
354
353
|
failedComparisons,
|
|
355
354
|
newComparisons,
|
|
356
355
|
identicalComparisons,
|
|
357
|
-
|
|
356
|
+
visual_review: build.visual_review || null,
|
|
358
357
|
totalComparisons
|
|
359
358
|
};
|
|
360
359
|
if (hasComparisonCounts) {
|