@vizzly-testing/cli 0.35.3-beta.2 → 0.35.3-beta.4
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 +3 -4
- package/dist/api/core.js +12 -5
- package/dist/api/endpoints.js +5 -50
- package/dist/api/index.js +1 -1
- package/dist/cli.js +4 -4
- package/dist/client/index.js +2 -1
- package/dist/commands/api.js +3 -3
- package/dist/commands/builds.js +12 -15
- package/dist/commands/comparisons.js +7 -9
- package/dist/commands/context.js +3 -24
- package/dist/commands/review.js +122 -35
- package/dist/commands/run.js +1 -3
- package/dist/commands/status.js +35 -79
- package/dist/commands/upload.js +1 -1
- package/dist/context/local-workspace-provider.js +36 -126
- package/dist/reporter/reporter-bundle.iife.js +49 -81
- package/dist/screenshot-server/core.js +5 -1
- package/dist/sdk/index.js +2 -1
- package/dist/server/handlers/api-handler.js +18 -2
- package/dist/server/handlers/tdd-handler.js +12 -11
- package/dist/server/routers/dashboard.js +5 -1
- package/dist/server/routers/screenshot.js +25 -2
- package/dist/tdd/runtime-context.js +0 -2
- package/dist/tdd/services/comparison-service.js +4 -68
- package/dist/tdd/tdd-service.js +10 -208
- package/dist/types/index.d.ts +6 -2
- package/dist/uploader/core.js +7 -8
- package/dist/utils/screenshot-options.js +61 -22
- package/dist/utils/visual-context-normalizers.js +86 -139
- package/package.json +1 -1
- package/skills/vizzly/SKILL.md +4 -4
- package/skills/vizzly/references/dynamic-content.md +2 -2
- package/dist/tdd/core/hotspot-coverage.js +0 -112
- package/dist/tdd/core/region-coverage.js +0 -203
- package/dist/tdd/metadata/hotspot-metadata.js +0 -93
- package/dist/tdd/metadata/region-metadata.js +0 -56
package/dist/tdd/tdd-service.js
CHANGED
|
@@ -9,19 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync as defaultExistsSync, mkdirSync as defaultMkdirSync, readFileSync as defaultReadFileSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
12
|
-
import { createApiClient as defaultCreateApiClient,
|
|
12
|
+
import { createApiClient as defaultCreateApiClient, getBuilds as defaultGetBuilds, getComparison as defaultGetComparison, getTddBaselines as defaultGetTddBaselines } from '../api/index.js';
|
|
13
13
|
import { NetworkError } from '../errors/vizzly-error.js';
|
|
14
14
|
import { colors as defaultColors } from '../utils/colors.js';
|
|
15
15
|
import { fetchWithTimeout as defaultFetchWithTimeout } from '../utils/fetch-utils.js';
|
|
16
16
|
import { getDefaultBranch as defaultGetDefaultBranch } from '../utils/git.js';
|
|
17
17
|
import * as defaultOutput from '../utils/output.js';
|
|
18
18
|
import { safePath as defaultSafePath, sanitizeScreenshotName as defaultSanitizeScreenshotName, validatePathSecurity as defaultValidatePathSecurity, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../utils/security.js';
|
|
19
|
-
import { calculateHotspotCoverage as defaultCalculateHotspotCoverage } from './core/hotspot-coverage.js';
|
|
20
19
|
// Import from extracted modules
|
|
21
20
|
import { generateBaselineFilename as defaultGenerateBaselineFilename, generateComparisonId as defaultGenerateComparisonId, generateScreenshotSignature as defaultGenerateScreenshotSignature } from './core/signature.js';
|
|
22
21
|
import { createEmptyBaselineMetadata as defaultCreateEmptyBaselineMetadata, loadBaselineMetadata as defaultLoadBaselineMetadata, saveBaselineMetadata as defaultSaveBaselineMetadata, upsertScreenshotInMetadata as defaultUpsertScreenshotInMetadata } from './metadata/baseline-metadata.js';
|
|
23
|
-
import { loadHotspotMetadata as defaultLoadHotspotMetadata, saveHotspotMetadata as defaultSaveHotspotMetadata } from './metadata/hotspot-metadata.js';
|
|
24
|
-
import { loadRegionMetadata as defaultLoadRegionMetadata, saveRegionMetadata as defaultSaveRegionMetadata } from './metadata/region-metadata.js';
|
|
25
22
|
import { buildTddDependencyOps, resolveTddPaths, resolveTddWorkingDirectory } from './runtime-context.js';
|
|
26
23
|
import { baselineExists as defaultBaselineExists, clearBaselineData as defaultClearBaselineData, clearRunData as defaultClearRunData, getBaselinePath as defaultGetBaselinePath, getCurrentPath as defaultGetCurrentPath, getDiffPath as defaultGetDiffPath, initializeDirectories as defaultInitializeDirectories, saveBaseline as defaultSaveBaseline, saveCurrent as defaultSaveCurrent } from './services/baseline-manager.js';
|
|
27
24
|
import { buildErrorComparison as defaultBuildErrorComparison, buildFailedComparison as defaultBuildFailedComparison, buildNewComparison as defaultBuildNewComparison, buildPassedComparison as defaultBuildPassedComparison, compareImages as defaultCompareImages, isDimensionMismatchError as defaultIsDimensionMismatchError } from './services/comparison-service.js';
|
|
@@ -46,7 +43,6 @@ export class TddService {
|
|
|
46
43
|
colors: defaultColors,
|
|
47
44
|
validatePathSecurity: defaultValidatePathSecurity,
|
|
48
45
|
initializeDirectories: defaultInitializeDirectories,
|
|
49
|
-
calculateHotspotCoverage: defaultCalculateHotspotCoverage,
|
|
50
46
|
fs: {
|
|
51
47
|
existsSync: defaultExistsSync,
|
|
52
48
|
mkdirSync: defaultMkdirSync,
|
|
@@ -58,8 +54,6 @@ export class TddService {
|
|
|
58
54
|
getTddBaselines: defaultGetTddBaselines,
|
|
59
55
|
getBuilds: defaultGetBuilds,
|
|
60
56
|
getComparison: defaultGetComparison,
|
|
61
|
-
getComparisonContext: defaultGetComparisonContext,
|
|
62
|
-
getBatchHotspots: defaultGetBatchHotspots,
|
|
63
57
|
fetchWithTimeout: defaultFetchWithTimeout,
|
|
64
58
|
getDefaultBranch: defaultGetDefaultBranch
|
|
65
59
|
},
|
|
@@ -67,11 +61,7 @@ export class TddService {
|
|
|
67
61
|
loadBaselineMetadata: defaultLoadBaselineMetadata,
|
|
68
62
|
saveBaselineMetadata: defaultSaveBaselineMetadata,
|
|
69
63
|
createEmptyBaselineMetadata: defaultCreateEmptyBaselineMetadata,
|
|
70
|
-
upsertScreenshotInMetadata: defaultUpsertScreenshotInMetadata
|
|
71
|
-
loadHotspotMetadata: defaultLoadHotspotMetadata,
|
|
72
|
-
saveHotspotMetadata: defaultSaveHotspotMetadata,
|
|
73
|
-
loadRegionMetadata: defaultLoadRegionMetadata,
|
|
74
|
-
saveRegionMetadata: defaultSaveRegionMetadata
|
|
64
|
+
upsertScreenshotInMetadata: defaultUpsertScreenshotInMetadata
|
|
75
65
|
},
|
|
76
66
|
baseline: {
|
|
77
67
|
baselineExists: defaultBaselineExists,
|
|
@@ -139,12 +129,6 @@ export class TddService {
|
|
|
139
129
|
this.minClusterSize = config.comparison?.minClusterSize ?? 2;
|
|
140
130
|
this.signatureProperties = config.signatureProperties ?? [];
|
|
141
131
|
|
|
142
|
-
// Hotspot data (loaded lazily from disk or downloaded from cloud)
|
|
143
|
-
this.hotspotData = null;
|
|
144
|
-
|
|
145
|
-
// Region data (user-defined 2D bounding boxes, loaded lazily)
|
|
146
|
-
this.regionData = null;
|
|
147
|
-
|
|
148
132
|
// Track whether results have been printed (to avoid duplicate output)
|
|
149
133
|
this._resultsPrinted = false;
|
|
150
134
|
if (this.setBaseline) {
|
|
@@ -170,7 +154,6 @@ export class TddService {
|
|
|
170
154
|
getTddBaselines,
|
|
171
155
|
getBuilds,
|
|
172
156
|
getComparison,
|
|
173
|
-
getComparisonContext,
|
|
174
157
|
clearBaselineData,
|
|
175
158
|
generateScreenshotSignature,
|
|
176
159
|
generateBaselineFilename,
|
|
@@ -223,19 +206,10 @@ export class TddService {
|
|
|
223
206
|
output.warn(`Build ${buildId} has status: ${baselineBuild.status} (expected: completed)`);
|
|
224
207
|
}
|
|
225
208
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
226
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
227
209
|
} else if (comparisonId) {
|
|
228
210
|
// Handle specific comparison download
|
|
229
211
|
output.info(`Using comparison: ${comparisonId}`);
|
|
230
212
|
let comparison = await getComparison(this.client, comparisonId);
|
|
231
|
-
let comparisonContext = null;
|
|
232
|
-
if (!comparison.dynamic_content && getComparisonContext) {
|
|
233
|
-
try {
|
|
234
|
-
comparisonContext = await getComparisonContext(this.client, comparisonId);
|
|
235
|
-
} catch (error) {
|
|
236
|
-
output.debug('tdd', `comparison context unavailable for ${comparisonId}: ${error.message}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
213
|
if (!comparison.baseline_screenshot) {
|
|
240
214
|
throw new Error(`Comparison ${comparisonId} has no baseline screenshot. This comparison may be a "new" screenshot.`);
|
|
241
215
|
}
|
|
@@ -266,11 +240,6 @@ export class TddService {
|
|
|
266
240
|
}
|
|
267
241
|
}
|
|
268
242
|
let screenshotName = comparison.baseline_name || comparison.current_name;
|
|
269
|
-
let dynamicContent = comparison.dynamic_content || {
|
|
270
|
-
hotspot_analysis: comparisonContext?.history?.hotspot_analysis,
|
|
271
|
-
confirmed_regions: comparisonContext?.history?.confirmed_regions
|
|
272
|
-
};
|
|
273
|
-
this.saveDynamicMetadataForScreenshot(screenshotName, dynamicContent);
|
|
274
243
|
let signature = generateScreenshotSignature(screenshotName, screenshotProperties, this.signatureProperties);
|
|
275
244
|
let filename = generateBaselineFilename(screenshotName, signature);
|
|
276
245
|
baselineBuild = {
|
|
@@ -310,7 +279,6 @@ export class TddService {
|
|
|
310
279
|
}
|
|
311
280
|
baselineBuild = apiResponse.build;
|
|
312
281
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
313
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
314
282
|
}
|
|
315
283
|
let buildDetails = baselineBuild;
|
|
316
284
|
if (!buildDetails.screenshots || buildDetails.screenshots.length === 0) {
|
|
@@ -436,7 +404,7 @@ export class TddService {
|
|
|
436
404
|
buildInfo: {
|
|
437
405
|
commitSha: baselineBuild.commit_sha,
|
|
438
406
|
commitMessage: baselineBuild.commit_message,
|
|
439
|
-
|
|
407
|
+
visual_review: baselineBuild.visual_review || null,
|
|
440
408
|
completedAt: baselineBuild.completed_at
|
|
441
409
|
},
|
|
442
410
|
screenshots: buildDetails.screenshots.filter(s => s.filename).map(s => ({
|
|
@@ -458,9 +426,6 @@ export class TddService {
|
|
|
458
426
|
};
|
|
459
427
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
460
428
|
|
|
461
|
-
// Hotspots and regions are now bundled in the tdd-baselines API response
|
|
462
|
-
// and saved earlier when processing the API response
|
|
463
|
-
|
|
464
429
|
// Save baseline build metadata for MCP plugin
|
|
465
430
|
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
466
431
|
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
@@ -470,7 +435,7 @@ export class TddService {
|
|
|
470
435
|
environment,
|
|
471
436
|
commitSha: baselineBuild.commit_sha,
|
|
472
437
|
commitMessage: baselineBuild.commit_message,
|
|
473
|
-
|
|
438
|
+
visual_review: baselineBuild.visual_review || null,
|
|
474
439
|
completedAt: baselineBuild.completed_at,
|
|
475
440
|
downloadedAt: new Date().toISOString()
|
|
476
441
|
}, null, 2));
|
|
@@ -495,49 +460,6 @@ export class TddService {
|
|
|
495
460
|
throw error;
|
|
496
461
|
}
|
|
497
462
|
}
|
|
498
|
-
saveDynamicMetadataFromBaselineResponse(apiResponse = {}) {
|
|
499
|
-
let {
|
|
500
|
-
saveHotspotMetadata,
|
|
501
|
-
saveRegionMetadata
|
|
502
|
-
} = this._deps;
|
|
503
|
-
let hasHotspots = Object.keys(apiResponse.hotspots || {}).length > 0;
|
|
504
|
-
let hasRegions = Object.keys(apiResponse.regions || {}).length > 0;
|
|
505
|
-
if (hasHotspots) {
|
|
506
|
-
this.hotspotData = apiResponse.hotspots;
|
|
507
|
-
saveHotspotMetadata(this.workingDir, apiResponse.hotspots, apiResponse.summary);
|
|
508
|
-
}
|
|
509
|
-
if (hasRegions) {
|
|
510
|
-
this.regionData = apiResponse.regions;
|
|
511
|
-
saveRegionMetadata(this.workingDir, apiResponse.regions, apiResponse.summary);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
saveDynamicMetadataForScreenshot(screenshotName, dynamicContent = {}) {
|
|
515
|
-
if (!screenshotName) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
let hotspotAnalysis = dynamicContent.hotspot_analysis;
|
|
519
|
-
let confirmedRegions = dynamicContent.confirmed_regions || [];
|
|
520
|
-
let hasHotspotAnalysis = hotspotAnalysis?.total_builds_analyzed > 0;
|
|
521
|
-
let hasConfirmedRegions = confirmedRegions.length > 0;
|
|
522
|
-
let summary = {
|
|
523
|
-
screenshots: 1,
|
|
524
|
-
hotspots: hasHotspotAnalysis ? 1 : 0,
|
|
525
|
-
regions: hasConfirmedRegions ? 1 : 0,
|
|
526
|
-
total_regions: confirmedRegions.length
|
|
527
|
-
};
|
|
528
|
-
this.saveDynamicMetadataFromBaselineResponse({
|
|
529
|
-
hotspots: hasHotspotAnalysis ? {
|
|
530
|
-
[screenshotName]: hotspotAnalysis
|
|
531
|
-
} : {},
|
|
532
|
-
regions: hasConfirmedRegions ? {
|
|
533
|
-
[screenshotName]: {
|
|
534
|
-
confirmed: confirmedRegions,
|
|
535
|
-
candidates: []
|
|
536
|
-
}
|
|
537
|
-
} : {},
|
|
538
|
-
summary
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
463
|
|
|
542
464
|
/**
|
|
543
465
|
* Process already-fetched baseline data (for use when caller handles auth)
|
|
@@ -576,7 +498,6 @@ export class TddService {
|
|
|
576
498
|
}
|
|
577
499
|
}
|
|
578
500
|
let baselineBuild = apiResponse.build;
|
|
579
|
-
this.saveDynamicMetadataFromBaselineResponse(apiResponse);
|
|
580
501
|
if (baselineBuild.status === 'failed') {
|
|
581
502
|
output.warn(`Build ${buildId} is marked as FAILED - falling back to local baselines`);
|
|
582
503
|
return await this.handleLocalBaselines();
|
|
@@ -708,7 +629,7 @@ export class TddService {
|
|
|
708
629
|
buildInfo: {
|
|
709
630
|
commitSha: baselineBuild.commit_sha,
|
|
710
631
|
commitMessage: baselineBuild.commit_message,
|
|
711
|
-
|
|
632
|
+
visual_review: baselineBuild.visual_review || null,
|
|
712
633
|
completedAt: baselineBuild.completed_at
|
|
713
634
|
},
|
|
714
635
|
screenshots: buildDetails.screenshots.filter(s => s.filename).map(s => ({
|
|
@@ -730,9 +651,6 @@ export class TddService {
|
|
|
730
651
|
};
|
|
731
652
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
732
653
|
|
|
733
|
-
// Hotspots and regions are now bundled in the tdd-baselines API response
|
|
734
|
-
// and saved earlier when processing the API response
|
|
735
|
-
|
|
736
654
|
// Save baseline build metadata for MCP plugin
|
|
737
655
|
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
738
656
|
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
@@ -742,7 +660,7 @@ export class TddService {
|
|
|
742
660
|
environment: 'test',
|
|
743
661
|
commitSha: baselineBuild.commit_sha,
|
|
744
662
|
commitMessage: baselineBuild.commit_message,
|
|
745
|
-
|
|
663
|
+
visual_review: baselineBuild.visual_review || null,
|
|
746
664
|
completedAt: baselineBuild.completed_at,
|
|
747
665
|
downloadedAt: new Date().toISOString()
|
|
748
666
|
}, null, 2));
|
|
@@ -764,118 +682,6 @@ export class TddService {
|
|
|
764
682
|
return this.baselineData;
|
|
765
683
|
}
|
|
766
684
|
|
|
767
|
-
/**
|
|
768
|
-
* Download hotspot data for screenshots
|
|
769
|
-
*/
|
|
770
|
-
async downloadHotspots(screenshots) {
|
|
771
|
-
let {
|
|
772
|
-
output,
|
|
773
|
-
getBatchHotspots,
|
|
774
|
-
saveHotspotMetadata
|
|
775
|
-
} = this._deps;
|
|
776
|
-
if (!this.config.apiKey) {
|
|
777
|
-
output.debug('tdd', 'Skipping hotspot download - no API token configured');
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
try {
|
|
781
|
-
let screenshotNames = [...new Set(screenshots.map(s => s.name))];
|
|
782
|
-
if (screenshotNames.length === 0) {
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
output.info(`🔥 Fetching hotspot data for ${screenshotNames.length} screenshots...`);
|
|
786
|
-
let response = await getBatchHotspots(this.client, screenshotNames);
|
|
787
|
-
if (!response.hotspots || Object.keys(response.hotspots).length === 0) {
|
|
788
|
-
output.debug('tdd', 'No hotspot data available from cloud');
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Update memory cache
|
|
793
|
-
this.hotspotData = response.hotspots;
|
|
794
|
-
|
|
795
|
-
// Save to disk using extracted module
|
|
796
|
-
saveHotspotMetadata(this.workingDir, response.hotspots, response.summary);
|
|
797
|
-
let hotspotCount = Object.keys(response.hotspots).length;
|
|
798
|
-
let totalRegions = Object.values(response.hotspots).reduce((sum, h) => sum + (h.regions?.length || 0), 0);
|
|
799
|
-
output.info(`Downloaded hotspot data for ${hotspotCount} screenshots (${totalRegions} regions total)`);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
output.debug('tdd', `Hotspot download failed: ${error.message}`);
|
|
802
|
-
output.warn('Could not fetch hotspot data - comparisons will run without noise filtering');
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* Load hotspot data from disk
|
|
808
|
-
*/
|
|
809
|
-
loadHotspots() {
|
|
810
|
-
let {
|
|
811
|
-
loadHotspotMetadata
|
|
812
|
-
} = this._deps;
|
|
813
|
-
return loadHotspotMetadata(this.workingDir);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Get hotspot for a specific screenshot
|
|
818
|
-
*
|
|
819
|
-
* Note: Once hotspotData is loaded (from disk or cloud), we don't reload.
|
|
820
|
-
* This is intentional - hotspots are downloaded once per session and cached.
|
|
821
|
-
* If a screenshot isn't in the cache, it means no hotspot data exists for it.
|
|
822
|
-
*/
|
|
823
|
-
getHotspotForScreenshot(screenshotName) {
|
|
824
|
-
// Check memory cache first
|
|
825
|
-
if (this.hotspotData?.[screenshotName]) {
|
|
826
|
-
return this.hotspotData[screenshotName];
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Try loading from disk (only if we haven't loaded yet)
|
|
830
|
-
if (!this.hotspotData) {
|
|
831
|
-
this.hotspotData = this.loadHotspots();
|
|
832
|
-
}
|
|
833
|
-
return this.hotspotData?.[screenshotName] || null;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Load region data from disk
|
|
838
|
-
*/
|
|
839
|
-
loadRegions() {
|
|
840
|
-
let {
|
|
841
|
-
loadRegionMetadata
|
|
842
|
-
} = this._deps;
|
|
843
|
-
return loadRegionMetadata(this.workingDir);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Get user-defined regions for a specific screenshot
|
|
848
|
-
*
|
|
849
|
-
* Note: Once regionData is loaded (from disk or cloud), we don't reload.
|
|
850
|
-
* This is intentional - regions are downloaded once per session and cached.
|
|
851
|
-
* If a screenshot isn't in the cache, it means no region data exists for it.
|
|
852
|
-
*
|
|
853
|
-
* @param {string} screenshotName - Name of the screenshot
|
|
854
|
-
* @returns {Object|null} Region data { confirmed: [], candidates: [] } or null
|
|
855
|
-
*/
|
|
856
|
-
getRegionsForScreenshot(screenshotName) {
|
|
857
|
-
// Check memory cache first
|
|
858
|
-
if (this.regionData?.[screenshotName]) {
|
|
859
|
-
return this.regionData[screenshotName];
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Try loading from disk (only if we haven't loaded yet)
|
|
863
|
-
if (!this.regionData) {
|
|
864
|
-
this.regionData = this.loadRegions();
|
|
865
|
-
}
|
|
866
|
-
return this.regionData?.[screenshotName] || null;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* Calculate hotspot coverage (delegating to pure function)
|
|
871
|
-
*/
|
|
872
|
-
calculateHotspotCoverage(diffClusters, hotspotAnalysis) {
|
|
873
|
-
let {
|
|
874
|
-
calculateHotspotCoverage
|
|
875
|
-
} = this._deps;
|
|
876
|
-
return calculateHotspotCoverage(diffClusters, hotspotAnalysis);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
685
|
/**
|
|
880
686
|
* Handle local baselines logic
|
|
881
687
|
*/
|
|
@@ -946,7 +752,7 @@ export class TddService {
|
|
|
946
752
|
/**
|
|
947
753
|
* Compare a screenshot against baseline
|
|
948
754
|
*/
|
|
949
|
-
async compareScreenshot(name, imageBuffer, properties = {}) {
|
|
755
|
+
async compareScreenshot(name, imageBuffer, properties = {}, screenshotOptions = {}) {
|
|
950
756
|
// Destructure dependencies
|
|
951
757
|
let {
|
|
952
758
|
output,
|
|
@@ -1044,8 +850,8 @@ export class TddService {
|
|
|
1044
850
|
}
|
|
1045
851
|
|
|
1046
852
|
// Baseline exists - compare
|
|
1047
|
-
let effectiveThreshold = typeof
|
|
1048
|
-
let effectiveMinClusterSize = Number.isInteger(
|
|
853
|
+
let effectiveThreshold = typeof screenshotOptions.threshold === 'number' && screenshotOptions.threshold >= 0 ? screenshotOptions.threshold : this.threshold;
|
|
854
|
+
let effectiveMinClusterSize = Number.isInteger(screenshotOptions.minClusterSize) && screenshotOptions.minClusterSize >= 1 ? screenshotOptions.minClusterSize : this.minClusterSize;
|
|
1049
855
|
try {
|
|
1050
856
|
let honeydiffResult = await compareImages(baselineImagePath, currentImagePath, diffImagePath, {
|
|
1051
857
|
threshold: effectiveThreshold,
|
|
@@ -1065,8 +871,6 @@ export class TddService {
|
|
|
1065
871
|
this._upsertComparison(result);
|
|
1066
872
|
return result;
|
|
1067
873
|
} else {
|
|
1068
|
-
let hotspotAnalysis = this.getHotspotForScreenshot(name);
|
|
1069
|
-
let regionData = this.getRegionsForScreenshot(name);
|
|
1070
874
|
let result = buildFailedComparison({
|
|
1071
875
|
name: sanitizedName,
|
|
1072
876
|
signature,
|
|
@@ -1076,9 +880,7 @@ export class TddService {
|
|
|
1076
880
|
properties: validatedProperties,
|
|
1077
881
|
threshold: effectiveThreshold,
|
|
1078
882
|
minClusterSize: effectiveMinClusterSize,
|
|
1079
|
-
honeydiffResult
|
|
1080
|
-
hotspotAnalysis,
|
|
1081
|
-
regionData
|
|
883
|
+
honeydiffResult
|
|
1082
884
|
});
|
|
1083
885
|
|
|
1084
886
|
// Log at debug level only (shown with --verbose)
|
package/dist/types/index.d.ts
CHANGED
|
@@ -210,7 +210,7 @@ export interface BuildResult {
|
|
|
210
210
|
failedComparisons?: number;
|
|
211
211
|
newComparisons?: number;
|
|
212
212
|
identicalComparisons?: number;
|
|
213
|
-
|
|
213
|
+
visual_review: unknown | null;
|
|
214
214
|
url?: string;
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -401,7 +401,11 @@ export interface TddService {
|
|
|
401
401
|
compareScreenshot(
|
|
402
402
|
name: string,
|
|
403
403
|
imageBuffer: Buffer,
|
|
404
|
-
properties?: Record<string, unknown
|
|
404
|
+
properties?: Record<string, unknown>,
|
|
405
|
+
screenshotOptions?: Pick<
|
|
406
|
+
ScreenshotOptions,
|
|
407
|
+
'threshold' | 'minClusterSize' | 'fullPage'
|
|
408
|
+
>
|
|
405
409
|
): Promise<ComparisonResult>;
|
|
406
410
|
|
|
407
411
|
getResults(): TddResults;
|
package/dist/uploader/core.js
CHANGED
|
@@ -340,13 +340,12 @@ export function buildUploadResult({
|
|
|
340
340
|
* @returns {Object} Wait result
|
|
341
341
|
*/
|
|
342
342
|
export function buildWaitResult(build) {
|
|
343
|
-
let totalComparisons = build.
|
|
344
|
-
let passedComparisons = build.
|
|
345
|
-
let failedComparisons = build.
|
|
346
|
-
let newComparisons = build.
|
|
347
|
-
let identicalComparisons = build.
|
|
348
|
-
let
|
|
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;
|
|
343
|
+
let totalComparisons = build.total_comparisons;
|
|
344
|
+
let passedComparisons = build.identical_comparisons;
|
|
345
|
+
let failedComparisons = build.changed_comparisons;
|
|
346
|
+
let newComparisons = build.new_comparisons;
|
|
347
|
+
let identicalComparisons = build.identical_comparisons;
|
|
348
|
+
let hasComparisonCounts = build.total_comparisons !== undefined || build.new_comparisons !== undefined || build.changed_comparisons !== undefined || build.identical_comparisons !== undefined;
|
|
350
349
|
let result = {
|
|
351
350
|
status: build.status,
|
|
352
351
|
build,
|
|
@@ -354,7 +353,7 @@ export function buildWaitResult(build) {
|
|
|
354
353
|
failedComparisons,
|
|
355
354
|
newComparisons,
|
|
356
355
|
identicalComparisons,
|
|
357
|
-
|
|
356
|
+
visual_review: build.visual_review || null,
|
|
358
357
|
totalComparisons
|
|
359
358
|
};
|
|
360
359
|
if (hasComparisonCounts) {
|
|
@@ -9,16 +9,62 @@ export let RESERVED_PROPERTY_OPTIONS = Object.freeze({
|
|
|
9
9
|
minClusterSize: {
|
|
10
10
|
message: 'Move "minClusterSize" out of properties; properties is only for user metadata.'
|
|
11
11
|
},
|
|
12
|
+
min_cluster_size: {
|
|
13
|
+
message: 'Move "min_cluster_size" out of properties; properties is only for user metadata.'
|
|
14
|
+
},
|
|
12
15
|
fullPage: {
|
|
13
16
|
message: 'Move "fullPage" out of properties; properties is only for user metadata.'
|
|
14
17
|
},
|
|
18
|
+
full_page: {
|
|
19
|
+
message: 'Move "full_page" out of properties; properties is only for user metadata.'
|
|
20
|
+
},
|
|
21
|
+
captureMode: {
|
|
22
|
+
message: 'Move "captureMode" out of properties; properties is only for user metadata.'
|
|
23
|
+
},
|
|
24
|
+
capture_mode: {
|
|
25
|
+
message: 'Move "capture_mode" out of properties; properties is only for user metadata.'
|
|
26
|
+
},
|
|
27
|
+
deviceScaleFactor: {
|
|
28
|
+
message: 'Move "deviceScaleFactor" out of properties; properties is only for user metadata.'
|
|
29
|
+
},
|
|
30
|
+
device_scale_factor: {
|
|
31
|
+
message: 'Move "device_scale_factor" out of properties; properties is only for user metadata.'
|
|
32
|
+
},
|
|
33
|
+
pixelRatio: {
|
|
34
|
+
message: 'Move "pixelRatio" out of properties; properties is only for user metadata.'
|
|
35
|
+
},
|
|
36
|
+
dpr: {
|
|
37
|
+
message: 'Move "dpr" out of properties; properties is only for user metadata.'
|
|
38
|
+
},
|
|
39
|
+
selector: {
|
|
40
|
+
message: 'Move "selector" out of properties; properties is only for user metadata.'
|
|
41
|
+
},
|
|
42
|
+
component: {
|
|
43
|
+
message: 'Move "component" out of properties; properties is only for user metadata.'
|
|
44
|
+
},
|
|
45
|
+
elementSelector: {
|
|
46
|
+
message: 'Move "elementSelector" out of properties; properties is only for user metadata.'
|
|
47
|
+
},
|
|
48
|
+
element_selector: {
|
|
49
|
+
message: 'Move "element_selector" out of properties; properties is only for user metadata.'
|
|
50
|
+
},
|
|
15
51
|
buildId: {
|
|
16
52
|
message: 'Move "buildId" out of properties; properties is only for user metadata.'
|
|
17
53
|
},
|
|
54
|
+
build_id: {
|
|
55
|
+
message: 'Move "build_id" out of properties; properties is only for user metadata.'
|
|
56
|
+
},
|
|
18
57
|
requestTimeout: {
|
|
19
58
|
message: 'Move "requestTimeout" out of properties; properties is only for user metadata.'
|
|
59
|
+
},
|
|
60
|
+
request_timeout: {
|
|
61
|
+
message: 'Move "request_timeout" out of properties; properties is only for user metadata.'
|
|
20
62
|
}
|
|
21
63
|
});
|
|
64
|
+
export let SCREENSHOT_OPTION_NAMES = Object.freeze(['threshold', 'minClusterSize', 'fullPage', 'captureMode', 'deviceScaleFactor', 'selector']);
|
|
65
|
+
export function getScreenshotOptionsPayload(options = {}) {
|
|
66
|
+
return Object.fromEntries(SCREENSHOT_OPTION_NAMES.filter(option => options[option] !== undefined).map(option => [option, options[option]]));
|
|
67
|
+
}
|
|
22
68
|
function createReservedPropertyWarning(option) {
|
|
23
69
|
return {
|
|
24
70
|
code: 'reserved-property-option',
|
|
@@ -28,8 +74,9 @@ function createReservedPropertyWarning(option) {
|
|
|
28
74
|
}
|
|
29
75
|
|
|
30
76
|
/**
|
|
31
|
-
* Normalize screenshot SDK options into the
|
|
32
|
-
*
|
|
77
|
+
* Normalize screenshot SDK options into the local and cloud upload payload.
|
|
78
|
+
* Reserved names found inside properties are discarded instead of being promoted
|
|
79
|
+
* into options.
|
|
33
80
|
*/
|
|
34
81
|
export function normalizeScreenshotOptions(options = {}) {
|
|
35
82
|
let {
|
|
@@ -38,38 +85,30 @@ export function normalizeScreenshotOptions(options = {}) {
|
|
|
38
85
|
requestTimeout,
|
|
39
86
|
threshold,
|
|
40
87
|
minClusterSize,
|
|
41
|
-
fullPage
|
|
88
|
+
fullPage,
|
|
89
|
+
captureMode,
|
|
90
|
+
deviceScaleFactor,
|
|
91
|
+
selector
|
|
42
92
|
} = options;
|
|
43
93
|
let warnings = [];
|
|
44
94
|
let normalizedProperties = {};
|
|
45
|
-
|
|
95
|
+
let sourceProperties = properties && typeof properties === 'object' && !Array.isArray(properties) ? properties : {};
|
|
96
|
+
for (let [key, value] of Object.entries(sourceProperties)) {
|
|
46
97
|
if (RESERVED_PROPERTY_OPTIONS[key]) {
|
|
47
98
|
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
99
|
continue;
|
|
58
100
|
}
|
|
59
101
|
normalizedProperties[key] = value;
|
|
60
102
|
}
|
|
61
|
-
if (threshold !== undefined) {
|
|
62
|
-
normalizedProperties.threshold = threshold;
|
|
63
|
-
}
|
|
64
|
-
if (minClusterSize !== undefined) {
|
|
65
|
-
normalizedProperties.minClusterSize = minClusterSize;
|
|
66
|
-
}
|
|
67
|
-
if (fullPage !== undefined) {
|
|
68
|
-
normalizedProperties.fullPage = fullPage;
|
|
69
|
-
}
|
|
70
103
|
return {
|
|
71
104
|
buildId,
|
|
72
105
|
requestTimeout,
|
|
106
|
+
threshold,
|
|
107
|
+
minClusterSize,
|
|
108
|
+
fullPage,
|
|
109
|
+
captureMode,
|
|
110
|
+
deviceScaleFactor,
|
|
111
|
+
selector,
|
|
73
112
|
properties: normalizedProperties,
|
|
74
113
|
warnings
|
|
75
114
|
};
|