@vizzly-testing/cli 0.19.2 → 0.20.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.
@@ -120,7 +120,6 @@ function createSimpleClient(serverUrl) {
120
120
  name,
121
121
  image,
122
122
  properties: options,
123
- threshold: options.threshold || 0,
124
123
  fullPage: options.fullPage || false
125
124
  }),
126
125
  signal: controller.signal
@@ -142,7 +142,7 @@ export class TddService {
142
142
  this.comparisons = [];
143
143
  this.threshold = config.comparison?.threshold || 2.0;
144
144
  this.minClusterSize = config.comparison?.minClusterSize ?? 2; // Filter single-pixel noise by default
145
- this.signatureProperties = []; // Custom properties from project's baseline_signature_properties
145
+ this.signatureProperties = config.signatureProperties ?? []; // Custom properties from project's baseline_signature_properties
146
146
 
147
147
  // Check if we're in baseline update mode
148
148
  if (this.setBaseline) {
@@ -748,7 +748,7 @@ export class TddService {
748
748
  this.threshold = metadata.threshold || this.threshold;
749
749
 
750
750
  // Restore signature properties from saved metadata (for variant support)
751
- this.signatureProperties = metadata.signatureProperties || [];
751
+ this.signatureProperties = metadata.signatureProperties || this.signatureProperties;
752
752
  if (this.signatureProperties.length > 0) {
753
753
  output.debug('tdd', `loaded signature properties: ${this.signatureProperties.join(', ')}`);
754
754
  }
@@ -857,16 +857,22 @@ export class TddService {
857
857
 
858
858
  // Baseline exists - compare with it
859
859
  try {
860
+ // Per-screenshot threshold/minClusterSize override support
861
+ // Priority: screenshot-level > config > defaults
862
+ // Validate overrides before using them
863
+ const effectiveThreshold = typeof validatedProperties.threshold === 'number' && validatedProperties.threshold >= 0 ? validatedProperties.threshold : this.threshold;
864
+ const effectiveMinClusterSize = Number.isInteger(validatedProperties.minClusterSize) && validatedProperties.minClusterSize >= 1 ? validatedProperties.minClusterSize : this.minClusterSize;
865
+
860
866
  // Try to compare - honeydiff will throw if dimensions don't match
861
867
  const result = await compare(baselineImagePath, currentImagePath, {
862
- threshold: this.threshold,
868
+ threshold: effectiveThreshold,
863
869
  // CIEDE2000 Delta E (2.0 = recommended default)
864
870
  antialiasing: true,
865
871
  diffPath: diffImagePath,
866
872
  overwrite: true,
867
873
  includeClusters: true,
868
874
  // Enable spatial clustering analysis
869
- minClusterSize: this.minClusterSize // Filter single-pixel noise (default: 2)
875
+ minClusterSize: effectiveMinClusterSize // Filter single-pixel noise (default: 2)
870
876
  });
871
877
  if (!result.isDifferent) {
872
878
  // Images match
@@ -879,7 +885,8 @@ export class TddService {
879
885
  diff: null,
880
886
  properties: validatedProperties,
881
887
  signature,
882
- threshold: this.threshold,
888
+ threshold: effectiveThreshold,
889
+ minClusterSize: effectiveMinClusterSize,
883
890
  // Include honeydiff metrics even for passing comparisons
884
891
  totalPixels: result.totalPixels,
885
892
  aaPixelsIgnored: result.aaPixelsIgnored,
@@ -925,7 +932,8 @@ export class TddService {
925
932
  diff: diffImagePath,
926
933
  properties: validatedProperties,
927
934
  signature,
928
- threshold: this.threshold,
935
+ threshold: effectiveThreshold,
936
+ minClusterSize: effectiveMinClusterSize,
929
937
  diffPercentage: result.diffPercentage,
930
938
  diffCount: result.diffPixels,
931
939
  reason: isHotspotFiltered ? 'hotspot-filtered' : 'pixel-diff',
@@ -189,10 +189,11 @@ export class TestRunner extends EventEmitter {
189
189
  };
190
190
 
191
191
  // Only include metadata if we have meaningful config to send
192
- if (this.config.comparison?.threshold != null) {
192
+ if (this.config.comparison?.threshold != null || this.config.comparison?.minClusterSize != null) {
193
193
  buildPayload.metadata = {
194
194
  comparison: {
195
- threshold: this.config.comparison.threshold
195
+ threshold: this.config.comparison.threshold,
196
+ minClusterSize: this.config.comparison.minClusterSize
196
197
  }
197
198
  };
198
199
  }
@@ -23,10 +23,11 @@
23
23
  * await vizzlyScreenshot('homepage', './screenshots/homepage.png');
24
24
  *
25
25
  * @example
26
- * // With properties and threshold
26
+ * // With properties and comparison settings
27
27
  * await vizzlyScreenshot('checkout-form', screenshot, {
28
28
  * properties: { browser: 'chrome', viewport: '1920x1080' },
29
- * threshold: 5
29
+ * threshold: 5,
30
+ * minClusterSize: 10
30
31
  * });
31
32
  */
32
33
  export function vizzlyScreenshot(
@@ -35,6 +36,7 @@ export function vizzlyScreenshot(
35
36
  options?: {
36
37
  properties?: Record<string, unknown>;
37
38
  threshold?: number;
39
+ minClusterSize?: number;
38
40
  fullPage?: boolean;
39
41
  }
40
42
  ): Promise<void>;
@@ -54,6 +54,8 @@ export interface VizzlyConfig {
54
54
  upload?: UploadConfig;
55
55
  comparison?: ComparisonConfig;
56
56
  tdd?: TddConfig;
57
+ /** Custom properties for baseline matching (e.g., ['theme', 'device']) */
58
+ signatureProperties?: string[];
57
59
  plugins?: string[];
58
60
  parallelId?: string;
59
61
  baselineBuildId?: string;
@@ -72,6 +74,7 @@ export interface VizzlyConfig {
72
74
  export interface ScreenshotOptions {
73
75
  properties?: Record<string, unknown>;
74
76
  threshold?: number;
77
+ minClusterSize?: number;
75
78
  fullPage?: boolean;
76
79
  buildId?: string;
77
80
  }
@@ -97,6 +100,7 @@ export interface ComparisonResult {
97
100
  properties: Record<string, unknown>;
98
101
  signature: string;
99
102
  threshold?: number;
103
+ minClusterSize?: number;
100
104
  diffPercentage?: number;
101
105
  diffCount?: number;
102
106
  error?: string;
@@ -465,6 +469,7 @@ export function vizzlyScreenshot(
465
469
  options?: {
466
470
  properties?: Record<string, unknown>;
467
471
  threshold?: number;
472
+ minClusterSize?: number;
468
473
  fullPage?: boolean;
469
474
  }
470
475
  ): Promise<void>;
@@ -36,9 +36,11 @@ const uploadSchema = z.object({
36
36
  /**
37
37
  * Comparison configuration schema
38
38
  * threshold: CIEDE2000 Delta E units (0.0 = exact, 1.0 = JND, 2.0 = recommended, 3.0+ = permissive)
39
+ * minClusterSize: pixels (1 = exact)
39
40
  */
40
41
  const comparisonSchema = z.object({
41
- threshold: z.number().min(0).default(2.0)
42
+ threshold: z.number().min(0).default(2.0),
43
+ minClusterSize: z.int().min(1).default(2)
42
44
  });
43
45
 
44
46
  /**
@@ -70,11 +72,13 @@ export const vizzlyConfigSchema = z.object({
70
72
  timeout: 30000
71
73
  }),
72
74
  comparison: comparisonSchema.default({
73
- threshold: 2.0
75
+ threshold: 2.0,
76
+ minClusterSize: 2
74
77
  }),
75
78
  tdd: tddSchema.default({
76
79
  openReport: false
77
80
  }),
81
+ signatureProperties: z.array(z.string()).default([]),
78
82
  plugins: z.array(z.string()).default([]),
79
83
  // Additional optional fields
80
84
  parallelId: z.string().optional(),
@@ -99,7 +103,8 @@ export const vizzlyConfigSchema = z.object({
99
103
  timeout: 30000
100
104
  },
101
105
  comparison: {
102
- threshold: 2.0
106
+ threshold: 2.0,
107
+ minClusterSize: 2
103
108
  },
104
109
  tdd: {
105
110
  openReport: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",