@vizzly-testing/cli 0.20.0 → 0.20.1-beta.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.
- package/dist/api/client.js +134 -0
- package/dist/api/core.js +341 -0
- package/dist/api/endpoints.js +314 -0
- package/dist/api/index.js +19 -0
- package/dist/auth/client.js +91 -0
- package/dist/auth/core.js +176 -0
- package/dist/auth/index.js +30 -0
- package/dist/auth/operations.js +148 -0
- package/dist/cli.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/finalize.js +41 -15
- package/dist/commands/login.js +7 -6
- package/dist/commands/logout.js +4 -4
- package/dist/commands/project.js +5 -4
- package/dist/commands/run.js +158 -90
- package/dist/commands/status.js +22 -18
- package/dist/commands/tdd.js +105 -78
- package/dist/commands/upload.js +61 -26
- package/dist/commands/whoami.js +4 -4
- package/dist/config/core.js +438 -0
- package/dist/config/index.js +13 -0
- package/dist/config/operations.js +327 -0
- package/dist/index.js +1 -1
- package/dist/project/core.js +295 -0
- package/dist/project/index.js +13 -0
- package/dist/project/operations.js +393 -0
- package/dist/report-generator/core.js +315 -0
- package/dist/report-generator/index.js +8 -0
- package/dist/report-generator/operations.js +196 -0
- package/dist/reporter/reporter-bundle.iife.js +16 -16
- package/dist/screenshot-server/core.js +157 -0
- package/dist/screenshot-server/index.js +11 -0
- package/dist/screenshot-server/operations.js +183 -0
- package/dist/sdk/index.js +3 -2
- package/dist/server/handlers/api-handler.js +14 -5
- package/dist/server/handlers/tdd-handler.js +80 -48
- package/dist/server-manager/core.js +183 -0
- package/dist/server-manager/index.js +81 -0
- package/dist/server-manager/operations.js +208 -0
- package/dist/services/build-manager.js +2 -69
- package/dist/services/index.js +21 -48
- package/dist/services/screenshot-server.js +40 -74
- package/dist/services/server-manager.js +45 -80
- package/dist/services/static-report-generator.js +21 -163
- package/dist/services/test-runner.js +90 -250
- package/dist/services/uploader.js +56 -358
- package/dist/tdd/core/hotspot-coverage.js +112 -0
- package/dist/tdd/core/signature.js +101 -0
- package/dist/tdd/index.js +19 -0
- package/dist/tdd/metadata/baseline-metadata.js +103 -0
- package/dist/tdd/metadata/hotspot-metadata.js +93 -0
- package/dist/tdd/services/baseline-downloader.js +151 -0
- package/dist/tdd/services/baseline-manager.js +166 -0
- package/dist/tdd/services/comparison-service.js +230 -0
- package/dist/tdd/services/hotspot-service.js +71 -0
- package/dist/tdd/services/result-service.js +123 -0
- package/dist/tdd/tdd-service.js +1081 -0
- package/dist/test-runner/core.js +255 -0
- package/dist/test-runner/index.js +13 -0
- package/dist/test-runner/operations.js +483 -0
- package/dist/uploader/core.js +396 -0
- package/dist/uploader/index.js +11 -0
- package/dist/uploader/operations.js +412 -0
- package/package.json +7 -12
- package/dist/services/api-service.js +0 -412
- package/dist/services/auth-service.js +0 -226
- package/dist/services/config-service.js +0 -369
- package/dist/services/html-report-generator.js +0 -455
- package/dist/services/project-service.js +0 -326
- package/dist/services/report-generator/report.css +0 -411
- package/dist/services/report-generator/viewer.js +0 -102
- package/dist/services/tdd-service.js +0 -1437
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uploader Core - Pure functions for upload logic
|
|
3
|
+
*
|
|
4
|
+
* No I/O, no side effects - just data transformations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import { basename } from 'node:path';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Constants
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_BATCH_SIZE = 50;
|
|
15
|
+
export const DEFAULT_SHA_CHECK_BATCH_SIZE = 100;
|
|
16
|
+
export const DEFAULT_TIMEOUT = 30000; // 30 seconds
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Validation
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate API key is present
|
|
24
|
+
* @param {string|undefined} apiKey - API key to validate
|
|
25
|
+
* @returns {{ valid: boolean, error: string|null }}
|
|
26
|
+
*/
|
|
27
|
+
export function validateApiKey(apiKey) {
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
return {
|
|
30
|
+
valid: false,
|
|
31
|
+
error: 'API key is required'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
valid: true,
|
|
36
|
+
error: null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate screenshots directory path
|
|
42
|
+
* @param {string|undefined} screenshotsDir - Directory path
|
|
43
|
+
* @returns {{ valid: boolean, error: string|null }}
|
|
44
|
+
*/
|
|
45
|
+
export function validateScreenshotsDir(screenshotsDir) {
|
|
46
|
+
if (!screenshotsDir) {
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
error: 'Screenshots directory is required'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
valid: true,
|
|
54
|
+
error: null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate directory stats
|
|
60
|
+
* @param {Object} stats - fs.stat result
|
|
61
|
+
* @param {string} path - Directory path for error message
|
|
62
|
+
* @returns {{ valid: boolean, error: string|null }}
|
|
63
|
+
*/
|
|
64
|
+
export function validateDirectoryStats(stats, path) {
|
|
65
|
+
if (!stats.isDirectory()) {
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
error: `${path} is not a directory`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
valid: true,
|
|
73
|
+
error: null
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate files were found
|
|
79
|
+
* @param {Array} files - Array of file paths
|
|
80
|
+
* @param {string} directory - Directory that was searched
|
|
81
|
+
* @returns {{ valid: boolean, error: string|null, context?: Object }}
|
|
82
|
+
*/
|
|
83
|
+
export function validateFilesFound(files, directory) {
|
|
84
|
+
if (!files || files.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
error: 'No screenshot files found',
|
|
88
|
+
context: {
|
|
89
|
+
directory,
|
|
90
|
+
pattern: '**/*.png'
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: true,
|
|
96
|
+
error: null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Browser Extraction
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
const KNOWN_BROWSERS = ['chrome', 'firefox', 'safari', 'edge', 'webkit'];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract browser name from filename
|
|
108
|
+
* @param {string} filename - The screenshot filename
|
|
109
|
+
* @returns {string|null} Browser name or null if not found
|
|
110
|
+
*/
|
|
111
|
+
export function extractBrowserFromFilename(filename) {
|
|
112
|
+
let lowerFilename = filename.toLowerCase();
|
|
113
|
+
for (let browser of KNOWN_BROWSERS) {
|
|
114
|
+
if (lowerFilename.includes(browser)) {
|
|
115
|
+
return browser;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Build Info Construction
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build API build info payload
|
|
127
|
+
* @param {Object} options - Upload options
|
|
128
|
+
* @param {string} [defaultBranch] - Default branch to use
|
|
129
|
+
* @returns {Object} Build info payload
|
|
130
|
+
*/
|
|
131
|
+
export function buildBuildInfo(options, defaultBranch = 'main') {
|
|
132
|
+
return {
|
|
133
|
+
name: options.buildName || `Upload ${new Date().toISOString()}`,
|
|
134
|
+
branch: options.branch || defaultBranch || 'main',
|
|
135
|
+
commit_sha: options.commit,
|
|
136
|
+
commit_message: options.message,
|
|
137
|
+
environment: options.environment || 'production',
|
|
138
|
+
threshold: options.threshold,
|
|
139
|
+
github_pull_request_number: options.pullRequestNumber,
|
|
140
|
+
parallel_id: options.parallelId
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// File Metadata Processing
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Compute SHA256 hash of a buffer
|
|
150
|
+
* @param {Buffer} buffer - File buffer
|
|
151
|
+
* @returns {string} Hex-encoded SHA256 hash
|
|
152
|
+
*/
|
|
153
|
+
export function computeSha256(buffer) {
|
|
154
|
+
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build file metadata object
|
|
159
|
+
* @param {string} filePath - Path to file
|
|
160
|
+
* @param {Buffer} buffer - File contents
|
|
161
|
+
* @returns {Object} File metadata
|
|
162
|
+
*/
|
|
163
|
+
export function buildFileMetadata(filePath, buffer) {
|
|
164
|
+
return {
|
|
165
|
+
path: filePath,
|
|
166
|
+
filename: basename(filePath),
|
|
167
|
+
buffer,
|
|
168
|
+
sha256: computeSha256(buffer)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Convert file metadata to screenshot check format
|
|
174
|
+
* @param {Object} file - File metadata
|
|
175
|
+
* @returns {Object} Screenshot format for SHA check
|
|
176
|
+
*/
|
|
177
|
+
export function fileToScreenshotFormat(file) {
|
|
178
|
+
return {
|
|
179
|
+
sha256: file.sha256,
|
|
180
|
+
name: file.filename.replace(/\.png$/, ''),
|
|
181
|
+
browser: extractBrowserFromFilename(file.filename) || 'chrome',
|
|
182
|
+
viewport_width: 1920,
|
|
183
|
+
viewport_height: 1080
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Partition files into those that need upload and those that exist
|
|
189
|
+
* @param {Array} fileMetadata - All file metadata
|
|
190
|
+
* @param {Set} existingShas - Set of SHAs that already exist
|
|
191
|
+
* @returns {{ toUpload: Array, existing: Array }}
|
|
192
|
+
*/
|
|
193
|
+
export function partitionFilesByExistence(fileMetadata, existingShas) {
|
|
194
|
+
return {
|
|
195
|
+
toUpload: fileMetadata.filter(f => !existingShas.has(f.sha256)),
|
|
196
|
+
existing: fileMetadata.filter(f => existingShas.has(f.sha256))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Progress Reporting
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build scanning phase progress
|
|
206
|
+
* @param {number} total - Total files found
|
|
207
|
+
* @returns {Object} Progress object
|
|
208
|
+
*/
|
|
209
|
+
export function buildScanningProgress(total) {
|
|
210
|
+
return {
|
|
211
|
+
phase: 'scanning',
|
|
212
|
+
message: `Found ${total} screenshots`,
|
|
213
|
+
total
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Build processing phase progress
|
|
219
|
+
* @param {number} current - Current file number
|
|
220
|
+
* @param {number} total - Total files
|
|
221
|
+
* @returns {Object} Progress object
|
|
222
|
+
*/
|
|
223
|
+
export function buildProcessingProgress(current, total) {
|
|
224
|
+
return {
|
|
225
|
+
phase: 'processing',
|
|
226
|
+
message: 'Processing files',
|
|
227
|
+
current,
|
|
228
|
+
total
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Build deduplication phase progress
|
|
234
|
+
* @param {number} toUpload - Files to upload
|
|
235
|
+
* @param {number} existing - Existing files
|
|
236
|
+
* @param {number} total - Total files
|
|
237
|
+
* @returns {Object} Progress object
|
|
238
|
+
*/
|
|
239
|
+
export function buildDeduplicationProgress(toUpload, existing, total) {
|
|
240
|
+
return {
|
|
241
|
+
phase: 'deduplication',
|
|
242
|
+
message: `Checking for duplicates (${toUpload} to upload, ${existing} existing)`,
|
|
243
|
+
toUpload,
|
|
244
|
+
existing,
|
|
245
|
+
total
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build uploading phase progress
|
|
251
|
+
* @param {number} current - Current upload number
|
|
252
|
+
* @param {number} total - Total to upload
|
|
253
|
+
* @returns {Object} Progress object
|
|
254
|
+
*/
|
|
255
|
+
export function buildUploadingProgress(current, total) {
|
|
256
|
+
return {
|
|
257
|
+
phase: 'uploading',
|
|
258
|
+
message: 'Uploading screenshots',
|
|
259
|
+
current,
|
|
260
|
+
total
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Build completed phase progress
|
|
266
|
+
* @param {string} buildId - Build ID
|
|
267
|
+
* @param {string|null} url - Build URL
|
|
268
|
+
* @returns {Object} Progress object
|
|
269
|
+
*/
|
|
270
|
+
export function buildCompletedProgress(buildId, url) {
|
|
271
|
+
return {
|
|
272
|
+
phase: 'completed',
|
|
273
|
+
message: 'Upload completed',
|
|
274
|
+
buildId,
|
|
275
|
+
url
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Result Building
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Build successful upload result
|
|
285
|
+
* @param {Object} options - Options
|
|
286
|
+
* @param {string} options.buildId - Build ID
|
|
287
|
+
* @param {string|null} options.url - Build URL
|
|
288
|
+
* @param {number} options.total - Total files
|
|
289
|
+
* @param {number} options.uploaded - Files uploaded
|
|
290
|
+
* @param {number} options.skipped - Files skipped
|
|
291
|
+
* @returns {Object} Upload result
|
|
292
|
+
*/
|
|
293
|
+
export function buildUploadResult({
|
|
294
|
+
buildId,
|
|
295
|
+
url,
|
|
296
|
+
total,
|
|
297
|
+
uploaded,
|
|
298
|
+
skipped
|
|
299
|
+
}) {
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
buildId,
|
|
303
|
+
url,
|
|
304
|
+
stats: {
|
|
305
|
+
total,
|
|
306
|
+
uploaded,
|
|
307
|
+
skipped
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Build wait result from build response
|
|
314
|
+
* @param {Object} build - Build object from API
|
|
315
|
+
* @returns {Object} Wait result
|
|
316
|
+
*/
|
|
317
|
+
export function buildWaitResult(build) {
|
|
318
|
+
let result = {
|
|
319
|
+
status: 'completed',
|
|
320
|
+
build
|
|
321
|
+
};
|
|
322
|
+
if (typeof build.comparisonsTotal === 'number') {
|
|
323
|
+
result.comparisons = build.comparisonsTotal;
|
|
324
|
+
result.passedComparisons = build.comparisonsPassed || 0;
|
|
325
|
+
result.failedComparisons = build.comparisonsFailed || 0;
|
|
326
|
+
} else {
|
|
327
|
+
result.passedComparisons = 0;
|
|
328
|
+
result.failedComparisons = 0;
|
|
329
|
+
}
|
|
330
|
+
if (build.url) {
|
|
331
|
+
result.url = build.url;
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Configuration
|
|
338
|
+
// ============================================================================
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Resolve batch size from options and config
|
|
342
|
+
* @param {Object} options - Runtime options
|
|
343
|
+
* @param {Object} uploadConfig - Upload configuration
|
|
344
|
+
* @returns {number} Resolved batch size
|
|
345
|
+
*/
|
|
346
|
+
export function resolveBatchSize(options, uploadConfig) {
|
|
347
|
+
return Number(options?.batchSize ?? uploadConfig?.batchSize ?? DEFAULT_BATCH_SIZE);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Resolve timeout from options and config
|
|
352
|
+
* @param {Object} options - Runtime options
|
|
353
|
+
* @param {Object} uploadConfig - Upload configuration
|
|
354
|
+
* @returns {number} Resolved timeout in ms
|
|
355
|
+
*/
|
|
356
|
+
export function resolveTimeout(options, uploadConfig) {
|
|
357
|
+
return Number(options?.timeout ?? uploadConfig?.timeout ?? DEFAULT_TIMEOUT);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Check if timeout has been exceeded
|
|
362
|
+
* @param {number} startTime - Start timestamp
|
|
363
|
+
* @param {number} timeout - Timeout in ms
|
|
364
|
+
* @returns {boolean} True if timed out
|
|
365
|
+
*/
|
|
366
|
+
export function isTimedOut(startTime, timeout) {
|
|
367
|
+
return Date.now() - startTime >= timeout;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get elapsed time since start
|
|
372
|
+
* @param {number} startTime - Start timestamp
|
|
373
|
+
* @returns {number} Elapsed time in ms
|
|
374
|
+
*/
|
|
375
|
+
export function getElapsedTime(startTime) {
|
|
376
|
+
return Date.now() - startTime;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Build glob pattern for screenshots
|
|
381
|
+
* @param {string} directory - Base directory
|
|
382
|
+
* @returns {string} Glob pattern
|
|
383
|
+
*/
|
|
384
|
+
export function buildScreenshotPattern(directory) {
|
|
385
|
+
return `${directory}/**/*.png`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Extract status code from error message
|
|
390
|
+
* @param {string} errorMessage - Error message
|
|
391
|
+
* @returns {string} Status code or 'unknown'
|
|
392
|
+
*/
|
|
393
|
+
export function extractStatusCodeFromError(errorMessage) {
|
|
394
|
+
let match = String(errorMessage || '').match(/API request failed: (\d+)/);
|
|
395
|
+
return match ? match[1] : 'unknown';
|
|
396
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uploader Module
|
|
3
|
+
*
|
|
4
|
+
* Exports pure functions (core) and I/O operations for screenshot uploading.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Core - pure functions
|
|
8
|
+
export { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, computeSha256, DEFAULT_BATCH_SIZE, DEFAULT_SHA_CHECK_BATCH_SIZE, DEFAULT_TIMEOUT, extractBrowserFromFilename, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, resolveBatchSize, resolveTimeout, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
|
|
9
|
+
|
|
10
|
+
// Operations - I/O with dependency injection
|
|
11
|
+
export { checkExistingFiles, findScreenshots, processFiles, upload, uploadFiles, waitForBuild } from './operations.js';
|