@vizzly-testing/cli 0.11.2 → 0.13.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 +87 -544
- package/dist/client/index.js +7 -5
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +20 -13
- package/dist/server/handlers/api-handler.js +53 -1
- package/dist/server/handlers/tdd-handler.js +262 -24
- package/dist/server/http-server.js +9 -9
- package/dist/services/tdd-service.js +173 -57
- package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +2 -1
- package/dist/types/reporter/src/components/comparison/comparison-group.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +9 -0
- package/dist/types/reporter/src/services/api-client.d.ts +1 -1
- package/dist/types/server/handlers/api-handler.d.ts +48 -0
- package/dist/types/server/handlers/tdd-handler.d.ts +69 -12
- package/dist/types/services/server-manager.d.ts +117 -12
- package/dist/types/services/tdd-service.d.ts +4 -2
- package/dist/types/utils/image-input-detector.d.ts +71 -0
- package/dist/utils/image-input-detector.js +150 -0
- package/docs/tdd-mode.md +6 -6
- package/package.json +3 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { compare } from '@vizzly-testing/honeydiff';
|
|
4
|
+
import crypto from 'crypto';
|
|
3
5
|
import { ApiService } from '../services/api-service.js';
|
|
4
6
|
import { createServiceLogger } from '../utils/logger-factory.js';
|
|
5
7
|
import { colors } from '../utils/colors.js';
|
|
@@ -13,13 +15,26 @@ const logger = createServiceLogger('TDD');
|
|
|
13
15
|
/**
|
|
14
16
|
* Generate a screenshot signature for baseline matching
|
|
15
17
|
* Uses same logic as screenshot-identity.js: name + viewport_width + browser
|
|
18
|
+
*
|
|
19
|
+
* Matches backend signature generation which uses:
|
|
20
|
+
* - screenshot.name
|
|
21
|
+
* - screenshot.viewport_width (top-level property)
|
|
22
|
+
* - screenshot.browser (top-level property)
|
|
16
23
|
*/
|
|
17
24
|
function generateScreenshotSignature(name, properties = {}) {
|
|
18
25
|
let parts = [name];
|
|
19
26
|
|
|
27
|
+
// Check for viewport_width as top-level property first (backend format)
|
|
28
|
+
let viewportWidth = properties.viewport_width;
|
|
29
|
+
|
|
30
|
+
// Fallback to nested viewport.width (SDK format)
|
|
31
|
+
if (!viewportWidth && properties.viewport?.width) {
|
|
32
|
+
viewportWidth = properties.viewport.width;
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
// Add viewport width if present
|
|
21
|
-
if (
|
|
22
|
-
parts.push(
|
|
36
|
+
if (viewportWidth) {
|
|
37
|
+
parts.push(viewportWidth.toString());
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
// Add browser if present
|
|
@@ -37,6 +52,14 @@ function signatureToFilename(signature) {
|
|
|
37
52
|
return signature.replace(/\|/g, '_');
|
|
38
53
|
}
|
|
39
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Generate a stable unique ID from signature for TDD comparisons
|
|
57
|
+
* This allows UI to reference specific variants without database IDs
|
|
58
|
+
*/
|
|
59
|
+
function generateComparisonId(signature) {
|
|
60
|
+
return crypto.createHash('sha256').update(signature).digest('hex').slice(0, 16);
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/**
|
|
41
64
|
* Create a new TDD service instance
|
|
42
65
|
*/
|
|
@@ -133,7 +156,7 @@ export class TddService {
|
|
|
133
156
|
}
|
|
134
157
|
} else if (comparisonId) {
|
|
135
158
|
// Use specific comparison ID - download only this comparison's baseline screenshot
|
|
136
|
-
logger.info(
|
|
159
|
+
logger.info(`Using comparison: ${comparisonId}`);
|
|
137
160
|
const comparison = await this.api.getComparison(comparisonId);
|
|
138
161
|
|
|
139
162
|
// A comparison doesn't have baselineBuild directly - we need to get it
|
|
@@ -148,6 +171,38 @@ export class TddService {
|
|
|
148
171
|
throw new Error(`Baseline screenshot for comparison ${comparisonId} has no download URL`);
|
|
149
172
|
}
|
|
150
173
|
|
|
174
|
+
// Extract properties from the current screenshot to ensure signature matching
|
|
175
|
+
// The baseline should use the same properties (viewport/browser) as the current screenshot
|
|
176
|
+
// so that generateScreenshotSignature produces the correct filename
|
|
177
|
+
// Use current screenshot properties since we're downloading baseline to compare against current
|
|
178
|
+
let screenshotProperties = {};
|
|
179
|
+
|
|
180
|
+
// Build properties from comparison API fields (added in backend update)
|
|
181
|
+
// Use current_* fields since we're matching against the current screenshot being tested
|
|
182
|
+
if (comparison.current_viewport_width || comparison.current_browser) {
|
|
183
|
+
if (comparison.current_viewport_width) {
|
|
184
|
+
screenshotProperties.viewport = {
|
|
185
|
+
width: comparison.current_viewport_width,
|
|
186
|
+
height: comparison.current_viewport_height
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (comparison.current_browser) {
|
|
190
|
+
screenshotProperties.browser = comparison.current_browser;
|
|
191
|
+
}
|
|
192
|
+
} else if (comparison.baseline_viewport_width || comparison.baseline_browser) {
|
|
193
|
+
// Fallback to baseline properties if current not available
|
|
194
|
+
if (comparison.baseline_viewport_width) {
|
|
195
|
+
screenshotProperties.viewport = {
|
|
196
|
+
width: comparison.baseline_viewport_width,
|
|
197
|
+
height: comparison.baseline_viewport_height
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (comparison.baseline_browser) {
|
|
201
|
+
screenshotProperties.browser = comparison.baseline_browser;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
logger.info(`📊 Extracted properties for signature: ${JSON.stringify(screenshotProperties)}`);
|
|
205
|
+
|
|
151
206
|
// For a specific comparison, we only download that one baseline screenshot
|
|
152
207
|
// Create a mock build structure with just this one screenshot
|
|
153
208
|
baselineBuild = {
|
|
@@ -157,8 +212,8 @@ export class TddService {
|
|
|
157
212
|
id: comparison.baseline_screenshot.id,
|
|
158
213
|
name: comparison.baseline_name || comparison.current_name,
|
|
159
214
|
original_url: baselineUrl,
|
|
160
|
-
metadata:
|
|
161
|
-
properties:
|
|
215
|
+
metadata: screenshotProperties,
|
|
216
|
+
properties: screenshotProperties
|
|
162
217
|
}]
|
|
163
218
|
};
|
|
164
219
|
} else {
|
|
@@ -193,12 +248,6 @@ export class TddService {
|
|
|
193
248
|
logger.info(`Using baseline from build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
194
249
|
logger.info(`Checking ${colors.cyan(buildDetails.screenshots.length)} baseline screenshots...`);
|
|
195
250
|
|
|
196
|
-
// Debug screenshots structure (only in debug mode)
|
|
197
|
-
logger.debug(`📊 Screenshots array structure:`, {
|
|
198
|
-
screenshotSample: buildDetails.screenshots.slice(0, 2),
|
|
199
|
-
totalCount: buildDetails.screenshots.length
|
|
200
|
-
});
|
|
201
|
-
|
|
202
251
|
// Check existing baseline metadata for efficient SHA comparison
|
|
203
252
|
const existingBaseline = await this.loadBaseline();
|
|
204
253
|
const existingShaMap = new Map();
|
|
@@ -479,6 +528,12 @@ export class TddService {
|
|
|
479
528
|
validatedProperties = {};
|
|
480
529
|
}
|
|
481
530
|
|
|
531
|
+
// Normalize properties to match backend format (viewport_width at top level)
|
|
532
|
+
// This ensures signature generation matches backend's screenshot-identity.js
|
|
533
|
+
if (validatedProperties.viewport?.width && !validatedProperties.viewport_width) {
|
|
534
|
+
validatedProperties.viewport_width = validatedProperties.viewport.width;
|
|
535
|
+
}
|
|
536
|
+
|
|
482
537
|
// Generate signature for baseline matching (name + viewport_width + browser)
|
|
483
538
|
const signature = generateScreenshotSignature(sanitizedName, validatedProperties);
|
|
484
539
|
const filename = signatureToFilename(signature);
|
|
@@ -536,12 +591,14 @@ export class TddService {
|
|
|
536
591
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
537
592
|
logger.debug(`✅ Created baseline for ${sanitizedName}`);
|
|
538
593
|
const result = {
|
|
594
|
+
id: generateComparisonId(signature),
|
|
539
595
|
name: sanitizedName,
|
|
540
596
|
status: 'new',
|
|
541
597
|
baseline: baselineImagePath,
|
|
542
598
|
current: currentImagePath,
|
|
543
599
|
diff: null,
|
|
544
|
-
properties: validatedProperties
|
|
600
|
+
properties: validatedProperties,
|
|
601
|
+
signature
|
|
545
602
|
};
|
|
546
603
|
this.comparisons.push(result);
|
|
547
604
|
return result;
|
|
@@ -549,55 +606,71 @@ export class TddService {
|
|
|
549
606
|
|
|
550
607
|
// Baseline exists - compare with it
|
|
551
608
|
try {
|
|
552
|
-
// Use odiff Node.js API to compare images
|
|
553
|
-
const {
|
|
554
|
-
compare
|
|
555
|
-
} = await import('odiff-bin');
|
|
556
|
-
|
|
557
609
|
// Log file sizes for debugging
|
|
558
610
|
const baselineSize = readFileSync(baselineImagePath).length;
|
|
559
611
|
const currentSize = readFileSync(currentImagePath).length;
|
|
560
612
|
logger.debug(`Comparing ${sanitizedName}`);
|
|
561
613
|
logger.debug(`Baseline: ${baselineImagePath} (${baselineSize} bytes)`);
|
|
562
614
|
logger.debug(`Current: ${currentImagePath} (${currentSize} bytes)`);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
615
|
+
|
|
616
|
+
// Try to compare - honeydiff will throw if dimensions don't match
|
|
617
|
+
const result = await compare(baselineImagePath, currentImagePath, {
|
|
618
|
+
colorThreshold: this.threshold,
|
|
619
|
+
// YIQ color threshold (0.0-1.0), default 0.1
|
|
620
|
+
antialiasing: true,
|
|
621
|
+
diffPath: diffImagePath,
|
|
622
|
+
overwrite: true,
|
|
623
|
+
includeClusters: true // Enable spatial clustering analysis
|
|
567
624
|
});
|
|
568
|
-
if (result.
|
|
625
|
+
if (!result.isDifferent) {
|
|
569
626
|
// Images match
|
|
570
627
|
const comparison = {
|
|
628
|
+
id: generateComparisonId(signature),
|
|
571
629
|
name: sanitizedName,
|
|
572
630
|
status: 'passed',
|
|
573
631
|
baseline: baselineImagePath,
|
|
574
632
|
current: currentImagePath,
|
|
575
633
|
diff: null,
|
|
576
634
|
properties: validatedProperties,
|
|
577
|
-
|
|
635
|
+
signature,
|
|
636
|
+
threshold: this.threshold,
|
|
637
|
+
// Include honeydiff metrics even for passing comparisons
|
|
638
|
+
totalPixels: result.totalPixels,
|
|
639
|
+
aaPixelsIgnored: result.aaPixelsIgnored,
|
|
640
|
+
aaPercentage: result.aaPercentage
|
|
578
641
|
};
|
|
579
642
|
logger.debug(`PASSED ${sanitizedName}`);
|
|
580
643
|
this.comparisons.push(comparison);
|
|
581
644
|
return comparison;
|
|
582
645
|
} else {
|
|
583
646
|
// Images differ
|
|
584
|
-
let diffInfo =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
diffInfo
|
|
647
|
+
let diffInfo = ` (${result.diffPercentage.toFixed(2)}% different, ${result.diffPixels} pixels)`;
|
|
648
|
+
|
|
649
|
+
// Add cluster info to log if available
|
|
650
|
+
if (result.diffClusters && result.diffClusters.length > 0) {
|
|
651
|
+
diffInfo += `, ${result.diffClusters.length} region${result.diffClusters.length > 1 ? 's' : ''}`;
|
|
589
652
|
}
|
|
590
653
|
const comparison = {
|
|
654
|
+
id: generateComparisonId(signature),
|
|
591
655
|
name: sanitizedName,
|
|
592
656
|
status: 'failed',
|
|
593
657
|
baseline: baselineImagePath,
|
|
594
658
|
current: currentImagePath,
|
|
595
659
|
diff: diffImagePath,
|
|
596
660
|
properties: validatedProperties,
|
|
661
|
+
signature,
|
|
597
662
|
threshold: this.threshold,
|
|
598
|
-
diffPercentage: result.
|
|
599
|
-
diffCount: result.
|
|
600
|
-
reason:
|
|
663
|
+
diffPercentage: result.diffPercentage,
|
|
664
|
+
diffCount: result.diffPixels,
|
|
665
|
+
reason: 'pixel-diff',
|
|
666
|
+
// Honeydiff metrics
|
|
667
|
+
totalPixels: result.totalPixels,
|
|
668
|
+
aaPixelsIgnored: result.aaPixelsIgnored,
|
|
669
|
+
aaPercentage: result.aaPercentage,
|
|
670
|
+
boundingBox: result.boundingBox,
|
|
671
|
+
heightDiff: result.heightDiff,
|
|
672
|
+
intensityStats: result.intensityStats,
|
|
673
|
+
diffClusters: result.diffClusters
|
|
601
674
|
};
|
|
602
675
|
logger.warn(`❌ ${colors.red('FAILED')} ${sanitizedName} - differences detected${diffInfo}`);
|
|
603
676
|
logger.info(` Diff saved to: ${diffImagePath}`);
|
|
@@ -605,15 +678,69 @@ export class TddService {
|
|
|
605
678
|
return comparison;
|
|
606
679
|
}
|
|
607
680
|
} catch (error) {
|
|
608
|
-
//
|
|
681
|
+
// Check if error is due to dimension mismatch
|
|
682
|
+
const isDimensionMismatch = error.message && error.message.includes("Image dimensions don't match");
|
|
683
|
+
if (isDimensionMismatch) {
|
|
684
|
+
// Different dimensions = different screenshot signature
|
|
685
|
+
// This shouldn't happen if signatures are working correctly, but handle gracefully
|
|
686
|
+
logger.warn(`⚠️ Dimension mismatch for ${sanitizedName} - baseline file exists but has different dimensions`);
|
|
687
|
+
logger.warn(` This indicates a signature collision. Creating new baseline with correct signature.`);
|
|
688
|
+
logger.debug(` Error: ${error.message}`);
|
|
689
|
+
|
|
690
|
+
// Create a new baseline for this screenshot (overwriting the incorrect one)
|
|
691
|
+
writeFileSync(baselineImagePath, imageBuffer);
|
|
692
|
+
|
|
693
|
+
// Update baseline metadata
|
|
694
|
+
if (!this.baselineData) {
|
|
695
|
+
this.baselineData = {
|
|
696
|
+
buildId: 'local-baseline',
|
|
697
|
+
buildName: 'Local TDD Baseline',
|
|
698
|
+
environment: 'test',
|
|
699
|
+
branch: 'local',
|
|
700
|
+
threshold: this.threshold,
|
|
701
|
+
screenshots: []
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
const screenshotEntry = {
|
|
705
|
+
name: sanitizedName,
|
|
706
|
+
properties: validatedProperties,
|
|
707
|
+
path: baselineImagePath,
|
|
708
|
+
signature: signature
|
|
709
|
+
};
|
|
710
|
+
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
711
|
+
if (existingIndex >= 0) {
|
|
712
|
+
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
713
|
+
} else {
|
|
714
|
+
this.baselineData.screenshots.push(screenshotEntry);
|
|
715
|
+
}
|
|
716
|
+
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
717
|
+
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
718
|
+
logger.info(`✅ Created new baseline for ${sanitizedName} (different dimensions)`);
|
|
719
|
+
const comparison = {
|
|
720
|
+
id: generateComparisonId(signature),
|
|
721
|
+
name: sanitizedName,
|
|
722
|
+
status: 'new',
|
|
723
|
+
baseline: baselineImagePath,
|
|
724
|
+
current: currentImagePath,
|
|
725
|
+
diff: null,
|
|
726
|
+
properties: validatedProperties,
|
|
727
|
+
signature
|
|
728
|
+
};
|
|
729
|
+
this.comparisons.push(comparison);
|
|
730
|
+
return comparison;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Handle other file errors or issues
|
|
609
734
|
logger.error(`❌ Error comparing ${sanitizedName}: ${error.message}`);
|
|
610
735
|
const comparison = {
|
|
736
|
+
id: generateComparisonId(signature),
|
|
611
737
|
name: sanitizedName,
|
|
612
738
|
status: 'error',
|
|
613
739
|
baseline: baselineImagePath,
|
|
614
740
|
current: currentImagePath,
|
|
615
741
|
diff: null,
|
|
616
742
|
properties: validatedProperties,
|
|
743
|
+
signature,
|
|
617
744
|
error: error.message
|
|
618
745
|
};
|
|
619
746
|
this.comparisons.push(comparison);
|
|
@@ -858,12 +985,14 @@ export class TddService {
|
|
|
858
985
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
859
986
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
860
987
|
const result = {
|
|
988
|
+
id: generateComparisonId(signature),
|
|
861
989
|
name,
|
|
862
990
|
status: 'new',
|
|
863
991
|
baseline: baselineImagePath,
|
|
864
992
|
current: currentImagePath,
|
|
865
993
|
diff: null,
|
|
866
|
-
properties
|
|
994
|
+
properties,
|
|
995
|
+
signature
|
|
867
996
|
};
|
|
868
997
|
this.comparisons.push(result);
|
|
869
998
|
logger.info(`✅ Baseline created for ${name}`);
|
|
@@ -913,12 +1042,14 @@ export class TddService {
|
|
|
913
1042
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
914
1043
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
915
1044
|
const result = {
|
|
1045
|
+
id: generateComparisonId(signature),
|
|
916
1046
|
name,
|
|
917
1047
|
status: 'baseline-updated',
|
|
918
1048
|
baseline: baselineImagePath,
|
|
919
1049
|
current: currentImagePath,
|
|
920
1050
|
diff: null,
|
|
921
|
-
properties
|
|
1051
|
+
properties,
|
|
1052
|
+
signature
|
|
922
1053
|
};
|
|
923
1054
|
this.comparisons.push(result);
|
|
924
1055
|
logger.info(`🐻 Baseline set for ${name}`);
|
|
@@ -927,55 +1058,45 @@ export class TddService {
|
|
|
927
1058
|
|
|
928
1059
|
/**
|
|
929
1060
|
* Accept a current screenshot as the new baseline
|
|
930
|
-
* @param {string}
|
|
1061
|
+
* @param {string} id - Comparison ID to accept (generated from signature)
|
|
931
1062
|
* @returns {Object} Result object
|
|
932
1063
|
*/
|
|
933
|
-
async acceptBaseline(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
// Find the comparison to get properties
|
|
938
|
-
let comparison = this.comparisons.find(c => c.name === sanitizedName);
|
|
1064
|
+
async acceptBaseline(id) {
|
|
1065
|
+
// Find the comparison by ID
|
|
1066
|
+
let comparison = this.comparisons.find(c => c.id === id);
|
|
939
1067
|
if (!comparison) {
|
|
940
|
-
throw new Error(`No comparison found
|
|
1068
|
+
throw new Error(`No comparison found with ID: ${id}`);
|
|
941
1069
|
}
|
|
1070
|
+
const sanitizedName = comparison.name;
|
|
942
1071
|
let properties = comparison.properties || {};
|
|
943
1072
|
let signature = generateScreenshotSignature(sanitizedName, properties);
|
|
944
1073
|
let filename = signatureToFilename(signature);
|
|
945
1074
|
|
|
946
1075
|
// Find the current screenshot file
|
|
947
1076
|
const currentImagePath = safePath(this.currentPath, `${filename}.png`);
|
|
948
|
-
logger.debug(`Looking for current screenshot at: ${currentImagePath}`);
|
|
949
1077
|
if (!existsSync(currentImagePath)) {
|
|
950
1078
|
logger.error(`Current screenshot not found at: ${currentImagePath}`);
|
|
951
|
-
throw new Error(`Current screenshot not found: ${
|
|
1079
|
+
throw new Error(`Current screenshot not found: ${sanitizedName} (looked at ${currentImagePath})`);
|
|
952
1080
|
}
|
|
953
1081
|
|
|
954
1082
|
// Read the current image
|
|
955
1083
|
const imageBuffer = readFileSync(currentImagePath);
|
|
956
|
-
logger.debug(`Read current image: ${imageBuffer.length} bytes`);
|
|
957
1084
|
|
|
958
1085
|
// Create baseline directory if it doesn't exist
|
|
959
1086
|
if (!existsSync(this.baselinePath)) {
|
|
960
1087
|
mkdirSync(this.baselinePath, {
|
|
961
1088
|
recursive: true
|
|
962
1089
|
});
|
|
963
|
-
logger.debug(`Created baseline directory: ${this.baselinePath}`);
|
|
964
1090
|
}
|
|
965
1091
|
|
|
966
1092
|
// Update the baseline
|
|
967
1093
|
const baselineImagePath = safePath(this.baselinePath, `${filename}.png`);
|
|
968
|
-
logger.debug(`Writing baseline to: ${baselineImagePath}`);
|
|
969
1094
|
|
|
970
1095
|
// Write the baseline image directly
|
|
971
1096
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
972
|
-
logger.debug(`Wrote baseline image: ${imageBuffer.length} bytes`);
|
|
973
1097
|
|
|
974
1098
|
// Verify the write
|
|
975
|
-
if (existsSync(baselineImagePath)) {
|
|
976
|
-
const writtenSize = readFileSync(baselineImagePath).length;
|
|
977
|
-
logger.debug(`Verified baseline file exists: ${writtenSize} bytes`);
|
|
978
|
-
} else {
|
|
1099
|
+
if (!existsSync(baselineImagePath)) {
|
|
979
1100
|
logger.error(`Baseline file does not exist after write!`);
|
|
980
1101
|
}
|
|
981
1102
|
|
|
@@ -989,7 +1110,6 @@ export class TddService {
|
|
|
989
1110
|
threshold: this.threshold,
|
|
990
1111
|
screenshots: []
|
|
991
1112
|
};
|
|
992
|
-
logger.debug(`Created new baseline metadata`);
|
|
993
1113
|
}
|
|
994
1114
|
|
|
995
1115
|
// Add or update screenshot in baseline metadata
|
|
@@ -1002,17 +1122,13 @@ export class TddService {
|
|
|
1002
1122
|
const existingIndex = this.baselineData.screenshots.findIndex(s => s.signature === signature);
|
|
1003
1123
|
if (existingIndex >= 0) {
|
|
1004
1124
|
this.baselineData.screenshots[existingIndex] = screenshotEntry;
|
|
1005
|
-
logger.debug(`Updated existing metadata entry at index ${existingIndex}`);
|
|
1006
1125
|
} else {
|
|
1007
1126
|
this.baselineData.screenshots.push(screenshotEntry);
|
|
1008
|
-
logger.debug(`Added new metadata entry (total: ${this.baselineData.screenshots.length})`);
|
|
1009
1127
|
}
|
|
1010
1128
|
|
|
1011
1129
|
// Save updated metadata
|
|
1012
1130
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
1013
1131
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
1014
|
-
logger.debug(`Saved metadata to: ${metadataPath}`);
|
|
1015
|
-
logger.debug(`Accepted ${sanitizedName} as new baseline`);
|
|
1016
1132
|
return {
|
|
1017
1133
|
name: sanitizedName,
|
|
1018
1134
|
status: 'accepted',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export default function ComparisonCard({ comparison, onAccept, onReject, userAction, }: {
|
|
1
|
+
export default function ComparisonCard({ comparison, onAccept, onReject, userAction, variantSelector, }: {
|
|
2
2
|
comparison: any;
|
|
3
3
|
onAccept: any;
|
|
4
4
|
onReject: any;
|
|
5
5
|
userAction: any;
|
|
6
|
+
variantSelector?: any;
|
|
6
7
|
}): any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison group component that displays multiple variants of the same screenshot
|
|
3
|
+
* Matches cloud product's grouped screenshot display
|
|
4
|
+
*/
|
|
5
|
+
export default function ComparisonGroup({ group, onAccept, onReject, loadingStates, }: {
|
|
6
|
+
group: any;
|
|
7
|
+
onAccept: any;
|
|
8
|
+
onReject: any;
|
|
9
|
+
loadingStates: any;
|
|
10
|
+
}): any;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant selector for toggling between different viewport sizes and browsers
|
|
3
|
+
* Matches cloud product's variant selection UI
|
|
4
|
+
*/
|
|
5
|
+
export default function VariantSelector({ group, selectedIndex, onSelect }: {
|
|
6
|
+
group: any;
|
|
7
|
+
selectedIndex: any;
|
|
8
|
+
onSelect: any;
|
|
9
|
+
}): any;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export function fetchReportData(): Promise<any>;
|
|
2
|
-
export function acceptBaseline(
|
|
2
|
+
export function acceptBaseline(comparisonId: any): Promise<any>;
|
|
3
3
|
export function acceptAllBaselines(): Promise<any>;
|
|
4
4
|
export function resetBaselines(): Promise<any>;
|
|
@@ -7,6 +7,9 @@ export function createApiHandler(apiService: any): {
|
|
|
7
7
|
count: number;
|
|
8
8
|
message: string;
|
|
9
9
|
error?: undefined;
|
|
10
|
+
originalPath?: undefined;
|
|
11
|
+
filePath?: undefined;
|
|
12
|
+
receivedType?: undefined;
|
|
10
13
|
name?: undefined;
|
|
11
14
|
};
|
|
12
15
|
} | {
|
|
@@ -17,6 +20,48 @@ export function createApiHandler(apiService: any): {
|
|
|
17
20
|
disabled?: undefined;
|
|
18
21
|
count?: undefined;
|
|
19
22
|
message?: undefined;
|
|
23
|
+
originalPath?: undefined;
|
|
24
|
+
filePath?: undefined;
|
|
25
|
+
receivedType?: undefined;
|
|
26
|
+
name?: undefined;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
statusCode: number;
|
|
30
|
+
body: {
|
|
31
|
+
error: string;
|
|
32
|
+
originalPath: any;
|
|
33
|
+
success?: undefined;
|
|
34
|
+
disabled?: undefined;
|
|
35
|
+
count?: undefined;
|
|
36
|
+
message?: undefined;
|
|
37
|
+
filePath?: undefined;
|
|
38
|
+
receivedType?: undefined;
|
|
39
|
+
name?: undefined;
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
statusCode: number;
|
|
43
|
+
body: {
|
|
44
|
+
error: string;
|
|
45
|
+
filePath: any;
|
|
46
|
+
success?: undefined;
|
|
47
|
+
disabled?: undefined;
|
|
48
|
+
count?: undefined;
|
|
49
|
+
message?: undefined;
|
|
50
|
+
originalPath?: undefined;
|
|
51
|
+
receivedType?: undefined;
|
|
52
|
+
name?: undefined;
|
|
53
|
+
};
|
|
54
|
+
} | {
|
|
55
|
+
statusCode: number;
|
|
56
|
+
body: {
|
|
57
|
+
error: string;
|
|
58
|
+
receivedType: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
|
|
59
|
+
success?: undefined;
|
|
60
|
+
disabled?: undefined;
|
|
61
|
+
count?: undefined;
|
|
62
|
+
message?: undefined;
|
|
63
|
+
originalPath?: undefined;
|
|
64
|
+
filePath?: undefined;
|
|
20
65
|
name?: undefined;
|
|
21
66
|
};
|
|
22
67
|
} | {
|
|
@@ -28,6 +73,9 @@ export function createApiHandler(apiService: any): {
|
|
|
28
73
|
disabled?: undefined;
|
|
29
74
|
message?: undefined;
|
|
30
75
|
error?: undefined;
|
|
76
|
+
originalPath?: undefined;
|
|
77
|
+
filePath?: undefined;
|
|
78
|
+
receivedType?: undefined;
|
|
31
79
|
};
|
|
32
80
|
}>;
|
|
33
81
|
getScreenshotCount: () => number;
|
|
@@ -6,6 +6,65 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
6
6
|
error: string;
|
|
7
7
|
details: any;
|
|
8
8
|
tddMode: boolean;
|
|
9
|
+
originalPath?: undefined;
|
|
10
|
+
filePath?: undefined;
|
|
11
|
+
receivedType?: undefined;
|
|
12
|
+
comparison?: undefined;
|
|
13
|
+
status?: undefined;
|
|
14
|
+
message?: undefined;
|
|
15
|
+
success?: undefined;
|
|
16
|
+
};
|
|
17
|
+
} | {
|
|
18
|
+
statusCode: number;
|
|
19
|
+
body: {
|
|
20
|
+
error: string;
|
|
21
|
+
originalPath: any;
|
|
22
|
+
tddMode: boolean;
|
|
23
|
+
details?: undefined;
|
|
24
|
+
filePath?: undefined;
|
|
25
|
+
receivedType?: undefined;
|
|
26
|
+
comparison?: undefined;
|
|
27
|
+
status?: undefined;
|
|
28
|
+
message?: undefined;
|
|
29
|
+
success?: undefined;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
statusCode: number;
|
|
33
|
+
body: {
|
|
34
|
+
error: string;
|
|
35
|
+
filePath: any;
|
|
36
|
+
tddMode: boolean;
|
|
37
|
+
details?: undefined;
|
|
38
|
+
originalPath?: undefined;
|
|
39
|
+
receivedType?: undefined;
|
|
40
|
+
comparison?: undefined;
|
|
41
|
+
status?: undefined;
|
|
42
|
+
message?: undefined;
|
|
43
|
+
success?: undefined;
|
|
44
|
+
};
|
|
45
|
+
} | {
|
|
46
|
+
statusCode: number;
|
|
47
|
+
body: {
|
|
48
|
+
error: string;
|
|
49
|
+
tddMode: boolean;
|
|
50
|
+
details?: undefined;
|
|
51
|
+
originalPath?: undefined;
|
|
52
|
+
filePath?: undefined;
|
|
53
|
+
receivedType?: undefined;
|
|
54
|
+
comparison?: undefined;
|
|
55
|
+
status?: undefined;
|
|
56
|
+
message?: undefined;
|
|
57
|
+
success?: undefined;
|
|
58
|
+
};
|
|
59
|
+
} | {
|
|
60
|
+
statusCode: number;
|
|
61
|
+
body: {
|
|
62
|
+
error: string;
|
|
63
|
+
receivedType: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
|
|
64
|
+
tddMode: boolean;
|
|
65
|
+
details?: undefined;
|
|
66
|
+
originalPath?: undefined;
|
|
67
|
+
filePath?: undefined;
|
|
9
68
|
comparison?: undefined;
|
|
10
69
|
status?: undefined;
|
|
11
70
|
message?: undefined;
|
|
@@ -26,6 +85,9 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
26
85
|
threshold: any;
|
|
27
86
|
};
|
|
28
87
|
tddMode: boolean;
|
|
88
|
+
originalPath?: undefined;
|
|
89
|
+
filePath?: undefined;
|
|
90
|
+
receivedType?: undefined;
|
|
29
91
|
status?: undefined;
|
|
30
92
|
message?: undefined;
|
|
31
93
|
success?: undefined;
|
|
@@ -47,17 +109,9 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
47
109
|
tddMode: boolean;
|
|
48
110
|
error?: undefined;
|
|
49
111
|
details?: undefined;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
statusCode: number;
|
|
54
|
-
body: {
|
|
55
|
-
error: string;
|
|
56
|
-
tddMode: boolean;
|
|
57
|
-
details?: undefined;
|
|
58
|
-
comparison?: undefined;
|
|
59
|
-
status?: undefined;
|
|
60
|
-
message?: undefined;
|
|
112
|
+
originalPath?: undefined;
|
|
113
|
+
filePath?: undefined;
|
|
114
|
+
receivedType?: undefined;
|
|
61
115
|
success?: undefined;
|
|
62
116
|
};
|
|
63
117
|
} | {
|
|
@@ -76,6 +130,9 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
76
130
|
tddMode: boolean;
|
|
77
131
|
error?: undefined;
|
|
78
132
|
details?: undefined;
|
|
133
|
+
originalPath?: undefined;
|
|
134
|
+
filePath?: undefined;
|
|
135
|
+
receivedType?: undefined;
|
|
79
136
|
status?: undefined;
|
|
80
137
|
message?: undefined;
|
|
81
138
|
};
|
|
@@ -89,7 +146,7 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
89
146
|
comparisons: any[];
|
|
90
147
|
baseline: any;
|
|
91
148
|
}>;
|
|
92
|
-
acceptBaseline: (
|
|
149
|
+
acceptBaseline: (comparisonId: any) => Promise<any>;
|
|
93
150
|
acceptAllBaselines: () => Promise<{
|
|
94
151
|
count: number;
|
|
95
152
|
}>;
|