@vizzly-testing/cli 0.34.0 → 0.35.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.
@@ -24,7 +24,9 @@ export interface BuildConfig {
24
24
 
25
25
  export interface UploadConfig {
26
26
  screenshotsDir?: string | string[];
27
+ /** Batch size used when uploading screenshots. */
27
28
  batchSize?: number;
29
+ /** Timeout in milliseconds for waiting on build processing after upload. */
28
30
  timeout?: number;
29
31
  }
30
32
 
@@ -76,13 +78,22 @@ export interface ScreenshotOptions {
76
78
  threshold?: number;
77
79
  minClusterSize?: number;
78
80
  fullPage?: boolean;
81
+ /** Transport-only build ID used to route the request. */
79
82
  buildId?: string;
80
- [key: string]: unknown;
83
+ /** Client-side HTTP timeout in milliseconds; not stored as metadata. */
84
+ requestTimeout?: number;
81
85
  }
82
86
 
83
87
  export interface ScreenshotResult {
84
88
  success: boolean;
85
- status?: 'passed' | 'failed' | 'new';
89
+ status?:
90
+ | 'passed'
91
+ | 'failed'
92
+ | 'new'
93
+ | 'match'
94
+ | 'diff'
95
+ | 'baseline-updated'
96
+ | 'error';
86
97
  name?: string;
87
98
  diffPercentage?: number;
88
99
  [key: string]: unknown;
@@ -95,7 +106,13 @@ export interface ScreenshotResult {
95
106
  export interface ComparisonResult {
96
107
  id: string;
97
108
  name: string;
98
- status: 'passed' | 'failed' | 'new' | 'error' | 'baseline-updated';
109
+ status:
110
+ | 'passed'
111
+ | 'failed'
112
+ | 'new'
113
+ | 'error'
114
+ | 'baseline-created'
115
+ | 'baseline-updated';
99
116
  baseline: string;
100
117
  current: string;
101
118
  diff: string | null;
@@ -150,7 +167,9 @@ export interface UploadOptions {
150
167
  message?: string;
151
168
  environment?: string;
152
169
  threshold?: number;
153
- pullRequestNumber?: string;
170
+ minClusterSize?: number;
171
+ metadata?: Record<string, unknown>;
172
+ pullRequestNumber?: string | number;
154
173
  parallelId?: string;
155
174
  onProgress?: (progress: UploadProgress) => void;
156
175
  }
@@ -186,8 +205,10 @@ export interface BuildResult {
186
205
  status: 'completed' | 'failed' | 'pending';
187
206
  build: unknown;
188
207
  comparisons?: number;
208
+ totalComparisons: number;
189
209
  passedComparisons?: number;
190
210
  failedComparisons?: number;
211
+ newComparisons: number;
191
212
  url?: string;
192
213
  }
193
214
 
@@ -487,15 +508,19 @@ export interface BuildOptions {
487
508
  buildName?: string;
488
509
  branch?: string;
489
510
  commit?: string;
511
+ commit_sha?: string;
490
512
  message?: string;
513
+ commit_message?: string;
491
514
  environment?: string;
492
515
  threshold?: number;
493
516
  eager?: boolean;
494
517
  allowNoToken?: boolean;
495
518
  wait?: boolean;
496
519
  uploadAll?: boolean;
497
- pullRequestNumber?: string;
520
+ pullRequestNumber?: string | number;
521
+ github_pull_request_number?: string | number;
498
522
  parallelId?: string;
523
+ parallel_id?: string;
499
524
  }
500
525
 
501
526
  /**
@@ -565,6 +590,8 @@ export function vizzlyScreenshot(
565
590
  threshold?: number;
566
591
  minClusterSize?: number;
567
592
  fullPage?: boolean;
593
+ requestTimeout?: number;
594
+ buildId?: string;
568
595
  [key: string]: unknown;
569
596
  }
570
597
  ): Promise<ScreenshotResult | null>;
@@ -573,6 +600,7 @@ export function vizzlyScreenshot(
573
600
  export function configure(config?: {
574
601
  serverUrl?: string;
575
602
  enabled?: boolean;
603
+ failOnDiff?: boolean;
576
604
  }): void;
577
605
 
578
606
  /** Enable or disable screenshot capture */
@@ -123,6 +123,25 @@ export function extractBrowserFromFilename(filename) {
123
123
  // Build Info Construction
124
124
  // ============================================================================
125
125
 
126
+ function buildUploadMetadata(options) {
127
+ let metadata = {
128
+ ...(options.metadata || {})
129
+ };
130
+ let comparison = {
131
+ ...(metadata.comparison || {})
132
+ };
133
+ if (options.threshold !== undefined) {
134
+ comparison.threshold = options.threshold;
135
+ }
136
+ if (options.minClusterSize !== undefined) {
137
+ comparison.minClusterSize = options.minClusterSize;
138
+ }
139
+ if (Object.keys(comparison).length > 0) {
140
+ metadata.comparison = comparison;
141
+ }
142
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
143
+ }
144
+
126
145
  /**
127
146
  * Build API build info payload
128
147
  * @param {Object} options - Upload options
@@ -130,7 +149,7 @@ export function extractBrowserFromFilename(filename) {
130
149
  * @returns {Object} Build info payload
131
150
  */
132
151
  export function buildBuildInfo(options, defaultBranch = 'main') {
133
- return {
152
+ let buildInfo = {
134
153
  name: options.buildName || `Upload ${new Date().toISOString()}`,
135
154
  branch: options.branch || defaultBranch || 'main',
136
155
  commit_sha: options.commit,
@@ -140,6 +159,11 @@ export function buildBuildInfo(options, defaultBranch = 'main') {
140
159
  github_pull_request_number: options.pullRequestNumber,
141
160
  parallel_id: options.parallelId
142
161
  };
162
+ let metadata = buildUploadMetadata(options);
163
+ if (metadata) {
164
+ buildInfo.metadata = metadata;
165
+ }
166
+ return buildInfo;
143
167
  }
144
168
 
145
169
  // ============================================================================
@@ -316,17 +340,25 @@ export function buildUploadResult({
316
340
  * @returns {Object} Wait result
317
341
  */
318
342
  export function buildWaitResult(build) {
343
+ let totalComparisons = build.comparisonsTotal ?? build.total_comparisons ?? 0;
344
+ let passedComparisons = build.comparisonsPassed ?? build.passed_comparisons ?? build.identicalComparisons ?? build.identical_comparisons ?? 0;
345
+ let failedComparisons = build.comparisonsFailed ?? build.failed_comparisons ?? build.changedComparisons ?? build.changed_comparisons ?? 0;
346
+ let newComparisons = build.comparisonsNew ?? build.newComparisons ?? build.new_comparisons ?? 0;
347
+ let identicalComparisons = build.identicalComparisons ?? build.identical_comparisons ?? passedComparisons;
348
+ let approvalStatus = build.approvalStatus ?? build.approval_status ?? 'pending';
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;
319
350
  let result = {
320
351
  status: 'completed',
321
- build
352
+ build,
353
+ passedComparisons,
354
+ failedComparisons,
355
+ newComparisons,
356
+ identicalComparisons,
357
+ approvalStatus,
358
+ totalComparisons
322
359
  };
323
- if (typeof build.comparisonsTotal === 'number') {
324
- result.comparisons = build.comparisonsTotal;
325
- result.passedComparisons = build.comparisonsPassed || 0;
326
- result.failedComparisons = build.comparisonsFailed || 0;
327
- } else {
328
- result.passedComparisons = 0;
329
- result.failedComparisons = 0;
360
+ if (hasComparisonCounts) {
361
+ result.comparisons = totalComparisons;
330
362
  }
331
363
  if (build.url) {
332
364
  result.url = build.url;
@@ -386,23 +386,25 @@ export async function upload({
386
386
  let buildInfo = buildBuildInfo(uploadOptions, defaultBranch);
387
387
  let build = await createBuild(client, buildInfo);
388
388
  let buildId = build.id;
389
-
390
- // Check existing files
391
- let {
392
- toUpload,
393
- existing,
394
- screenshots
395
- } = await checkExistingFiles({
396
- fileMetadata,
397
- client,
398
- signal,
399
- buildId,
400
- deps: {
401
- checkShas,
402
- createError,
403
- output
404
- }
405
- });
389
+ let toUpload = fileMetadata;
390
+ let existing = [];
391
+ let screenshots = [];
392
+ if (!uploadOptions.uploadAll) {
393
+ let existingResult = await checkExistingFiles({
394
+ fileMetadata,
395
+ client,
396
+ signal,
397
+ buildId,
398
+ deps: {
399
+ checkShas,
400
+ createError,
401
+ output
402
+ }
403
+ });
404
+ toUpload = existingResult.toUpload;
405
+ existing = existingResult.existing;
406
+ screenshots = existingResult.screenshots;
407
+ }
406
408
  onProgress(buildDeduplicationProgress(toUpload.length, existing.length, files.length));
407
409
 
408
410
  // Upload files
@@ -2,7 +2,7 @@ import { resolve } from 'node:path';
2
2
  import { cosmiconfigSync } from 'cosmiconfig';
3
3
  import { CONFIG_DEFAULTS, deepMerge } from '../config/core.js';
4
4
  import { validateVizzlyConfigWithDefaults } from './config-schema.js';
5
- import { getApiToken, getApiUrl, getBuildName, getParallelId } from './environment-config.js';
5
+ import { getApiToken, getApiUrl, getBuildName, getMinClusterSize, getParallelId, getThreshold } from './environment-config.js';
6
6
  import { getAccessToken } from './global-config.js';
7
7
  import * as output from './output.js';
8
8
  import { getActiveProjectLink } from './project-link-store.js';
@@ -26,10 +26,12 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
26
26
  mergeConfig(config, validatedFileConfig);
27
27
 
28
28
  // 3. Override with environment variables (higher priority than fallbacks)
29
- const envApiKey = getApiToken();
30
- const envApiUrl = getApiUrl();
31
- const envBuildName = getBuildName();
32
- const envParallelId = getParallelId();
29
+ let envApiKey = getApiToken();
30
+ let envApiUrl = getApiUrl();
31
+ let envBuildName = getBuildName();
32
+ let envParallelId = getParallelId();
33
+ let envThreshold = getThreshold();
34
+ let envMinClusterSize = getMinClusterSize();
33
35
  if (envApiKey) {
34
36
  config.apiKey = envApiKey;
35
37
  output.debug('config', 'using token from environment');
@@ -40,6 +42,10 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
40
42
  output.debug('config', 'using build name from environment');
41
43
  }
42
44
  if (envParallelId) config.parallelId = envParallelId;
45
+ if (envThreshold !== undefined) config.comparison.threshold = envThreshold;
46
+ if (envMinClusterSize !== undefined) {
47
+ config.comparison.minClusterSize = envMinClusterSize;
48
+ }
43
49
 
44
50
  // 4. Apply CLI overrides (highest priority)
45
51
  if (cliOverrides.token) {
@@ -109,7 +115,7 @@ function applyCLIOverrides(config, cliOverrides = {}) {
109
115
  }
110
116
 
111
117
  // Comparison overrides
112
- if (cliOverrides.threshold !== undefined) config.comparison.threshold = cliOverrides.threshold;
118
+ if (cliOverrides.threshold !== undefined) config.comparison.threshold = Number(cliOverrides.threshold);
113
119
  if (cliOverrides.minClusterSize !== undefined) {
114
120
  config.comparison.minClusterSize = Number(cliOverrides.minClusterSize);
115
121
  }
@@ -11,6 +11,37 @@
11
11
  import { existsSync, readFileSync } from 'node:fs';
12
12
  import { homedir } from 'node:os';
13
13
  import { join } from 'node:path';
14
+ function getGlobalConfigPath() {
15
+ return join(process.env.VIZZLY_HOME || join(homedir(), '.vizzly'), 'config.json');
16
+ }
17
+ function readJsonFile(path) {
18
+ try {
19
+ if (existsSync(path)) {
20
+ return JSON.parse(readFileSync(path, 'utf8'));
21
+ }
22
+ } catch {
23
+ // Optional context should never block the command that is rendering it.
24
+ }
25
+ return null;
26
+ }
27
+ function readGlobalConfig() {
28
+ return readJsonFile(getGlobalConfigPath()) || {};
29
+ }
30
+ function readBaselineInfo(cwd) {
31
+ let path = join(cwd, '.vizzly', 'baselines');
32
+ let metadata = readJsonFile(join(path, 'metadata.json'));
33
+ return {
34
+ count: metadata?.screenshots?.length || 0,
35
+ path: metadata ? path : null
36
+ };
37
+ }
38
+ function readServerInfo(cwd) {
39
+ let serverInfo = readJsonFile(join(cwd, '.vizzly', 'server.json'));
40
+ return {
41
+ running: !!serverInfo,
42
+ port: serverInfo ? serverInfo.port : null
43
+ };
44
+ }
14
45
 
15
46
  /**
16
47
  * Get dynamic context about the current Vizzly state
@@ -22,46 +53,12 @@ export function getContext() {
22
53
  let items = [];
23
54
  try {
24
55
  let cwd = process.cwd();
25
- let globalConfigPath = join(process.env.VIZZLY_HOME || join(homedir(), '.vizzly'), 'config.json');
26
-
27
- // Load global config once
28
- let globalConfig = {};
29
- try {
30
- if (existsSync(globalConfigPath)) {
31
- globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf8'));
32
- }
33
- } catch {
34
- // Ignore
35
- }
56
+ let globalConfig = readGlobalConfig();
36
57
 
37
58
  // Check for vizzly.config.js (project config)
38
59
  let hasProjectConfig = existsSync(join(cwd, 'vizzly.config.js'));
39
-
40
- // Check for .vizzly directory (TDD baselines)
41
- let baselineCount = 0;
42
- try {
43
- let metaPath = join(cwd, '.vizzly', 'baselines', 'metadata.json');
44
- if (existsSync(metaPath)) {
45
- let meta = JSON.parse(readFileSync(metaPath, 'utf8'));
46
- baselineCount = meta.screenshots?.length || 0;
47
- }
48
- } catch {
49
- // Ignore
50
- }
51
-
52
- // Check for TDD server running
53
- let serverRunning = false;
54
- let serverPort = null;
55
- try {
56
- let serverFile = join(cwd, '.vizzly', 'server.json');
57
- if (existsSync(serverFile)) {
58
- let serverInfo = JSON.parse(readFileSync(serverFile, 'utf8'));
59
- serverPort = serverInfo.port;
60
- serverRunning = true;
61
- }
62
- } catch {
63
- // Ignore
64
- }
60
+ let baselineInfo = readBaselineInfo(cwd);
61
+ let serverInfo = readServerInfo(cwd);
65
62
 
66
63
  // Check for OAuth login (from vizzly login)
67
64
  let isLoggedIn = !!globalConfig.auth?.accessToken;
@@ -71,11 +68,11 @@ export function getContext() {
71
68
  let hasEnvToken = !!process.env.VIZZLY_TOKEN;
72
69
 
73
70
  // Build context items - prioritize most useful info
74
- if (serverRunning) {
71
+ if (serverInfo.running) {
75
72
  items.push({
76
73
  type: 'success',
77
74
  label: 'TDD Server',
78
- value: `running on :${serverPort}`
75
+ value: `running on :${serverInfo.port}`
79
76
  });
80
77
  }
81
78
  if (isLoggedIn && userName) {
@@ -97,14 +94,14 @@ export function getContext() {
97
94
  value: 'run vizzly login'
98
95
  });
99
96
  }
100
- if (baselineCount > 0) {
97
+ if (baselineInfo.count > 0) {
101
98
  items.push({
102
99
  type: 'success',
103
100
  label: 'Baselines',
104
- value: `${baselineCount} screenshots`
101
+ value: `${baselineInfo.count} screenshots`
105
102
  });
106
103
  }
107
- if (!hasProjectConfig && !serverRunning && baselineCount === 0) {
104
+ if (!hasProjectConfig && !serverInfo.running && baselineInfo.count === 0) {
108
105
  // Only show "no config" hint if there's nothing else useful
109
106
  items.push({
110
107
  type: 'info',
@@ -126,7 +123,6 @@ export function getContext() {
126
123
  */
127
124
  export function getDetailedContext() {
128
125
  let cwd = process.cwd();
129
- let globalConfigPath = join(process.env.VIZZLY_HOME || join(homedir(), '.vizzly'), 'config.json');
130
126
  let context = {
131
127
  tddServer: {
132
128
  running: false,
@@ -146,42 +142,16 @@ export function getDetailedContext() {
146
142
  }
147
143
  };
148
144
  try {
149
- // Load global config
150
- let globalConfig = {};
151
- try {
152
- if (existsSync(globalConfigPath)) {
153
- globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf8'));
154
- }
155
- } catch {
156
- // Ignore
157
- }
145
+ let globalConfig = readGlobalConfig();
158
146
 
159
147
  // Check for vizzly.config.js
160
148
  context.project.hasConfig = existsSync(join(cwd, 'vizzly.config.js'));
161
-
162
- // Check for baselines
163
- try {
164
- let metaPath = join(cwd, '.vizzly', 'baselines', 'metadata.json');
165
- if (existsSync(metaPath)) {
166
- let meta = JSON.parse(readFileSync(metaPath, 'utf8'));
167
- context.baselines.count = meta.screenshots?.length || 0;
168
- context.baselines.path = join(cwd, '.vizzly', 'baselines');
169
- }
170
- } catch {
171
- // Ignore
172
- }
173
-
174
- // Check for TDD server
175
- try {
176
- let serverFile = join(cwd, '.vizzly', 'server.json');
177
- if (existsSync(serverFile)) {
178
- let serverInfo = JSON.parse(readFileSync(serverFile, 'utf8'));
179
- context.tddServer.running = true;
180
- context.tddServer.port = serverInfo.port;
181
- }
182
- } catch {
183
- // Ignore
184
- }
149
+ let baselineInfo = readBaselineInfo(cwd);
150
+ context.baselines.count = baselineInfo.count;
151
+ context.baselines.path = baselineInfo.path;
152
+ let serverInfo = readServerInfo(cwd);
153
+ context.tddServer.running = serverInfo.running;
154
+ context.tddServer.port = serverInfo.port;
185
155
 
186
156
  // Check auth status
187
157
  context.auth.loggedIn = !!globalConfig.auth?.accessToken;
@@ -85,6 +85,24 @@ export function getBuildName() {
85
85
  return process.env.VIZZLY_BUILD_NAME;
86
86
  }
87
87
 
88
+ /**
89
+ * Get comparison threshold from environment
90
+ * @returns {number|undefined} Threshold value when set
91
+ */
92
+ export function getThreshold() {
93
+ if (process.env.VIZZLY_THRESHOLD === undefined) return undefined;
94
+ return Number(process.env.VIZZLY_THRESHOLD);
95
+ }
96
+
97
+ /**
98
+ * Get minimum changed cluster size from environment
99
+ * @returns {number|undefined} Minimum cluster size when set
100
+ */
101
+ export function getMinClusterSize() {
102
+ if (process.env.VIZZLY_MIN_CLUSTER_SIZE === undefined) return undefined;
103
+ return Number(process.env.VIZZLY_MIN_CLUSTER_SIZE);
104
+ }
105
+
88
106
  /**
89
107
  * Check if TDD mode is enabled
90
108
  * @returns {boolean} Whether TDD mode is enabled
@@ -117,6 +135,8 @@ export function getAllEnvironmentConfig() {
117
135
  buildId: getBuildId(),
118
136
  buildName: getBuildName(),
119
137
  parallelId: getParallelId(),
138
+ threshold: getThreshold(),
139
+ minClusterSize: getMinClusterSize(),
120
140
  tddMode: isTddMode()
121
141
  };
122
142
  }
@@ -282,7 +282,7 @@ export function success(message, data = {}) {
282
282
  });
283
283
  if (config.silent) return;
284
284
  if (config.json) {
285
- console.log(JSON.stringify({
285
+ console.error(JSON.stringify({
286
286
  status: 'success',
287
287
  message,
288
288
  ...data
@@ -301,7 +301,7 @@ export function result(message) {
301
301
  if (config.silent) return;
302
302
  const elapsed = getElapsedTime();
303
303
  if (config.json) {
304
- console.log(JSON.stringify({
304
+ console.error(JSON.stringify({
305
305
  status: 'complete',
306
306
  message,
307
307
  elapsed
@@ -319,7 +319,7 @@ export function info(message, data = {}) {
319
319
  writeLog('info', message, data);
320
320
  if (!shouldLog('info')) return;
321
321
  if (config.json) {
322
- console.log(JSON.stringify({
322
+ console.error(JSON.stringify({
323
323
  status: 'info',
324
324
  message,
325
325
  ...data
@@ -513,7 +513,7 @@ export function stopSpinner() {
513
513
  export function progress(message, current = 0, total = 0) {
514
514
  if (config.silent) return;
515
515
  if (config.json) {
516
- console.log(JSON.stringify({
516
+ console.error(JSON.stringify({
517
517
  status: 'progress',
518
518
  message,
519
519
  progress: {
@@ -941,7 +941,7 @@ export function complete(message, options = {}) {
941
941
  } = options;
942
942
  let detailStr = detail ? ` ${colors.brand.textMuted(detail)}` : '';
943
943
  if (config.json) {
944
- console.log(JSON.stringify({
944
+ console.error(JSON.stringify({
945
945
  status: 'complete',
946
946
  message,
947
947
  detail
@@ -1,21 +1,63 @@
1
+ /**
2
+ * Screenshot option names that are part of the SDK/config contract, not the
3
+ * user's arbitrary metadata bag.
4
+ */
5
+ export let RESERVED_PROPERTY_OPTIONS = Object.freeze({
6
+ threshold: {
7
+ message: 'Move "threshold" out of properties; properties is only for user metadata.'
8
+ },
9
+ minClusterSize: {
10
+ message: 'Move "minClusterSize" out of properties; properties is only for user metadata.'
11
+ },
12
+ fullPage: {
13
+ message: 'Move "fullPage" out of properties; properties is only for user metadata.'
14
+ },
15
+ buildId: {
16
+ message: 'Move "buildId" out of properties; properties is only for user metadata.'
17
+ },
18
+ requestTimeout: {
19
+ message: 'Move "requestTimeout" out of properties; properties is only for user metadata.'
20
+ }
21
+ });
22
+ function createReservedPropertyWarning(option) {
23
+ return {
24
+ code: 'reserved-property-option',
25
+ option,
26
+ message: RESERVED_PROPERTY_OPTIONS[option].message
27
+ };
28
+ }
29
+
1
30
  /**
2
31
  * Normalize screenshot SDK options into the properties payload consumed by the
3
32
  * local TDD server and cloud-compatible comparison path.
4
33
  */
5
- export function createScreenshotProperties(options = {}) {
34
+ export function normalizeScreenshotOptions(options = {}) {
6
35
  let {
7
- buildId: _buildId,
36
+ buildId,
8
37
  properties = {},
9
- requestTimeout: _requestTimeout,
38
+ requestTimeout,
10
39
  threshold,
11
40
  minClusterSize,
12
- fullPage,
13
- ...topLevelProperties
41
+ fullPage
14
42
  } = options;
15
- let normalizedProperties = {
16
- ...topLevelProperties,
17
- ...properties
18
- };
43
+ let warnings = [];
44
+ let normalizedProperties = {};
45
+ for (let [key, value] of Object.entries(properties || {})) {
46
+ if (RESERVED_PROPERTY_OPTIONS[key]) {
47
+ warnings.push(createReservedPropertyWarning(key));
48
+ if (key === 'threshold' && threshold === undefined) threshold = value;
49
+ if (key === 'minClusterSize' && minClusterSize === undefined) {
50
+ minClusterSize = value;
51
+ }
52
+ if (key === 'fullPage' && fullPage === undefined) fullPage = value;
53
+ if (key === 'buildId' && buildId === undefined) buildId = value;
54
+ if (key === 'requestTimeout' && requestTimeout === undefined) {
55
+ requestTimeout = value;
56
+ }
57
+ continue;
58
+ }
59
+ normalizedProperties[key] = value;
60
+ }
19
61
  if (threshold !== undefined) {
20
62
  normalizedProperties.threshold = threshold;
21
63
  }
@@ -25,5 +67,13 @@ export function createScreenshotProperties(options = {}) {
25
67
  if (fullPage !== undefined) {
26
68
  normalizedProperties.fullPage = fullPage;
27
69
  }
28
- return normalizedProperties;
70
+ return {
71
+ buildId,
72
+ requestTimeout,
73
+ properties: normalizedProperties,
74
+ warnings
75
+ };
76
+ }
77
+ export function createScreenshotProperties(options = {}) {
78
+ return normalizeScreenshotOptions(options).properties;
29
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Visual regression testing from your terminal",
5
5
  "keywords": [
6
6
  "llm-context",