@vizzly-testing/cli 0.28.1 → 0.29.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/dist/commands/builds.js +74 -10
- package/dist/commands/comparisons.js +68 -15
- package/package.json +1 -1
package/dist/commands/builds.js
CHANGED
|
@@ -110,11 +110,27 @@ export async function buildsCommand(options = {}, globalOptions = {}, deps = {})
|
|
|
110
110
|
for (let build of builds) {
|
|
111
111
|
let statusColor = getStatusColor(colors, build.status);
|
|
112
112
|
let statusBadge = statusColor(build.status.toUpperCase());
|
|
113
|
-
|
|
113
|
+
|
|
114
|
+
// Approval badge
|
|
115
|
+
let approvalBadge = '';
|
|
116
|
+
if (build.approval_status && build.status === 'completed') {
|
|
117
|
+
approvalBadge = ` ${getApprovalBadge(colors, build.approval_status)}`;
|
|
118
|
+
}
|
|
119
|
+
output.print(` ${colors.bold(build.name || build.id)} ${statusBadge}${approvalBadge}`);
|
|
114
120
|
let details = [];
|
|
115
121
|
if (build.branch) details.push(build.branch);
|
|
116
122
|
if (build.commit_sha) details.push(build.commit_sha.substring(0, 7));
|
|
117
123
|
if (build.screenshot_count) details.push(`${build.screenshot_count} screenshots`);
|
|
124
|
+
|
|
125
|
+
// Comparison counts summary
|
|
126
|
+
let compParts = [];
|
|
127
|
+
let changed = build.changed_comparisons || 0;
|
|
128
|
+
let identical = build.identical_comparisons || 0;
|
|
129
|
+
let newCount = build.new_comparisons || 0;
|
|
130
|
+
if (changed > 0) compParts.push(`${changed} changed`);
|
|
131
|
+
if (newCount > 0) compParts.push(`${newCount} new`);
|
|
132
|
+
if (identical > 0) compParts.push(`${identical} identical`);
|
|
133
|
+
if (compParts.length > 0) details.push(compParts.join(' · '));
|
|
118
134
|
if (details.length > 0) {
|
|
119
135
|
output.print(` ${colors.dim(details.join(' · '))}`);
|
|
120
136
|
}
|
|
@@ -161,13 +177,35 @@ function formatBuildForJson(build, includeComparisons = false) {
|
|
|
161
177
|
completedAt: build.completed_at
|
|
162
178
|
};
|
|
163
179
|
if (includeComparisons && build.comparisons) {
|
|
164
|
-
result.comparisonDetails = build.comparisons.map(c =>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
result.comparisonDetails = build.comparisons.map(c => {
|
|
181
|
+
let diffUrl = c.diff_image?.url || c.diff_image_url || c.diff_url || null;
|
|
182
|
+
let diffImage = c.diff_image || {};
|
|
183
|
+
let clusterMetadata = c.cluster_metadata || diffImage.cluster_metadata || null;
|
|
184
|
+
let ssimScore = c.ssim_score ?? diffImage.ssim_score ?? null;
|
|
185
|
+
let gmsdScore = c.gmsd_score ?? diffImage.gmsd_score ?? null;
|
|
186
|
+
let fingerprintHash = c.fingerprint_hash || diffImage.fingerprint_hash || null;
|
|
187
|
+
let hasHoneydiff = clusterMetadata || ssimScore != null || gmsdScore != null || fingerprintHash;
|
|
188
|
+
return {
|
|
189
|
+
id: c.id,
|
|
190
|
+
name: c.name || c.current_name,
|
|
191
|
+
status: c.status,
|
|
192
|
+
diffPercentage: c.diff_percentage,
|
|
193
|
+
approvalStatus: c.approval_status,
|
|
194
|
+
urls: {
|
|
195
|
+
baseline: c.baseline_screenshot?.original_url || c.baseline_original_url || c.baseline_screenshot_url || null,
|
|
196
|
+
current: c.current_screenshot?.original_url || c.current_original_url || c.current_screenshot_url || null,
|
|
197
|
+
diff: diffUrl
|
|
198
|
+
},
|
|
199
|
+
honeydiff: hasHoneydiff ? {
|
|
200
|
+
ssimScore,
|
|
201
|
+
gmsdScore,
|
|
202
|
+
clusterClassification: clusterMetadata?.classification || null,
|
|
203
|
+
clusterMetadata,
|
|
204
|
+
fingerprintHash,
|
|
205
|
+
diffRegions: c.diff_regions ?? diffImage.diff_regions ?? null
|
|
206
|
+
} : null
|
|
207
|
+
};
|
|
208
|
+
});
|
|
171
209
|
}
|
|
172
210
|
return result;
|
|
173
211
|
}
|
|
@@ -214,8 +252,17 @@ function displayBuild(output, build, verbose) {
|
|
|
214
252
|
output.blank();
|
|
215
253
|
output.labelValue('Comparisons', '');
|
|
216
254
|
for (let comp of build.comparisons.slice(0, verbose ? 50 : 10)) {
|
|
217
|
-
let
|
|
218
|
-
|
|
255
|
+
let resultIcon = getComparisonStatusIcon(colors, comp.result || comp.status);
|
|
256
|
+
let compName = comp.name || comp.current_name || comp.id;
|
|
257
|
+
let diffInfo = '';
|
|
258
|
+
if (comp.diff_percentage > 0) {
|
|
259
|
+
diffInfo = colors.dim(` (${comp.diff_percentage.toFixed(2)}%)`);
|
|
260
|
+
}
|
|
261
|
+
let classification = '';
|
|
262
|
+
if (verbose && comp.cluster_metadata?.classification) {
|
|
263
|
+
classification = colors.dim(` [${comp.cluster_metadata.classification}]`);
|
|
264
|
+
}
|
|
265
|
+
output.print(` ${resultIcon} ${compName}${diffInfo}${classification}`);
|
|
219
266
|
}
|
|
220
267
|
if (build.comparisons.length > (verbose ? 50 : 10)) {
|
|
221
268
|
output.hint(` ... and ${build.comparisons.length - (verbose ? 50 : 10)} more`);
|
|
@@ -240,6 +287,23 @@ function getStatusColor(colors, status) {
|
|
|
240
287
|
}
|
|
241
288
|
}
|
|
242
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Get colored approval badge
|
|
292
|
+
*/
|
|
293
|
+
function getApprovalBadge(colors, approvalStatus) {
|
|
294
|
+
switch (approvalStatus) {
|
|
295
|
+
case 'approved':
|
|
296
|
+
case 'auto_approved':
|
|
297
|
+
return colors.brand.success('APPROVED');
|
|
298
|
+
case 'rejected':
|
|
299
|
+
return colors.brand.error('REJECTED');
|
|
300
|
+
case 'pending':
|
|
301
|
+
return colors.brand.warning('PENDING');
|
|
302
|
+
default:
|
|
303
|
+
return colors.dim(approvalStatus?.toUpperCase() || '');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
243
307
|
/**
|
|
244
308
|
* Get icon for comparison status
|
|
245
309
|
*/
|
|
@@ -154,6 +154,16 @@ export async function comparisonsCommand(options = {}, globalOptions = {}, deps
|
|
|
154
154
|
* Format a comparison for JSON output
|
|
155
155
|
*/
|
|
156
156
|
function formatComparisonForJson(comparison) {
|
|
157
|
+
// API endpoints return different shapes:
|
|
158
|
+
// - Single comparison: nested baseline_screenshot/current_screenshot + flat diff_url, honeydiff at top level
|
|
159
|
+
// - Build comparisons: flat diff_url/diff_image_url, no storage URLs, limited honeydiff
|
|
160
|
+
// - Search: nested diff_image with honeydiff, no current/baseline URLs
|
|
161
|
+
let diffImage = comparison.diff_image || {};
|
|
162
|
+
let clusterMetadata = comparison.cluster_metadata || diffImage.cluster_metadata || null;
|
|
163
|
+
let ssimScore = comparison.ssim_score ?? diffImage.ssim_score ?? null;
|
|
164
|
+
let gmsdScore = comparison.gmsd_score ?? diffImage.gmsd_score ?? null;
|
|
165
|
+
let fingerprintHash = comparison.fingerprint_hash || diffImage.fingerprint_hash || null;
|
|
166
|
+
let hasHoneydiff = clusterMetadata || ssimScore != null || gmsdScore != null || fingerprintHash;
|
|
157
167
|
return {
|
|
158
168
|
id: comparison.id,
|
|
159
169
|
name: comparison.name,
|
|
@@ -166,10 +176,20 @@ function formatComparisonForJson(comparison) {
|
|
|
166
176
|
} : null,
|
|
167
177
|
browser: comparison.browser || null,
|
|
168
178
|
urls: {
|
|
169
|
-
baseline: comparison.baseline_screenshot?.original_url || null,
|
|
170
|
-
current: comparison.current_screenshot?.original_url || null,
|
|
171
|
-
diff: comparison.diff_image?.url || null
|
|
179
|
+
baseline: comparison.baseline_screenshot?.original_url || comparison.baseline_original_url || comparison.baseline_screenshot_url || null,
|
|
180
|
+
current: comparison.current_screenshot?.original_url || comparison.current_original_url || comparison.current_screenshot_url || null,
|
|
181
|
+
diff: comparison.diff_image?.url || comparison.diff_image_url || comparison.diff_url || null
|
|
172
182
|
},
|
|
183
|
+
honeydiff: hasHoneydiff ? {
|
|
184
|
+
ssimScore,
|
|
185
|
+
gmsdScore,
|
|
186
|
+
clusterClassification: clusterMetadata?.classification || null,
|
|
187
|
+
clusterMetadata,
|
|
188
|
+
fingerprintHash,
|
|
189
|
+
diffRegions: comparison.diff_regions ?? diffImage.diff_regions ?? null,
|
|
190
|
+
diffLines: comparison.diff_lines ?? diffImage.diff_lines ?? null,
|
|
191
|
+
fingerprintData: comparison.fingerprint_data ?? diffImage.fingerprint_data ?? null
|
|
192
|
+
} : null,
|
|
173
193
|
buildId: comparison.build_id,
|
|
174
194
|
buildName: comparison.build_name,
|
|
175
195
|
buildBranch: comparison.build_branch,
|
|
@@ -209,18 +229,40 @@ function displayComparison(output, comparison, verbose) {
|
|
|
209
229
|
output.labelValue('Commit', comparison.build_commit_sha.substring(0, 8));
|
|
210
230
|
}
|
|
211
231
|
|
|
212
|
-
//
|
|
232
|
+
// Honeydiff analysis in verbose mode
|
|
213
233
|
if (verbose) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
234
|
+
let clusterMetadata = comparison.cluster_metadata || comparison.diff_image?.cluster_metadata;
|
|
235
|
+
let ssim = comparison.ssim_score ?? comparison.diff_image?.ssim_score;
|
|
236
|
+
let gmsd = comparison.gmsd_score ?? comparison.diff_image?.gmsd_score;
|
|
237
|
+
let fingerprint = comparison.fingerprint_hash || comparison.diff_image?.fingerprint_hash;
|
|
238
|
+
if (clusterMetadata || ssim != null || gmsd != null || fingerprint) {
|
|
239
|
+
output.blank();
|
|
240
|
+
if (clusterMetadata?.classification) {
|
|
241
|
+
output.labelValue('Classification', clusterMetadata.classification);
|
|
242
|
+
}
|
|
243
|
+
if (ssim != null) {
|
|
244
|
+
output.labelValue('SSIM', ssim.toFixed(4));
|
|
245
|
+
}
|
|
246
|
+
if (gmsd != null) {
|
|
247
|
+
output.labelValue('GMSD', gmsd.toFixed(4));
|
|
248
|
+
}
|
|
249
|
+
if (fingerprint) {
|
|
250
|
+
output.labelValue('Fingerprint', fingerprint);
|
|
251
|
+
}
|
|
221
252
|
}
|
|
222
|
-
|
|
223
|
-
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// URLs in verbose mode
|
|
256
|
+
if (verbose) {
|
|
257
|
+
let baselineUrl = comparison.baseline_screenshot?.original_url || comparison.baseline_original_url || comparison.baseline_screenshot_url;
|
|
258
|
+
let currentUrl = comparison.current_screenshot?.original_url || comparison.current_original_url || comparison.current_screenshot_url;
|
|
259
|
+
let diffUrl = comparison.diff_image?.url || comparison.diff_image_url || comparison.diff_url;
|
|
260
|
+
if (baselineUrl || currentUrl || diffUrl) {
|
|
261
|
+
output.blank();
|
|
262
|
+
output.labelValue('URLs', '');
|
|
263
|
+
if (baselineUrl) output.print(` Baseline: ${baselineUrl}`);
|
|
264
|
+
if (currentUrl) output.print(` Current: ${currentUrl}`);
|
|
265
|
+
if (diffUrl) output.print(` Diff: ${diffUrl}`);
|
|
224
266
|
}
|
|
225
267
|
}
|
|
226
268
|
if (comparison.created_at) {
|
|
@@ -258,7 +300,8 @@ function displayBuildComparisons(output, build, comparisons, verbose) {
|
|
|
258
300
|
for (let comp of comparisons.slice(0, verbose ? 100 : 20)) {
|
|
259
301
|
let icon = getStatusIcon(colors, comp.status);
|
|
260
302
|
let diffInfo = comp.diff_percentage != null ? colors.dim(` (${(comp.diff_percentage * 100).toFixed(1)}%)`) : '';
|
|
261
|
-
|
|
303
|
+
let classification = verbose ? getClassificationLabel(colors, comp.cluster_metadata) : '';
|
|
304
|
+
output.print(` ${icon} ${comp.name}${diffInfo}${classification}`);
|
|
262
305
|
}
|
|
263
306
|
if (comparisons.length > (verbose ? 100 : 20)) {
|
|
264
307
|
output.blank();
|
|
@@ -299,7 +342,8 @@ function displaySearchResults(output, comparisons, searchPattern, pagination, ve
|
|
|
299
342
|
}
|
|
300
343
|
for (let comp of group.comparisons.slice(0, verbose ? 10 : 3)) {
|
|
301
344
|
let icon = getStatusIcon(colors, comp.status);
|
|
302
|
-
|
|
345
|
+
let classification = verbose ? getClassificationLabel(colors, comp.cluster_metadata || comp.diff_image?.cluster_metadata) : '';
|
|
346
|
+
output.print(` ${icon} ${comp.name}${classification}`);
|
|
303
347
|
}
|
|
304
348
|
if (group.comparisons.length > (verbose ? 10 : 3)) {
|
|
305
349
|
output.print(` ${colors.dim(`... and ${group.comparisons.length - (verbose ? 10 : 3)} more`)}`);
|
|
@@ -311,6 +355,15 @@ function displaySearchResults(output, comparisons, searchPattern, pagination, ve
|
|
|
311
355
|
}
|
|
312
356
|
}
|
|
313
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Get a classification label for verbose display
|
|
360
|
+
*/
|
|
361
|
+
function getClassificationLabel(colors, clusterMetadata) {
|
|
362
|
+
let classification = clusterMetadata?.classification;
|
|
363
|
+
if (!classification) return '';
|
|
364
|
+
return colors.dim(` [${classification}]`);
|
|
365
|
+
}
|
|
366
|
+
|
|
314
367
|
/**
|
|
315
368
|
* Get icon for comparison status
|
|
316
369
|
*/
|