@vizzly-testing/cli 0.28.0 → 0.29.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.
@@ -50,9 +50,7 @@ export async function buildsCommand(options = {}, globalOptions = {}, deps = {})
50
50
  if (options.build) {
51
51
  output.startSpinner('Fetching build...');
52
52
  let include = options.comparisons ? 'comparisons' : undefined;
53
- let response = await getBuild(client, options.build, {
54
- include
55
- });
53
+ let response = await getBuild(client, options.build, include);
56
54
  output.stopSpinner();
57
55
  let build = response.build || response;
58
56
  if (globalOptions.json) {
@@ -163,13 +161,35 @@ function formatBuildForJson(build, includeComparisons = false) {
163
161
  completedAt: build.completed_at
164
162
  };
165
163
  if (includeComparisons && build.comparisons) {
166
- result.comparisonDetails = build.comparisons.map(c => ({
167
- id: c.id,
168
- name: c.name,
169
- status: c.status,
170
- diffPercentage: c.diff_percentage,
171
- approvalStatus: c.approval_status
172
- }));
164
+ result.comparisonDetails = build.comparisons.map(c => {
165
+ let diffUrl = c.diff_image?.url || c.diff_image_url || c.diff_url || null;
166
+ let diffImage = c.diff_image || {};
167
+ let clusterMetadata = c.cluster_metadata || diffImage.cluster_metadata || null;
168
+ let ssimScore = c.ssim_score ?? diffImage.ssim_score ?? null;
169
+ let gmsdScore = c.gmsd_score ?? diffImage.gmsd_score ?? null;
170
+ let fingerprintHash = c.fingerprint_hash || diffImage.fingerprint_hash || null;
171
+ let hasHoneydiff = clusterMetadata || ssimScore != null || gmsdScore != null || fingerprintHash;
172
+ return {
173
+ id: c.id,
174
+ name: c.name,
175
+ status: c.status,
176
+ diffPercentage: c.diff_percentage,
177
+ approvalStatus: c.approval_status,
178
+ urls: {
179
+ baseline: c.baseline_screenshot?.original_url || c.baseline_original_url || c.baseline_screenshot_url || null,
180
+ current: c.current_screenshot?.original_url || c.current_original_url || c.current_screenshot_url || null,
181
+ diff: diffUrl
182
+ },
183
+ honeydiff: hasHoneydiff ? {
184
+ ssimScore,
185
+ gmsdScore,
186
+ clusterClassification: clusterMetadata?.classification || null,
187
+ clusterMetadata,
188
+ fingerprintHash,
189
+ diffRegions: c.diff_regions ?? diffImage.diff_regions ?? null
190
+ } : null
191
+ };
192
+ });
173
193
  }
174
194
  return result;
175
195
  }
@@ -65,9 +65,7 @@ export async function comparisonsCommand(options = {}, globalOptions = {}, deps
65
65
  // Get comparisons for a specific build
66
66
  if (options.build) {
67
67
  output.startSpinner('Fetching comparisons for build...');
68
- let response = await getBuild(client, options.build, {
69
- include: 'comparisons'
70
- });
68
+ let response = await getBuild(client, options.build, 'comparisons');
71
69
  output.stopSpinner();
72
70
  let build = response.build || response;
73
71
  let comparisons = build.comparisons || [];
@@ -156,6 +154,16 @@ export async function comparisonsCommand(options = {}, globalOptions = {}, deps
156
154
  * Format a comparison for JSON output
157
155
  */
158
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;
159
167
  return {
160
168
  id: comparison.id,
161
169
  name: comparison.name,
@@ -168,10 +176,20 @@ function formatComparisonForJson(comparison) {
168
176
  } : null,
169
177
  browser: comparison.browser || null,
170
178
  urls: {
171
- baseline: comparison.baseline_screenshot?.original_url || null,
172
- current: comparison.current_screenshot?.original_url || null,
173
- 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
174
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,
175
193
  buildId: comparison.build_id,
176
194
  buildName: comparison.build_name,
177
195
  buildBranch: comparison.build_branch,
@@ -211,18 +229,40 @@ function displayComparison(output, comparison, verbose) {
211
229
  output.labelValue('Commit', comparison.build_commit_sha.substring(0, 8));
212
230
  }
213
231
 
214
- // URLs in verbose mode
232
+ // Honeydiff analysis in verbose mode
215
233
  if (verbose) {
216
- output.blank();
217
- output.labelValue('URLs', '');
218
- if (comparison.baseline_screenshot?.original_url) {
219
- output.print(` Baseline: ${comparison.baseline_screenshot.original_url}`);
220
- }
221
- if (comparison.current_screenshot?.original_url) {
222
- output.print(` Current: ${comparison.current_screenshot.original_url}`);
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
+ }
223
252
  }
224
- if (comparison.diff_image?.url) {
225
- output.print(` Diff: ${comparison.diff_image.url}`);
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}`);
226
266
  }
227
267
  }
228
268
  if (comparison.created_at) {
@@ -260,7 +300,8 @@ function displayBuildComparisons(output, build, comparisons, verbose) {
260
300
  for (let comp of comparisons.slice(0, verbose ? 100 : 20)) {
261
301
  let icon = getStatusIcon(colors, comp.status);
262
302
  let diffInfo = comp.diff_percentage != null ? colors.dim(` (${(comp.diff_percentage * 100).toFixed(1)}%)`) : '';
263
- output.print(` ${icon} ${comp.name}${diffInfo}`);
303
+ let classification = verbose ? getClassificationLabel(colors, comp.cluster_metadata) : '';
304
+ output.print(` ${icon} ${comp.name}${diffInfo}${classification}`);
264
305
  }
265
306
  if (comparisons.length > (verbose ? 100 : 20)) {
266
307
  output.blank();
@@ -301,7 +342,8 @@ function displaySearchResults(output, comparisons, searchPattern, pagination, ve
301
342
  }
302
343
  for (let comp of group.comparisons.slice(0, verbose ? 10 : 3)) {
303
344
  let icon = getStatusIcon(colors, comp.status);
304
- output.print(` ${icon} ${comp.name}`);
345
+ let classification = verbose ? getClassificationLabel(colors, comp.cluster_metadata || comp.diff_image?.cluster_metadata) : '';
346
+ output.print(` ${icon} ${comp.name}${classification}`);
305
347
  }
306
348
  if (group.comparisons.length > (verbose ? 10 : 3)) {
307
349
  output.print(` ${colors.dim(`... and ${group.comparisons.length - (verbose ? 10 : 3)} more`)}`);
@@ -313,6 +355,15 @@ function displaySearchResults(output, comparisons, searchPattern, pagination, ve
313
355
  }
314
356
  }
315
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
+
316
367
  /**
317
368
  * Get icon for comparison status
318
369
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",