@vizzly-testing/cli 0.15.1 → 0.16.1

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.
Files changed (135) hide show
  1. package/dist/client/index.js +21 -4
  2. package/dist/reporter/reporter-bundle.css +1 -1
  3. package/dist/reporter/reporter-bundle.iife.js +11 -11
  4. package/dist/sdk/index.js +6 -6
  5. package/dist/services/api-service.js +42 -0
  6. package/dist/services/tdd-service.js +189 -6
  7. package/dist/services/test-runner.js +0 -8
  8. package/dist/services/uploader.js +1 -1
  9. package/dist/types/client.d.ts +88 -0
  10. package/dist/types/config.d.ts +29 -0
  11. package/dist/types/index.d.ts +426 -9
  12. package/dist/types/sdk.d.ts +146 -0
  13. package/dist/utils/config-helpers.js +2 -2
  14. package/docs/tdd-mode.md +34 -0
  15. package/package.json +21 -4
  16. package/dist/types/cli.d.ts +0 -2
  17. package/dist/types/client/index.d.ts +0 -80
  18. package/dist/types/commands/doctor.d.ts +0 -11
  19. package/dist/types/commands/finalize.d.ts +0 -13
  20. package/dist/types/commands/init.d.ts +0 -32
  21. package/dist/types/commands/login.d.ts +0 -11
  22. package/dist/types/commands/logout.d.ts +0 -11
  23. package/dist/types/commands/project.d.ts +0 -28
  24. package/dist/types/commands/run.d.ts +0 -16
  25. package/dist/types/commands/status.d.ts +0 -13
  26. package/dist/types/commands/tdd-daemon.d.ts +0 -24
  27. package/dist/types/commands/tdd.d.ts +0 -17
  28. package/dist/types/commands/upload.d.ts +0 -13
  29. package/dist/types/commands/whoami.d.ts +0 -11
  30. package/dist/types/errors/vizzly-error.d.ts +0 -75
  31. package/dist/types/plugin-loader.d.ts +0 -7
  32. package/dist/types/reporter/src/api/client.d.ts +0 -178
  33. package/dist/types/reporter/src/components/app-router.d.ts +0 -1
  34. package/dist/types/reporter/src/components/code-block.d.ts +0 -4
  35. package/dist/types/reporter/src/components/comparison/comparison-actions.d.ts +0 -5
  36. package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +0 -7
  37. package/dist/types/reporter/src/components/comparison/comparison-group.d.ts +0 -10
  38. package/dist/types/reporter/src/components/comparison/comparison-list.d.ts +0 -6
  39. package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +0 -10
  40. package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +0 -11
  41. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +0 -14
  42. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +0 -30
  43. package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +0 -8
  44. package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +0 -8
  45. package/dist/types/reporter/src/components/comparison/fullscreen-viewer.d.ts +0 -13
  46. package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +0 -16
  47. package/dist/types/reporter/src/components/comparison/screenshot-list.d.ts +0 -9
  48. package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +0 -9
  49. package/dist/types/reporter/src/components/comparison/view-mode-selector.d.ts +0 -4
  50. package/dist/types/reporter/src/components/dashboard/dashboard-filters.d.ts +0 -16
  51. package/dist/types/reporter/src/components/design-system/alert.d.ts +0 -9
  52. package/dist/types/reporter/src/components/design-system/badge.d.ts +0 -17
  53. package/dist/types/reporter/src/components/design-system/button.d.ts +0 -19
  54. package/dist/types/reporter/src/components/design-system/card.d.ts +0 -31
  55. package/dist/types/reporter/src/components/design-system/empty-state.d.ts +0 -13
  56. package/dist/types/reporter/src/components/design-system/form-controls.d.ts +0 -44
  57. package/dist/types/reporter/src/components/design-system/health-ring.d.ts +0 -7
  58. package/dist/types/reporter/src/components/design-system/index.d.ts +0 -11
  59. package/dist/types/reporter/src/components/design-system/modal.d.ts +0 -10
  60. package/dist/types/reporter/src/components/design-system/skeleton.d.ts +0 -19
  61. package/dist/types/reporter/src/components/design-system/spinner.d.ts +0 -10
  62. package/dist/types/reporter/src/components/design-system/tabs.d.ts +0 -13
  63. package/dist/types/reporter/src/components/layout/header.d.ts +0 -5
  64. package/dist/types/reporter/src/components/layout/index.d.ts +0 -2
  65. package/dist/types/reporter/src/components/layout/layout.d.ts +0 -6
  66. package/dist/types/reporter/src/components/ui/smart-image.d.ts +0 -7
  67. package/dist/types/reporter/src/components/ui/toast.d.ts +0 -4
  68. package/dist/types/reporter/src/components/views/builds-view.d.ts +0 -1
  69. package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +0 -5
  70. package/dist/types/reporter/src/components/views/comparisons-view.d.ts +0 -5
  71. package/dist/types/reporter/src/components/views/projects-view.d.ts +0 -1
  72. package/dist/types/reporter/src/components/views/settings-view.d.ts +0 -1
  73. package/dist/types/reporter/src/components/views/stats-view.d.ts +0 -1
  74. package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +0 -1
  75. package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +0 -15
  76. package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +0 -6
  77. package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +0 -6
  78. package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +0 -9
  79. package/dist/types/reporter/src/hooks/use-comparison-filters.d.ts +0 -20
  80. package/dist/types/reporter/src/hooks/use-image-loader.d.ts +0 -1
  81. package/dist/types/reporter/src/lib/query-client.d.ts +0 -2
  82. package/dist/types/reporter/src/lib/query-keys.d.ts +0 -13
  83. package/dist/types/reporter/src/main.d.ts +0 -1
  84. package/dist/types/reporter/src/services/api-client.d.ts +0 -11
  85. package/dist/types/reporter/src/utils/comparison-helpers.d.ts +0 -16
  86. package/dist/types/reporter/src/utils/constants.d.ts +0 -37
  87. package/dist/types/reporter/vite.config.d.ts +0 -2
  88. package/dist/types/reporter/vite.dev.config.d.ts +0 -2
  89. package/dist/types/sdk/index.d.ts +0 -110
  90. package/dist/types/server/handlers/api-handler.d.ts +0 -88
  91. package/dist/types/server/handlers/tdd-handler.d.ts +0 -162
  92. package/dist/types/server/http-server.d.ts +0 -6
  93. package/dist/types/server/middleware/cors.d.ts +0 -11
  94. package/dist/types/server/middleware/json-parser.d.ts +0 -10
  95. package/dist/types/server/middleware/response.d.ts +0 -50
  96. package/dist/types/server/routers/assets.d.ts +0 -6
  97. package/dist/types/server/routers/auth.d.ts +0 -9
  98. package/dist/types/server/routers/baseline.d.ts +0 -13
  99. package/dist/types/server/routers/cloud-proxy.d.ts +0 -11
  100. package/dist/types/server/routers/config.d.ts +0 -9
  101. package/dist/types/server/routers/dashboard.d.ts +0 -6
  102. package/dist/types/server/routers/health.d.ts +0 -11
  103. package/dist/types/server/routers/projects.d.ts +0 -9
  104. package/dist/types/server/routers/screenshot.d.ts +0 -11
  105. package/dist/types/services/api-service.d.ts +0 -100
  106. package/dist/types/services/auth-service.d.ts +0 -59
  107. package/dist/types/services/build-manager.d.ts +0 -69
  108. package/dist/types/services/config-service.d.ts +0 -97
  109. package/dist/types/services/html-report-generator.d.ts +0 -52
  110. package/dist/types/services/index.d.ts +0 -7
  111. package/dist/types/services/project-service.d.ts +0 -105
  112. package/dist/types/services/report-generator/viewer.d.ts +0 -0
  113. package/dist/types/services/screenshot-server.d.ts +0 -10
  114. package/dist/types/services/server-manager.d.ts +0 -269
  115. package/dist/types/services/static-report-generator.d.ts +0 -25
  116. package/dist/types/services/tdd-service.d.ts +0 -95
  117. package/dist/types/services/test-runner.d.ts +0 -31
  118. package/dist/types/services/uploader.d.ts +0 -36
  119. package/dist/types/utils/browser.d.ts +0 -6
  120. package/dist/types/utils/build-history.d.ts +0 -16
  121. package/dist/types/utils/ci-env.d.ts +0 -55
  122. package/dist/types/utils/colors.d.ts +0 -12
  123. package/dist/types/utils/config-helpers.d.ts +0 -6
  124. package/dist/types/utils/config-loader.d.ts +0 -25
  125. package/dist/types/utils/config-schema.d.ts +0 -51
  126. package/dist/types/utils/environment-config.d.ts +0 -59
  127. package/dist/types/utils/environment.d.ts +0 -36
  128. package/dist/types/utils/fetch-utils.d.ts +0 -1
  129. package/dist/types/utils/file-helpers.d.ts +0 -18
  130. package/dist/types/utils/git.d.ts +0 -56
  131. package/dist/types/utils/global-config.d.ts +0 -84
  132. package/dist/types/utils/image-input-detector.d.ts +0 -71
  133. package/dist/types/utils/output.d.ts +0 -84
  134. package/dist/types/utils/package-info.d.ts +0 -15
  135. package/dist/types/utils/security.d.ts +0 -30
package/dist/sdk/index.js CHANGED
@@ -22,7 +22,7 @@ import { VizzlyError } from '../errors/vizzly-error.js';
22
22
  /**
23
23
  * Create a new Vizzly instance with custom configuration
24
24
  *
25
- * @param {import('../types').VizzlyConfig} [config] - Configuration options
25
+ * @param {Object} [config] - Configuration options
26
26
  * @returns {Promise<VizzlySDK>} Configured Vizzly SDK instance
27
27
  *
28
28
  * @example
@@ -147,7 +147,7 @@ export function createVizzly(config = {}, options = {}) {
147
147
  */
148
148
  export class VizzlySDK extends EventEmitter {
149
149
  /**
150
- * @param {import('../types').VizzlyConfig} config - Configuration
150
+ * @param {Object} config - Configuration
151
151
  * @param {Object} services - Service instances
152
152
  */
153
153
  constructor(config, services) {
@@ -223,7 +223,7 @@ export class VizzlySDK extends EventEmitter {
223
223
  * Capture a screenshot
224
224
  * @param {string} name - Screenshot name
225
225
  * @param {Buffer|string} imageBuffer - Image data as a Buffer, or a file path to an image
226
- * @param {import('../types').ScreenshotOptions} [options] - Options
226
+ * @param {Object} [options] - Options
227
227
  * @returns {Promise<void>}
228
228
  * @throws {VizzlyError} When server is not running
229
229
  * @throws {VizzlyError} When file path is provided but file doesn't exist
@@ -288,8 +288,8 @@ export class VizzlySDK extends EventEmitter {
288
288
 
289
289
  /**
290
290
  * Upload all captured screenshots
291
- * @param {import('../types').UploadOptions} [options] - Upload options
292
- * @returns {Promise<import('../types').UploadResult>} Upload result
291
+ * @param {Object} [options] - Upload options
292
+ * @returns {Promise<Object>} Upload result
293
293
  */
294
294
  async upload(options = {}) {
295
295
  if (!this.services?.uploader) {
@@ -332,7 +332,7 @@ export class VizzlySDK extends EventEmitter {
332
332
  * Run local comparison in TDD mode
333
333
  * @param {string} name - Screenshot name
334
334
  * @param {Buffer|string} imageBuffer - Current image as a Buffer, or a file path to an image
335
- * @returns {Promise<import('../types').ComparisonResult>} Comparison result
335
+ * @returns {Promise<Object>} Comparison result
336
336
  * @throws {VizzlyError} When file path is provided but file doesn't exist
337
337
  * @throws {VizzlyError} When file cannot be read due to permissions or I/O errors
338
338
  */
@@ -357,4 +357,46 @@ export class ApiService {
357
357
  }
358
358
  });
359
359
  }
360
+
361
+ /**
362
+ * Get hotspot analysis for a single screenshot
363
+ * @param {string} screenshotName - Screenshot name to get hotspots for
364
+ * @param {Object} options - Optional settings
365
+ * @param {number} [options.windowSize=20] - Number of historical builds to analyze
366
+ * @returns {Promise<Object>} Hotspot analysis data
367
+ */
368
+ async getScreenshotHotspots(screenshotName, options = {}) {
369
+ let {
370
+ windowSize = 20
371
+ } = options;
372
+ let queryParams = new URLSearchParams({
373
+ windowSize: String(windowSize)
374
+ });
375
+ let encodedName = encodeURIComponent(screenshotName);
376
+ return this.request(`/api/sdk/screenshots/${encodedName}/hotspots?${queryParams}`);
377
+ }
378
+
379
+ /**
380
+ * Batch get hotspot analysis for multiple screenshots
381
+ * More efficient than calling getScreenshotHotspots for each screenshot
382
+ * @param {string[]} screenshotNames - Array of screenshot names
383
+ * @param {Object} options - Optional settings
384
+ * @param {number} [options.windowSize=20] - Number of historical builds to analyze
385
+ * @returns {Promise<Object>} Hotspots keyed by screenshot name
386
+ */
387
+ async getBatchHotspots(screenshotNames, options = {}) {
388
+ let {
389
+ windowSize = 20
390
+ } = options;
391
+ return this.request('/api/sdk/screenshots/hotspots', {
392
+ method: 'POST',
393
+ headers: {
394
+ 'Content-Type': 'application/json'
395
+ },
396
+ body: JSON.stringify({
397
+ screenshot_names: screenshotNames,
398
+ windowSize
399
+ })
400
+ });
401
+ }
360
402
  }
@@ -420,6 +420,9 @@ export class TddService {
420
420
  const metadataPath = join(this.baselinePath, 'metadata.json');
421
421
  writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
422
422
 
423
+ // Download hotspot data for noise filtering
424
+ await this.downloadHotspots(buildDetails.screenshots);
425
+
423
426
  // Save baseline build metadata for MCP plugin
424
427
  const baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
425
428
  const buildMetadata = {
@@ -459,6 +462,152 @@ export class TddService {
459
462
  }
460
463
  }
461
464
 
465
+ /**
466
+ * Download hotspot data for screenshots from the cloud
467
+ * Hotspots identify regions that frequently change (timestamps, IDs, etc.)
468
+ * Used to filter out known dynamic content during comparisons
469
+ * @param {Array} screenshots - Array of screenshot objects with name property
470
+ */
471
+ async downloadHotspots(screenshots) {
472
+ // Only attempt if we have an API token
473
+ if (!this.config.apiKey) {
474
+ output.debug('tdd', 'Skipping hotspot download - no API token configured');
475
+ return;
476
+ }
477
+ try {
478
+ // Get unique screenshot names
479
+ let screenshotNames = [...new Set(screenshots.map(s => s.name))];
480
+ if (screenshotNames.length === 0) {
481
+ return;
482
+ }
483
+ output.info(`🔥 Fetching hotspot data for ${screenshotNames.length} screenshots...`);
484
+
485
+ // Use batch endpoint for efficiency
486
+ let response = await this.api.getBatchHotspots(screenshotNames);
487
+ if (!response.hotspots || Object.keys(response.hotspots).length === 0) {
488
+ output.debug('tdd', 'No hotspot data available from cloud');
489
+ return;
490
+ }
491
+
492
+ // Store hotspots in a separate file for easy access during comparisons
493
+ this.hotspotData = response.hotspots;
494
+ let hotspotsPath = safePath(this.workingDir, '.vizzly', 'hotspots.json');
495
+ writeFileSync(hotspotsPath, JSON.stringify({
496
+ downloadedAt: new Date().toISOString(),
497
+ summary: response.summary,
498
+ hotspots: response.hotspots
499
+ }, null, 2));
500
+ let hotspotCount = Object.keys(response.hotspots).length;
501
+ let totalRegions = Object.values(response.hotspots).reduce((sum, h) => sum + (h.regions?.length || 0), 0);
502
+ output.info(`✅ Downloaded hotspot data for ${hotspotCount} screenshots (${totalRegions} regions total)`);
503
+ } catch (error) {
504
+ // Don't fail baseline download if hotspot fetch fails
505
+ output.debug('tdd', `Hotspot download failed: ${error.message}`);
506
+ output.warn('⚠️ Could not fetch hotspot data - comparisons will run without noise filtering');
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Load hotspot data from disk
512
+ * @returns {Object|null} Hotspot data keyed by screenshot name, or null if not available
513
+ */
514
+ loadHotspots() {
515
+ try {
516
+ let hotspotsPath = safePath(this.workingDir, '.vizzly', 'hotspots.json');
517
+ if (!existsSync(hotspotsPath)) {
518
+ return null;
519
+ }
520
+ let data = JSON.parse(readFileSync(hotspotsPath, 'utf8'));
521
+ return data.hotspots || null;
522
+ } catch (error) {
523
+ output.debug('tdd', `Failed to load hotspots: ${error.message}`);
524
+ return null;
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Get hotspot analysis for a specific screenshot
530
+ * @param {string} screenshotName - Name of the screenshot
531
+ * @returns {Object|null} Hotspot analysis or null if not available
532
+ */
533
+ getHotspotForScreenshot(screenshotName) {
534
+ // Check memory cache first
535
+ if (this.hotspotData && this.hotspotData[screenshotName]) {
536
+ return this.hotspotData[screenshotName];
537
+ }
538
+
539
+ // Try loading from disk
540
+ if (!this.hotspotData) {
541
+ this.hotspotData = this.loadHotspots();
542
+ }
543
+ return this.hotspotData?.[screenshotName] || null;
544
+ }
545
+
546
+ /**
547
+ * Calculate what percentage of diff falls within hotspot regions
548
+ * Uses 1D Y-coordinate matching (same algorithm as cloud)
549
+ * @param {Array} diffClusters - Array of diff clusters from honeydiff
550
+ * @param {Object} hotspotAnalysis - Hotspot data with regions array
551
+ * @returns {Object} Coverage info { coverage, linesInHotspots, totalLines }
552
+ */
553
+ calculateHotspotCoverage(diffClusters, hotspotAnalysis) {
554
+ if (!diffClusters || diffClusters.length === 0) {
555
+ return {
556
+ coverage: 0,
557
+ linesInHotspots: 0,
558
+ totalLines: 0
559
+ };
560
+ }
561
+ if (!hotspotAnalysis || !hotspotAnalysis.regions || hotspotAnalysis.regions.length === 0) {
562
+ return {
563
+ coverage: 0,
564
+ linesInHotspots: 0,
565
+ totalLines: 0
566
+ };
567
+ }
568
+
569
+ // Extract Y-coordinates (diff lines) from clusters
570
+ // Each cluster has a boundingBox with y and height
571
+ let diffLines = [];
572
+ for (let cluster of diffClusters) {
573
+ if (cluster.boundingBox) {
574
+ let {
575
+ y,
576
+ height
577
+ } = cluster.boundingBox;
578
+ // Add all Y lines covered by this cluster
579
+ for (let line = y; line < y + height; line++) {
580
+ diffLines.push(line);
581
+ }
582
+ }
583
+ }
584
+ if (diffLines.length === 0) {
585
+ return {
586
+ coverage: 0,
587
+ linesInHotspots: 0,
588
+ totalLines: 0
589
+ };
590
+ }
591
+
592
+ // Remove duplicates and sort
593
+ diffLines = [...new Set(diffLines)].sort((a, b) => a - b);
594
+
595
+ // Check how many diff lines fall within hotspot regions
596
+ let linesInHotspots = 0;
597
+ for (let line of diffLines) {
598
+ let inHotspot = hotspotAnalysis.regions.some(region => line >= region.y1 && line <= region.y2);
599
+ if (inHotspot) {
600
+ linesInHotspots++;
601
+ }
602
+ }
603
+ let coverage = linesInHotspots / diffLines.length;
604
+ return {
605
+ coverage,
606
+ linesInHotspots,
607
+ totalLines: diffLines.length
608
+ };
609
+ }
610
+
462
611
  /**
463
612
  * Download baselines using OAuth authentication
464
613
  * Used when user is logged in via device flow but no API token is configured
@@ -784,17 +933,36 @@ export class TddService {
784
933
  this.comparisons.push(comparison);
785
934
  return comparison;
786
935
  } else {
787
- // Images differ
936
+ // Images differ - check if differences are in known hotspot regions
937
+ let hotspotAnalysis = this.getHotspotForScreenshot(name);
938
+ let hotspotCoverage = null;
939
+ let isHotspotFiltered = false;
940
+ if (hotspotAnalysis && result.diffClusters && result.diffClusters.length > 0) {
941
+ hotspotCoverage = this.calculateHotspotCoverage(result.diffClusters, hotspotAnalysis);
942
+
943
+ // Consider it filtered if:
944
+ // 1. High confidence hotspot data (score >= 70)
945
+ // 2. 80%+ of the diff is within hotspot regions
946
+ let isHighConfidence = hotspotAnalysis.confidence === 'high' || hotspotAnalysis.confidence_score && hotspotAnalysis.confidence_score >= 70;
947
+ if (isHighConfidence && hotspotCoverage.coverage >= 0.8) {
948
+ isHotspotFiltered = true;
949
+ }
950
+ }
788
951
  let diffInfo = ` (${result.diffPercentage.toFixed(2)}% different, ${result.diffPixels} pixels)`;
789
952
 
790
953
  // Add cluster info to log if available
791
954
  if (result.diffClusters && result.diffClusters.length > 0) {
792
955
  diffInfo += `, ${result.diffClusters.length} region${result.diffClusters.length > 1 ? 's' : ''}`;
793
956
  }
957
+
958
+ // Add hotspot info if applicable
959
+ if (hotspotCoverage && hotspotCoverage.coverage > 0) {
960
+ diffInfo += `, ${Math.round(hotspotCoverage.coverage * 100)}% in hotspots`;
961
+ }
794
962
  const comparison = {
795
963
  id: generateComparisonId(signature),
796
964
  name: sanitizedName,
797
- status: 'failed',
965
+ status: isHotspotFiltered ? 'passed' : 'failed',
798
966
  baseline: baselineImagePath,
799
967
  current: currentImagePath,
800
968
  diff: diffImagePath,
@@ -803,7 +971,7 @@ export class TddService {
803
971
  threshold: this.threshold,
804
972
  diffPercentage: result.diffPercentage,
805
973
  diffCount: result.diffPixels,
806
- reason: 'pixel-diff',
974
+ reason: isHotspotFiltered ? 'hotspot-filtered' : 'pixel-diff',
807
975
  // Honeydiff metrics
808
976
  totalPixels: result.totalPixels,
809
977
  aaPixelsIgnored: result.aaPixelsIgnored,
@@ -811,10 +979,25 @@ export class TddService {
811
979
  boundingBox: result.boundingBox,
812
980
  heightDiff: result.heightDiff,
813
981
  intensityStats: result.intensityStats,
814
- diffClusters: result.diffClusters
982
+ diffClusters: result.diffClusters,
983
+ // Hotspot analysis data
984
+ hotspotAnalysis: hotspotCoverage ? {
985
+ coverage: hotspotCoverage.coverage,
986
+ linesInHotspots: hotspotCoverage.linesInHotspots,
987
+ totalLines: hotspotCoverage.totalLines,
988
+ confidence: hotspotAnalysis?.confidence,
989
+ confidenceScore: hotspotAnalysis?.confidence_score,
990
+ regionCount: hotspotAnalysis?.regions?.length || 0,
991
+ isFiltered: isHotspotFiltered
992
+ } : null
815
993
  };
816
- output.warn(`❌ ${colors.red('FAILED')} ${sanitizedName} - differences detected${diffInfo}`);
817
- output.info(` Diff saved to: ${diffImagePath}`);
994
+ if (isHotspotFiltered) {
995
+ output.info(`✅ ${colors.green('PASSED')} ${sanitizedName} - differences in known hotspots${diffInfo}`);
996
+ output.debug('tdd', `Hotspot filtered: ${Math.round(hotspotCoverage.coverage * 100)}% coverage, confidence: ${hotspotAnalysis.confidence}`);
997
+ } else {
998
+ output.warn(`❌ ${colors.red('FAILED')} ${sanitizedName} - differences detected${diffInfo}`);
999
+ output.info(` Diff saved to: ${diffImagePath}`);
1000
+ }
818
1001
  this.comparisons.push(comparison);
819
1002
  return comparison;
820
1003
  }
@@ -94,14 +94,6 @@ export class TestRunner extends EventEmitter {
94
94
 
95
95
  // Start server with appropriate handler
96
96
  await this.serverManager.start(buildId, tdd, options.setBaseline);
97
-
98
- // Forward server events
99
- if (this.serverManager.server?.emitter) {
100
- this.serverManager.server.emitter.on('screenshot-captured', screenshotInfo => {
101
- screenshotCount++;
102
- this.emit('screenshot-captured', screenshotInfo);
103
- });
104
- }
105
97
  const env = {
106
98
  ...process.env,
107
99
  VIZZLY_SERVER_URL: `http://localhost:${this.config.server.port}`,
@@ -24,7 +24,7 @@ export function createUploader({
24
24
  userAgent,
25
25
  command,
26
26
  upload: uploadConfig = {}
27
- }, options = {}) {
27
+ } = {}, options = {}) {
28
28
  let signal = options.signal || new AbortController().signal;
29
29
  const api = new ApiService({
30
30
  baseUrl: apiUrl,
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Vizzly Client Type Definitions
3
+ * Lightweight client for test runners
4
+ * @module @vizzly-testing/cli/client
5
+ */
6
+
7
+ /**
8
+ * Take a screenshot for visual regression testing
9
+ *
10
+ * @param name - Unique name for the screenshot
11
+ * @param imageBuffer - PNG image data as a Buffer, or a file path to an image
12
+ * @param options - Optional configuration
13
+ *
14
+ * @example
15
+ * // Basic usage with Buffer
16
+ * import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
17
+ *
18
+ * const screenshot = await page.screenshot();
19
+ * await vizzlyScreenshot('homepage', screenshot);
20
+ *
21
+ * @example
22
+ * // Basic usage with file path
23
+ * await vizzlyScreenshot('homepage', './screenshots/homepage.png');
24
+ *
25
+ * @example
26
+ * // With properties and threshold
27
+ * await vizzlyScreenshot('checkout-form', screenshot, {
28
+ * properties: { browser: 'chrome', viewport: '1920x1080' },
29
+ * threshold: 5
30
+ * });
31
+ */
32
+ export function vizzlyScreenshot(
33
+ name: string,
34
+ imageBuffer: Buffer | string,
35
+ options?: {
36
+ properties?: Record<string, unknown>;
37
+ threshold?: number;
38
+ fullPage?: boolean;
39
+ }
40
+ ): Promise<void>;
41
+
42
+ /**
43
+ * Wait for all queued screenshots to be processed
44
+ *
45
+ * @example
46
+ * afterAll(async () => {
47
+ * await vizzlyFlush();
48
+ * });
49
+ */
50
+ export function vizzlyFlush(): Promise<void>;
51
+
52
+ /**
53
+ * Check if the Vizzly client is initialized and ready
54
+ *
55
+ * @returns True if client is ready, false otherwise
56
+ */
57
+ export function isVizzlyReady(): boolean;
58
+
59
+ /**
60
+ * Configure the client with custom settings
61
+ *
62
+ * @param config - Configuration options
63
+ */
64
+ export function configure(config?: {
65
+ serverUrl?: string;
66
+ enabled?: boolean;
67
+ }): void;
68
+
69
+ /**
70
+ * Enable or disable screenshot capture
71
+ *
72
+ * @param enabled - Whether to enable screenshots
73
+ */
74
+ export function setEnabled(enabled: boolean): void;
75
+
76
+ /**
77
+ * Get information about Vizzly client state
78
+ *
79
+ * @returns Client information
80
+ */
81
+ export function getVizzlyInfo(): {
82
+ enabled: boolean;
83
+ serverUrl: string | null;
84
+ ready: boolean;
85
+ buildId: string | null;
86
+ tddMode: boolean;
87
+ disabled: boolean;
88
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Vizzly Config Type Definitions
3
+ * Configuration helpers
4
+ * @module @vizzly-testing/cli/config
5
+ */
6
+
7
+ export { VizzlyConfig } from './index';
8
+
9
+ /**
10
+ * Define Vizzly configuration with type hints
11
+ *
12
+ * Use this in your vizzly.config.js for better IDE support:
13
+ *
14
+ * @example
15
+ * import { defineConfig } from '@vizzly-testing/cli/config';
16
+ *
17
+ * export default defineConfig({
18
+ * apiKey: process.env.VIZZLY_TOKEN,
19
+ * server: {
20
+ * port: 47392
21
+ * },
22
+ * comparison: {
23
+ * threshold: 0.1
24
+ * }
25
+ * });
26
+ */
27
+ export function defineConfig(
28
+ config: import('./index').VizzlyConfig
29
+ ): import('./index').VizzlyConfig;