@vizzly-testing/cli 0.7.2 → 0.9.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/README.md +27 -14
- package/dist/cli.js +25 -1
- package/dist/client/index.js +77 -11
- package/dist/commands/init.js +23 -17
- package/dist/commands/tdd-daemon.js +312 -0
- package/dist/commands/tdd.js +45 -14
- package/dist/commands/upload.js +3 -1
- package/dist/reporter/reporter-bundle.css +1 -0
- package/dist/reporter/reporter-bundle.iife.js +57 -0
- package/dist/sdk/index.js +1 -1
- package/dist/server/handlers/api-handler.js +98 -30
- package/dist/server/handlers/tdd-handler.js +264 -77
- package/dist/server/http-server.js +358 -15
- package/dist/services/api-service.js +6 -1
- package/dist/services/html-report-generator.js +77 -0
- package/dist/services/report-generator/report.css +56 -0
- package/dist/services/screenshot-server.js +6 -3
- package/dist/services/server-manager.js +2 -9
- package/dist/services/tdd-service.js +188 -25
- package/dist/services/test-runner.js +43 -1
- package/dist/types/commands/tdd-daemon.d.ts +18 -0
- package/dist/types/container/index.d.ts +1 -3
- package/dist/types/reporter/src/components/app-router.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/comparison-actions.d.ts +5 -0
- package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-list.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/view-mode-selector.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-filters.d.ts +16 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +5 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +4 -0
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +8 -0
- package/dist/types/reporter/src/components/ui/smart-image.d.ts +7 -0
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +5 -0
- package/dist/types/reporter/src/components/ui/toast.d.ts +4 -0
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +6 -0
- package/dist/types/reporter/src/components/views/stats-view.d.ts +6 -0
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +5 -0
- package/dist/types/reporter/src/hooks/use-comparison-filters.d.ts +20 -0
- package/dist/types/reporter/src/hooks/use-image-loader.d.ts +1 -0
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +7 -0
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +9 -0
- package/dist/types/reporter/src/main.d.ts +1 -0
- package/dist/types/reporter/src/services/api-client.d.ts +4 -0
- package/dist/types/reporter/src/utils/comparison-helpers.d.ts +16 -0
- package/dist/types/reporter/src/utils/constants.d.ts +37 -0
- package/dist/types/reporter/vite.config.d.ts +2 -0
- package/dist/types/reporter/vite.dev.config.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +2 -3
- package/dist/types/server/handlers/api-handler.d.ts +5 -14
- package/dist/types/server/handlers/tdd-handler.d.ts +18 -17
- package/dist/types/server/http-server.d.ts +2 -1
- package/dist/types/services/base-service.d.ts +1 -2
- package/dist/types/services/html-report-generator.d.ts +3 -3
- package/dist/types/services/screenshot-server.d.ts +1 -1
- package/dist/types/services/server-manager.d.ts +25 -35
- package/dist/types/services/tdd-service.d.ts +7 -1
- package/dist/types/services/test-runner.d.ts +6 -1
- package/dist/types/utils/build-history.d.ts +16 -0
- package/dist/types/utils/config-loader.d.ts +1 -1
- package/dist/types/utils/console-ui.d.ts +1 -1
- package/dist/types/utils/git.d.ts +4 -4
- package/dist/types/utils/security.d.ts +2 -1
- package/dist/utils/build-history.js +103 -0
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/console-ui.js +2 -1
- package/dist/utils/environment-config.js +1 -1
- package/dist/utils/security.js +14 -5
- package/docs/api-reference.md +2 -4
- package/docs/doctor-command.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/tdd-mode.md +176 -112
- package/package.json +17 -4
|
@@ -10,6 +10,33 @@ import { HtmlReportGenerator } from './html-report-generator.js';
|
|
|
10
10
|
import { sanitizeScreenshotName, validatePathSecurity, safePath, validateScreenshotProperties } from '../utils/security.js';
|
|
11
11
|
const logger = createServiceLogger('TDD');
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Generate a screenshot signature for baseline matching
|
|
15
|
+
* Uses same logic as screenshot-identity.js: name + viewport_width + browser
|
|
16
|
+
*/
|
|
17
|
+
function generateScreenshotSignature(name, properties = {}) {
|
|
18
|
+
let parts = [name];
|
|
19
|
+
|
|
20
|
+
// Add viewport width if present
|
|
21
|
+
if (properties.viewport?.width) {
|
|
22
|
+
parts.push(properties.viewport.width.toString());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Add browser if present
|
|
26
|
+
if (properties.browser) {
|
|
27
|
+
parts.push(properties.browser);
|
|
28
|
+
}
|
|
29
|
+
return parts.join('|');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a safe filename from signature
|
|
34
|
+
*/
|
|
35
|
+
function signatureToFilename(signature) {
|
|
36
|
+
// Replace pipe separators with underscores for filesystem safety
|
|
37
|
+
return signature.replace(/\|/g, '_');
|
|
38
|
+
}
|
|
39
|
+
|
|
13
40
|
/**
|
|
14
41
|
* Create a new TDD service instance
|
|
15
42
|
*/
|
|
@@ -153,8 +180,8 @@ export class TddService {
|
|
|
153
180
|
const existingShaMap = new Map();
|
|
154
181
|
if (existingBaseline) {
|
|
155
182
|
existingBaseline.screenshots.forEach(s => {
|
|
156
|
-
if (s.sha256) {
|
|
157
|
-
existingShaMap.set(s.
|
|
183
|
+
if (s.sha256 && s.signature) {
|
|
184
|
+
existingShaMap.set(s.signature, s.sha256);
|
|
158
185
|
}
|
|
159
186
|
});
|
|
160
187
|
}
|
|
@@ -178,11 +205,16 @@ export class TddService {
|
|
|
178
205
|
errorCount++;
|
|
179
206
|
continue;
|
|
180
207
|
}
|
|
181
|
-
|
|
208
|
+
|
|
209
|
+
// Generate signature for baseline matching (same as compareScreenshot)
|
|
210
|
+
let properties = validateScreenshotProperties(screenshot.metadata || screenshot.properties || {});
|
|
211
|
+
let signature = generateScreenshotSignature(sanitizedName, properties);
|
|
212
|
+
let filename = signatureToFilename(signature);
|
|
213
|
+
const imagePath = safePath(this.baselinePath, `${filename}.png`);
|
|
182
214
|
|
|
183
215
|
// Check if we already have this file with the same SHA (using metadata)
|
|
184
216
|
if (existsSync(imagePath) && screenshot.sha256) {
|
|
185
|
-
const storedSha = existingShaMap.get(
|
|
217
|
+
const storedSha = existingShaMap.get(signature);
|
|
186
218
|
if (storedSha === screenshot.sha256) {
|
|
187
219
|
logger.debug(`⚡ Skipping ${sanitizedName} - SHA match from metadata`);
|
|
188
220
|
downloadedCount++; // Count as "downloaded" since we have it
|
|
@@ -204,7 +236,10 @@ export class TddService {
|
|
|
204
236
|
screenshot,
|
|
205
237
|
sanitizedName,
|
|
206
238
|
imagePath,
|
|
207
|
-
downloadUrl
|
|
239
|
+
downloadUrl,
|
|
240
|
+
signature,
|
|
241
|
+
filename,
|
|
242
|
+
properties
|
|
208
243
|
});
|
|
209
244
|
}
|
|
210
245
|
|
|
@@ -293,14 +328,18 @@ export class TddService {
|
|
|
293
328
|
logger.warn(`Screenshot name sanitization failed for '${s.name}': ${error.message}`);
|
|
294
329
|
return null; // Skip invalid screenshots
|
|
295
330
|
}
|
|
331
|
+
let properties = validateScreenshotProperties(s.metadata || s.properties || {});
|
|
332
|
+
let signature = generateScreenshotSignature(sanitizedName, properties);
|
|
333
|
+
let filename = signatureToFilename(signature);
|
|
296
334
|
return {
|
|
297
335
|
name: sanitizedName,
|
|
298
336
|
originalName: s.name,
|
|
299
337
|
sha256: s.sha256,
|
|
300
338
|
// Store remote SHA for quick comparison
|
|
301
339
|
id: s.id,
|
|
302
|
-
properties:
|
|
303
|
-
path: safePath(this.baselinePath, `${
|
|
340
|
+
properties: properties,
|
|
341
|
+
path: safePath(this.baselinePath, `${filename}.png`),
|
|
342
|
+
signature: signature,
|
|
304
343
|
originalUrl: s.original_url,
|
|
305
344
|
fileSize: s.file_size_bytes,
|
|
306
345
|
dimensions: {
|
|
@@ -395,9 +434,13 @@ export class TddService {
|
|
|
395
434
|
logger.warn(`Property validation failed for '${sanitizedName}': ${error.message}`);
|
|
396
435
|
validatedProperties = {};
|
|
397
436
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const
|
|
437
|
+
|
|
438
|
+
// Generate signature for baseline matching (name + viewport_width + browser)
|
|
439
|
+
const signature = generateScreenshotSignature(sanitizedName, validatedProperties);
|
|
440
|
+
const filename = signatureToFilename(signature);
|
|
441
|
+
const currentImagePath = safePath(this.currentPath, `${filename}.png`);
|
|
442
|
+
const baselineImagePath = safePath(this.baselinePath, `${filename}.png`);
|
|
443
|
+
const diffImagePath = safePath(this.diffPath, `${filename}.png`);
|
|
401
444
|
|
|
402
445
|
// Save current screenshot
|
|
403
446
|
writeFileSync(currentImagePath, imageBuffer);
|
|
@@ -408,11 +451,15 @@ export class TddService {
|
|
|
408
451
|
}
|
|
409
452
|
|
|
410
453
|
// Check if baseline exists
|
|
411
|
-
|
|
412
|
-
|
|
454
|
+
const baselineExists = existsSync(baselineImagePath);
|
|
455
|
+
if (!baselineExists) {
|
|
456
|
+
logger.debug(`No baseline found for ${sanitizedName} - creating baseline`);
|
|
457
|
+
logger.debug(`Path: ${baselineImagePath}`);
|
|
458
|
+
logger.debug(`Size: ${imageBuffer.length} bytes`);
|
|
413
459
|
|
|
414
460
|
// Copy current screenshot to baseline directory for future comparisons
|
|
415
461
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
462
|
+
logger.debug(`Created baseline: ${imageBuffer.length} bytes`);
|
|
416
463
|
|
|
417
464
|
// Update or create baseline metadata
|
|
418
465
|
if (!this.baselineData) {
|
|
@@ -430,9 +477,10 @@ export class TddService {
|
|
|
430
477
|
const screenshotEntry = {
|
|
431
478
|
name: sanitizedName,
|
|
432
479
|
properties: validatedProperties,
|
|
433
|
-
path: baselineImagePath
|
|
480
|
+
path: baselineImagePath,
|
|
481
|
+
signature: signature
|
|
434
482
|
};
|
|
435
|
-
const existingIndex = this.baselineData.screenshots.findIndex(s => s.
|
|
483
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
436
484
|
if (existingIndex >= 0) {
|
|
437
485
|
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
438
486
|
} else {
|
|
@@ -442,7 +490,7 @@ export class TddService {
|
|
|
442
490
|
// Save updated metadata
|
|
443
491
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
444
492
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
445
|
-
logger.
|
|
493
|
+
logger.debug(`✅ Created baseline for ${sanitizedName}`);
|
|
446
494
|
const result = {
|
|
447
495
|
name: sanitizedName,
|
|
448
496
|
status: 'new',
|
|
@@ -454,12 +502,20 @@ export class TddService {
|
|
|
454
502
|
this.comparisons.push(result);
|
|
455
503
|
return result;
|
|
456
504
|
}
|
|
505
|
+
|
|
506
|
+
// Baseline exists - compare with it
|
|
457
507
|
try {
|
|
458
508
|
// Use odiff Node.js API to compare images
|
|
459
509
|
const {
|
|
460
510
|
compare
|
|
461
511
|
} = await import('odiff-bin');
|
|
462
|
-
|
|
512
|
+
|
|
513
|
+
// Log file sizes for debugging
|
|
514
|
+
const baselineSize = readFileSync(baselineImagePath).length;
|
|
515
|
+
const currentSize = readFileSync(currentImagePath).length;
|
|
516
|
+
logger.debug(`Comparing ${sanitizedName}`);
|
|
517
|
+
logger.debug(`Baseline: ${baselineImagePath} (${baselineSize} bytes)`);
|
|
518
|
+
logger.debug(`Current: ${currentImagePath} (${currentSize} bytes)`);
|
|
463
519
|
const result = await compare(baselineImagePath, currentImagePath, diffImagePath, {
|
|
464
520
|
threshold: this.threshold,
|
|
465
521
|
outputDiffMask: true
|
|
@@ -475,7 +531,7 @@ export class TddService {
|
|
|
475
531
|
properties: validatedProperties,
|
|
476
532
|
threshold: this.threshold
|
|
477
533
|
};
|
|
478
|
-
logger.
|
|
534
|
+
logger.debug(`PASSED ${sanitizedName}`);
|
|
479
535
|
this.comparisons.push(comparison);
|
|
480
536
|
return comparison;
|
|
481
537
|
} else {
|
|
@@ -672,7 +728,10 @@ export class TddService {
|
|
|
672
728
|
logger.warn(`Skipping baseline update for invalid name '${name}': ${error.message}`);
|
|
673
729
|
continue;
|
|
674
730
|
}
|
|
675
|
-
|
|
731
|
+
let validatedProperties = validateScreenshotProperties(comparison.properties || {});
|
|
732
|
+
let signature = generateScreenshotSignature(sanitizedName, validatedProperties);
|
|
733
|
+
let filename = signatureToFilename(signature);
|
|
734
|
+
const baselineImagePath = safePath(this.baselinePath, `${filename}.png`);
|
|
676
735
|
try {
|
|
677
736
|
// Copy current screenshot to baseline
|
|
678
737
|
const currentBuffer = readFileSync(current);
|
|
@@ -681,10 +740,11 @@ export class TddService {
|
|
|
681
740
|
// Update baseline metadata
|
|
682
741
|
const screenshotEntry = {
|
|
683
742
|
name: sanitizedName,
|
|
684
|
-
properties:
|
|
685
|
-
path: baselineImagePath
|
|
743
|
+
properties: validatedProperties,
|
|
744
|
+
path: baselineImagePath,
|
|
745
|
+
signature: signature
|
|
686
746
|
};
|
|
687
|
-
const existingIndex = this.baselineData.screenshots.findIndex(s => s.
|
|
747
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
688
748
|
if (existingIndex >= 0) {
|
|
689
749
|
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
690
750
|
} else {
|
|
@@ -732,13 +792,17 @@ export class TddService {
|
|
|
732
792
|
};
|
|
733
793
|
}
|
|
734
794
|
|
|
795
|
+
// Generate signature for this screenshot
|
|
796
|
+
let signature = generateScreenshotSignature(name, properties || {});
|
|
797
|
+
|
|
735
798
|
// Add screenshot to baseline metadata
|
|
736
799
|
const screenshotEntry = {
|
|
737
800
|
name,
|
|
738
801
|
properties: properties || {},
|
|
739
|
-
path: baselineImagePath
|
|
802
|
+
path: baselineImagePath,
|
|
803
|
+
signature: signature
|
|
740
804
|
};
|
|
741
|
-
const existingIndex = this.baselineData.screenshots.findIndex(s => s.
|
|
805
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
742
806
|
if (existingIndex >= 0) {
|
|
743
807
|
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
744
808
|
} else {
|
|
@@ -783,13 +847,17 @@ export class TddService {
|
|
|
783
847
|
};
|
|
784
848
|
}
|
|
785
849
|
|
|
850
|
+
// Generate signature for this screenshot
|
|
851
|
+
let signature = generateScreenshotSignature(name, properties || {});
|
|
852
|
+
|
|
786
853
|
// Add screenshot to baseline metadata
|
|
787
854
|
const screenshotEntry = {
|
|
788
855
|
name,
|
|
789
856
|
properties: properties || {},
|
|
790
|
-
path: baselineImagePath
|
|
857
|
+
path: baselineImagePath,
|
|
858
|
+
signature: signature
|
|
791
859
|
};
|
|
792
|
-
const existingIndex = this.baselineData.screenshots.findIndex(s => s.
|
|
860
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
793
861
|
if (existingIndex >= 0) {
|
|
794
862
|
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
795
863
|
} else {
|
|
@@ -811,4 +879,99 @@ export class TddService {
|
|
|
811
879
|
logger.info(`🐻 Baseline set for ${name}`);
|
|
812
880
|
return result;
|
|
813
881
|
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Accept a current screenshot as the new baseline
|
|
885
|
+
* @param {string} name - Screenshot name to accept
|
|
886
|
+
* @returns {Object} Result object
|
|
887
|
+
*/
|
|
888
|
+
async acceptBaseline(name) {
|
|
889
|
+
const sanitizedName = sanitizeScreenshotName(name);
|
|
890
|
+
logger.debug(`Starting accept baseline for: ${sanitizedName}`);
|
|
891
|
+
|
|
892
|
+
// Find the comparison to get properties
|
|
893
|
+
let comparison = this.comparisons.find(c => c.name === sanitizedName);
|
|
894
|
+
if (!comparison) {
|
|
895
|
+
throw new Error(`No comparison found for screenshot: ${name}`);
|
|
896
|
+
}
|
|
897
|
+
let properties = comparison.properties || {};
|
|
898
|
+
let signature = generateScreenshotSignature(sanitizedName, properties);
|
|
899
|
+
let filename = signatureToFilename(signature);
|
|
900
|
+
|
|
901
|
+
// Find the current screenshot file
|
|
902
|
+
const currentImagePath = safePath(this.currentPath, `${filename}.png`);
|
|
903
|
+
logger.debug(`Looking for current screenshot at: ${currentImagePath}`);
|
|
904
|
+
if (!existsSync(currentImagePath)) {
|
|
905
|
+
logger.error(`Current screenshot not found at: ${currentImagePath}`);
|
|
906
|
+
throw new Error(`Current screenshot not found: ${name} (looked at ${currentImagePath})`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Read the current image
|
|
910
|
+
const imageBuffer = readFileSync(currentImagePath);
|
|
911
|
+
logger.debug(`Read current image: ${imageBuffer.length} bytes`);
|
|
912
|
+
|
|
913
|
+
// Create baseline directory if it doesn't exist
|
|
914
|
+
if (!existsSync(this.baselinePath)) {
|
|
915
|
+
mkdirSync(this.baselinePath, {
|
|
916
|
+
recursive: true
|
|
917
|
+
});
|
|
918
|
+
logger.debug(`Created baseline directory: ${this.baselinePath}`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Update the baseline
|
|
922
|
+
const baselineImagePath = safePath(this.baselinePath, `${filename}.png`);
|
|
923
|
+
logger.debug(`Writing baseline to: ${baselineImagePath}`);
|
|
924
|
+
|
|
925
|
+
// Write the baseline image directly
|
|
926
|
+
writeFileSync(baselineImagePath, imageBuffer);
|
|
927
|
+
logger.debug(`Wrote baseline image: ${imageBuffer.length} bytes`);
|
|
928
|
+
|
|
929
|
+
// Verify the write
|
|
930
|
+
if (existsSync(baselineImagePath)) {
|
|
931
|
+
const writtenSize = readFileSync(baselineImagePath).length;
|
|
932
|
+
logger.debug(`Verified baseline file exists: ${writtenSize} bytes`);
|
|
933
|
+
} else {
|
|
934
|
+
logger.error(`Baseline file does not exist after write!`);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Update baseline metadata
|
|
938
|
+
if (!this.baselineData) {
|
|
939
|
+
this.baselineData = {
|
|
940
|
+
buildId: 'local-baseline',
|
|
941
|
+
buildName: 'Local TDD Baseline',
|
|
942
|
+
environment: 'test',
|
|
943
|
+
branch: 'local',
|
|
944
|
+
threshold: this.threshold,
|
|
945
|
+
screenshots: []
|
|
946
|
+
};
|
|
947
|
+
logger.debug(`Created new baseline metadata`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Add or update screenshot in baseline metadata
|
|
951
|
+
const screenshotEntry = {
|
|
952
|
+
name: sanitizedName,
|
|
953
|
+
properties: properties,
|
|
954
|
+
path: baselineImagePath,
|
|
955
|
+
signature: signature
|
|
956
|
+
};
|
|
957
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
958
|
+
if (existingIndex >= 0) {
|
|
959
|
+
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
960
|
+
logger.debug(`Updated existing metadata entry at index ${existingIndex}`);
|
|
961
|
+
} else {
|
|
962
|
+
this.baselineData.screenshots.push(screenshotEntry);
|
|
963
|
+
logger.debug(`Added new metadata entry (total: ${this.baselineData.screenshots.length})`);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Save updated metadata
|
|
967
|
+
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
968
|
+
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
969
|
+
logger.debug(`Saved metadata to: ${metadataPath}`);
|
|
970
|
+
logger.debug(`Accepted ${sanitizedName} as new baseline`);
|
|
971
|
+
return {
|
|
972
|
+
name: sanitizedName,
|
|
973
|
+
status: 'accepted',
|
|
974
|
+
message: 'Screenshot accepted as new baseline'
|
|
975
|
+
};
|
|
976
|
+
}
|
|
814
977
|
}
|
|
@@ -14,6 +14,32 @@ export class TestRunner extends BaseService {
|
|
|
14
14
|
this.tddService = tddService;
|
|
15
15
|
this.testProcess = null;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize server for daemon mode (no test execution)
|
|
20
|
+
* @param {Object} options - Options for server initialization
|
|
21
|
+
*/
|
|
22
|
+
async initialize(options) {
|
|
23
|
+
const {
|
|
24
|
+
tdd,
|
|
25
|
+
daemon
|
|
26
|
+
} = options;
|
|
27
|
+
if (!tdd || !daemon) {
|
|
28
|
+
throw new VizzlyError('Initialize method is only for TDD daemon mode', 'INVALID_MODE');
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// Start server manager for daemon mode
|
|
32
|
+
await this.serverManager.start(null, tdd, options.setBaseline);
|
|
33
|
+
this.emit('server-ready', {
|
|
34
|
+
port: options.port,
|
|
35
|
+
mode: 'daemon',
|
|
36
|
+
tdd: true
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.logger.error('Failed to initialize TDD daemon server:', error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
17
43
|
async run(options) {
|
|
18
44
|
const {
|
|
19
45
|
testCommand,
|
|
@@ -100,6 +126,11 @@ export class TestRunner extends BaseService {
|
|
|
100
126
|
this.logger.error('Failed to finalize build:', finalizeError);
|
|
101
127
|
}
|
|
102
128
|
}
|
|
129
|
+
|
|
130
|
+
// In API mode, get actual screenshot count from handler after flush
|
|
131
|
+
if (!tdd && this.serverManager.server?.getScreenshotCount) {
|
|
132
|
+
screenshotCount = this.serverManager.server.getScreenshotCount(buildId) || 0;
|
|
133
|
+
}
|
|
103
134
|
try {
|
|
104
135
|
await this.serverManager.stop();
|
|
105
136
|
} catch (stopError) {
|
|
@@ -182,10 +213,21 @@ export class TestRunner extends BaseService {
|
|
|
182
213
|
this.logger.debug(`TDD build ${buildId} finalization skipped (local-only mode)`);
|
|
183
214
|
}
|
|
184
215
|
} else {
|
|
185
|
-
// API mode:
|
|
216
|
+
// API mode: flush uploads first, then finalize build
|
|
217
|
+
if (this.serverManager.server?.finishBuild) {
|
|
218
|
+
this.logger.debug(`Flushing uploads for build ${buildId}...`);
|
|
219
|
+
await this.serverManager.server.finishBuild(buildId);
|
|
220
|
+
this.logger.debug(`Upload flush complete for build ${buildId}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Then update build status via API
|
|
224
|
+
this.logger.debug(`Finalizing build ${buildId} via API...`);
|
|
186
225
|
const apiService = await this.createApiService();
|
|
187
226
|
if (apiService) {
|
|
188
227
|
await apiService.finalizeBuild(buildId, success, executionTime);
|
|
228
|
+
this.logger.debug(`Build ${buildId} finalized successfully`);
|
|
229
|
+
} else {
|
|
230
|
+
this.logger.warn(`No API service available to finalize build ${buildId}`);
|
|
189
231
|
}
|
|
190
232
|
}
|
|
191
233
|
} catch (error) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start TDD server in daemon mode
|
|
3
|
+
* @param {Object} options - Command options
|
|
4
|
+
* @param {Object} globalOptions - Global CLI options
|
|
5
|
+
*/
|
|
6
|
+
export function tddStartCommand(options?: any, globalOptions?: any): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Stop TDD daemon server
|
|
9
|
+
* @param {Object} options - Command options
|
|
10
|
+
* @param {Object} globalOptions - Global CLI options
|
|
11
|
+
*/
|
|
12
|
+
export function tddStopCommand(options?: any, globalOptions?: any): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Check TDD daemon server status
|
|
15
|
+
* @param {Object} options - Command options
|
|
16
|
+
* @param {Object} globalOptions - Global CLI options
|
|
17
|
+
*/
|
|
18
|
+
export function tddStatusCommand(options: any, globalOptions?: any): Promise<void>;
|
|
@@ -13,8 +13,7 @@ export function createServiceContainer(config: any, command?: string): ServiceCo
|
|
|
13
13
|
/**
|
|
14
14
|
* Service container for dependency injection and lifecycle management
|
|
15
15
|
*/
|
|
16
|
-
export class ServiceContainer
|
|
17
|
-
constructor();
|
|
16
|
+
export class ServiceContainer {
|
|
18
17
|
services: Map<any, any>;
|
|
19
18
|
instances: Map<any, any>;
|
|
20
19
|
starting: Map<any, any>;
|
|
@@ -58,4 +57,3 @@ export type ServiceDefinition = {
|
|
|
58
57
|
*/
|
|
59
58
|
dependencies?: string[];
|
|
60
59
|
};
|
|
61
|
-
import { EventEmitter } from 'events';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function DashboardFilters({ filter, setFilter, sortBy, setSortBy, searchQuery, setSearchQuery, selectedBrowser, setSelectedBrowser, selectedViewport, setSelectedViewport, availableFilters, counts, onRefresh, loading, }: {
|
|
2
|
+
filter: any;
|
|
3
|
+
setFilter: any;
|
|
4
|
+
sortBy: any;
|
|
5
|
+
setSortBy: any;
|
|
6
|
+
searchQuery: any;
|
|
7
|
+
setSearchQuery: any;
|
|
8
|
+
selectedBrowser: any;
|
|
9
|
+
setSelectedBrowser: any;
|
|
10
|
+
selectedViewport: any;
|
|
11
|
+
setSelectedViewport: any;
|
|
12
|
+
availableFilters: any;
|
|
13
|
+
counts: any;
|
|
14
|
+
onRefresh: any;
|
|
15
|
+
loading: any;
|
|
16
|
+
}): any;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default function useComparisonFilters(comparisons?: any[]): {
|
|
2
|
+
filteredComparisons: any;
|
|
3
|
+
filter: any;
|
|
4
|
+
setFilter: any;
|
|
5
|
+
sortBy: any;
|
|
6
|
+
setSortBy: any;
|
|
7
|
+
searchQuery: any;
|
|
8
|
+
setSearchQuery: any;
|
|
9
|
+
selectedBrowser: any;
|
|
10
|
+
setSelectedBrowser: any;
|
|
11
|
+
selectedViewport: any;
|
|
12
|
+
setSelectedViewport: any;
|
|
13
|
+
availableFilters: any;
|
|
14
|
+
counts: {
|
|
15
|
+
all: number;
|
|
16
|
+
failed: number;
|
|
17
|
+
passed: number;
|
|
18
|
+
new: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useImageLoader(src: any): any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getStatusInfo(comparison: any): {
|
|
2
|
+
type: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
icon: React.ForwardRefExoticComponent<any>;
|
|
6
|
+
colorClass: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: string;
|
|
9
|
+
label: string;
|
|
10
|
+
icon: React.ForwardRefExoticComponent<any>;
|
|
11
|
+
colorClass: string;
|
|
12
|
+
description?: undefined;
|
|
13
|
+
};
|
|
14
|
+
export function calculatePassRate(summary: any): number;
|
|
15
|
+
export function sortComparisons(comparisons: any, sortBy: any): any[];
|
|
16
|
+
export function filterComparisons(comparisons: any, filter: any): any;
|