@vizzly-testing/cli 0.13.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +68 -68
- package/dist/commands/doctor.js +30 -34
- package/dist/commands/finalize.js +24 -23
- package/dist/commands/init.js +30 -28
- package/dist/commands/login.js +49 -55
- package/dist/commands/logout.js +14 -19
- package/dist/commands/project.js +83 -103
- package/dist/commands/run.js +77 -89
- package/dist/commands/status.js +48 -49
- package/dist/commands/tdd-daemon.js +90 -86
- package/dist/commands/tdd.js +59 -88
- package/dist/commands/upload.js +57 -57
- package/dist/commands/whoami.js +40 -45
- package/dist/index.js +2 -5
- package/dist/plugin-loader.js +15 -17
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +78 -32
- package/dist/sdk/index.js +36 -45
- package/dist/server/handlers/api-handler.js +14 -15
- package/dist/server/handlers/tdd-handler.js +34 -37
- package/dist/server/http-server.js +75 -869
- package/dist/server/middleware/cors.js +22 -0
- package/dist/server/middleware/json-parser.js +35 -0
- package/dist/server/middleware/response.js +79 -0
- package/dist/server/routers/assets.js +91 -0
- package/dist/server/routers/auth.js +144 -0
- package/dist/server/routers/baseline.js +163 -0
- package/dist/server/routers/cloud-proxy.js +146 -0
- package/dist/server/routers/config.js +126 -0
- package/dist/server/routers/dashboard.js +130 -0
- package/dist/server/routers/health.js +61 -0
- package/dist/server/routers/projects.js +168 -0
- package/dist/server/routers/screenshot.js +86 -0
- package/dist/services/auth-service.js +1 -1
- package/dist/services/build-manager.js +13 -40
- package/dist/services/config-service.js +2 -4
- package/dist/services/html-report-generator.js +6 -5
- package/dist/services/index.js +64 -0
- package/dist/services/project-service.js +121 -40
- package/dist/services/screenshot-server.js +9 -9
- package/dist/services/server-manager.js +11 -18
- package/dist/services/static-report-generator.js +3 -4
- package/dist/services/tdd-service.js +246 -103
- package/dist/services/test-runner.js +24 -25
- package/dist/services/uploader.js +5 -4
- package/dist/types/commands/init.d.ts +1 -2
- package/dist/types/index.d.ts +2 -3
- package/dist/types/plugin-loader.d.ts +1 -2
- package/dist/types/reporter/src/api/client.d.ts +178 -0
- package/dist/types/reporter/src/components/app-router.d.ts +1 -3
- package/dist/types/reporter/src/components/code-block.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/fullscreen-viewer.d.ts +13 -0
- package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
- package/dist/types/reporter/src/components/comparison/screenshot-list.d.ts +9 -0
- package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +1 -1
- package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
- package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
- package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
- package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
- package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
- package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
- package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
- package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
- package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
- package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
- package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
- package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +5 -0
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +5 -6
- package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
- package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
- package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
- package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
- package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
- package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
- package/dist/types/sdk/index.d.ts +2 -4
- package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/server/middleware/cors.d.ts +11 -0
- package/dist/types/server/middleware/json-parser.d.ts +10 -0
- package/dist/types/server/middleware/response.d.ts +50 -0
- package/dist/types/server/routers/assets.d.ts +6 -0
- package/dist/types/server/routers/auth.d.ts +9 -0
- package/dist/types/server/routers/baseline.d.ts +13 -0
- package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
- package/dist/types/server/routers/config.d.ts +9 -0
- package/dist/types/server/routers/dashboard.d.ts +6 -0
- package/dist/types/server/routers/health.d.ts +11 -0
- package/dist/types/server/routers/projects.d.ts +9 -0
- package/dist/types/server/routers/screenshot.d.ts +11 -0
- package/dist/types/services/build-manager.d.ts +4 -3
- package/dist/types/services/config-service.d.ts +2 -3
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/project-service.d.ts +6 -4
- package/dist/types/services/screenshot-server.d.ts +5 -5
- package/dist/types/services/server-manager.d.ts +5 -3
- package/dist/types/services/tdd-service.d.ts +12 -1
- package/dist/types/services/test-runner.d.ts +3 -3
- package/dist/types/utils/output.d.ts +84 -0
- package/dist/utils/config-loader.js +24 -48
- package/dist/utils/global-config.js +2 -17
- package/dist/utils/output.js +445 -0
- package/dist/utils/security.js +3 -4
- package/docs/api-reference.md +0 -1
- package/docs/plugins.md +22 -22
- package/package.json +3 -2
- package/dist/container/index.js +0 -215
- package/dist/services/base-service.js +0 -154
- package/dist/types/container/index.d.ts +0 -59
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
- package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
- package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
- package/dist/types/services/base-service.d.ts +0 -71
- package/dist/types/utils/console-ui.d.ts +0 -61
- package/dist/types/utils/logger-factory.d.ts +0 -26
- package/dist/types/utils/logger.d.ts +0 -79
- package/dist/utils/console-ui.js +0 -241
- package/dist/utils/logger-factory.js +0 -76
- package/dist/utils/logger.js +0 -231
|
@@ -3,14 +3,13 @@ import { join } from 'path';
|
|
|
3
3
|
import { compare } from '@vizzly-testing/honeydiff';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { ApiService } from '../services/api-service.js';
|
|
6
|
-
import
|
|
6
|
+
import * as output from '../utils/output.js';
|
|
7
7
|
import { colors } from '../utils/colors.js';
|
|
8
8
|
import { getDefaultBranch } from '../utils/git.js';
|
|
9
9
|
import { fetchWithTimeout } from '../utils/fetch-utils.js';
|
|
10
10
|
import { NetworkError } from '../errors/vizzly-error.js';
|
|
11
11
|
import { HtmlReportGenerator } from './html-report-generator.js';
|
|
12
12
|
import { sanitizeScreenshotName, validatePathSecurity, safePath, validateScreenshotProperties } from '../utils/security.js';
|
|
13
|
-
const logger = createServiceLogger('TDD');
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Generate a screenshot signature for baseline matching
|
|
@@ -64,12 +63,13 @@ function generateComparisonId(signature) {
|
|
|
64
63
|
* Create a new TDD service instance
|
|
65
64
|
*/
|
|
66
65
|
export function createTDDService(config, options = {}) {
|
|
67
|
-
return new TddService(config, options.workingDir, options.setBaseline);
|
|
66
|
+
return new TddService(config, options.workingDir, options.setBaseline, options.authService);
|
|
68
67
|
}
|
|
69
68
|
export class TddService {
|
|
70
|
-
constructor(config, workingDir = process.cwd(), setBaseline = false) {
|
|
69
|
+
constructor(config, workingDir = process.cwd(), setBaseline = false, authService = null) {
|
|
71
70
|
this.config = config;
|
|
72
71
|
this.setBaseline = setBaseline;
|
|
72
|
+
this.authService = authService;
|
|
73
73
|
this.api = new ApiService({
|
|
74
74
|
baseUrl: config.apiUrl,
|
|
75
75
|
token: config.apiKey,
|
|
@@ -81,7 +81,7 @@ export class TddService {
|
|
|
81
81
|
try {
|
|
82
82
|
this.workingDir = validatePathSecurity(workingDir, workingDir);
|
|
83
83
|
} catch (error) {
|
|
84
|
-
|
|
84
|
+
output.error(`Invalid working directory: ${error.message}`);
|
|
85
85
|
throw new Error(`Working directory validation failed: ${error.message}`);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -95,7 +95,7 @@ export class TddService {
|
|
|
95
95
|
|
|
96
96
|
// Check if we're in baseline update mode
|
|
97
97
|
if (this.setBaseline) {
|
|
98
|
-
|
|
98
|
+
output.info('🐻 Baseline update mode - will overwrite existing baselines with new ones');
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Ensure directories exist
|
|
@@ -106,7 +106,7 @@ export class TddService {
|
|
|
106
106
|
recursive: true
|
|
107
107
|
});
|
|
108
108
|
} catch (error) {
|
|
109
|
-
|
|
109
|
+
output.error(`Failed to create directory ${dir}: ${error.message}`);
|
|
110
110
|
throw new Error(`Directory creation failed: ${error.message}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -119,9 +119,9 @@ export class TddService {
|
|
|
119
119
|
if (!branch) {
|
|
120
120
|
// If we can't detect a default branch, use 'main' as fallback
|
|
121
121
|
branch = 'main';
|
|
122
|
-
|
|
122
|
+
output.warn(`⚠️ Could not detect default branch, using 'main' as fallback`);
|
|
123
123
|
} else {
|
|
124
|
-
|
|
124
|
+
output.debug('tdd', `detected default branch: ${branch}`);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
try {
|
|
@@ -130,9 +130,9 @@ export class TddService {
|
|
|
130
130
|
// Use specific build ID - get it with screenshots in one call
|
|
131
131
|
const apiResponse = await this.api.getBuild(buildId, 'screenshots');
|
|
132
132
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
apiResponse
|
|
133
|
+
// API response available in verbose mode
|
|
134
|
+
output.debug('tdd', 'fetched baseline build', {
|
|
135
|
+
id: apiResponse?.build?.id || apiResponse?.id
|
|
136
136
|
});
|
|
137
137
|
if (!apiResponse) {
|
|
138
138
|
throw new Error(`Build ${buildId} not found or API returned null`);
|
|
@@ -141,22 +141,22 @@ export class TddService {
|
|
|
141
141
|
// Handle wrapped response format
|
|
142
142
|
baselineBuild = apiResponse.build || apiResponse;
|
|
143
143
|
if (!baselineBuild.id) {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
output.warn(`⚠️ Build response structure: ${JSON.stringify(Object.keys(apiResponse))}`);
|
|
145
|
+
output.warn(`⚠️ Extracted build keys: ${JSON.stringify(Object.keys(baselineBuild))}`);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// Check build status and warn if it's not successful
|
|
149
149
|
if (baselineBuild.status === 'failed') {
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
output.warn(`⚠️ Build ${buildId} is marked as FAILED - falling back to local baselines`);
|
|
151
|
+
output.info(`💡 To use remote baselines, specify a successful build ID instead`);
|
|
152
152
|
// Fall back to local baseline logic
|
|
153
153
|
return await this.handleLocalBaselines();
|
|
154
154
|
} else if (baselineBuild.status !== 'completed') {
|
|
155
|
-
|
|
155
|
+
output.warn(`⚠️ Build ${buildId} has status: ${baselineBuild.status} (expected: completed)`);
|
|
156
156
|
}
|
|
157
157
|
} else if (comparisonId) {
|
|
158
158
|
// Use specific comparison ID - download only this comparison's baseline screenshot
|
|
159
|
-
|
|
159
|
+
output.info(`Using comparison: ${comparisonId}`);
|
|
160
160
|
const comparison = await this.api.getComparison(comparisonId);
|
|
161
161
|
|
|
162
162
|
// A comparison doesn't have baselineBuild directly - we need to get it
|
|
@@ -201,7 +201,7 @@ export class TddService {
|
|
|
201
201
|
screenshotProperties.browser = comparison.baseline_browser;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
output.info(`📊 Extracted properties for signature: ${JSON.stringify(screenshotProperties)}`);
|
|
205
205
|
|
|
206
206
|
// For a specific comparison, we only download that one baseline screenshot
|
|
207
207
|
// Create a mock build structure with just this one screenshot
|
|
@@ -225,8 +225,8 @@ export class TddService {
|
|
|
225
225
|
limit: 1
|
|
226
226
|
});
|
|
227
227
|
if (!builds.data || builds.data.length === 0) {
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
output.warn(`⚠️ No baseline builds found for ${environment}/${branch}`);
|
|
229
|
+
output.info('💡 Run a build in normal mode first to create baselines');
|
|
230
230
|
return null;
|
|
231
231
|
}
|
|
232
232
|
baselineBuild = builds.data[0];
|
|
@@ -242,11 +242,11 @@ export class TddService {
|
|
|
242
242
|
buildDetails = await this.api.getBuild(actualBuildId, 'screenshots');
|
|
243
243
|
}
|
|
244
244
|
if (!buildDetails.screenshots || buildDetails.screenshots.length === 0) {
|
|
245
|
-
|
|
245
|
+
output.warn('⚠️ No screenshots found in baseline build');
|
|
246
246
|
return null;
|
|
247
247
|
}
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
output.info(`Using baseline from build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
249
|
+
output.info(`Checking ${colors.cyan(buildDetails.screenshots.length)} baseline screenshots...`);
|
|
250
250
|
|
|
251
251
|
// Check existing baseline metadata for efficient SHA comparison
|
|
252
252
|
const existingBaseline = await this.loadBaseline();
|
|
@@ -274,7 +274,7 @@ export class TddService {
|
|
|
274
274
|
try {
|
|
275
275
|
sanitizedName = sanitizeScreenshotName(screenshot.name);
|
|
276
276
|
} catch (error) {
|
|
277
|
-
|
|
277
|
+
output.warn(`Skipping screenshot with invalid name '${screenshot.name}': ${error.message}`);
|
|
278
278
|
errorCount++;
|
|
279
279
|
continue;
|
|
280
280
|
}
|
|
@@ -289,19 +289,16 @@ export class TddService {
|
|
|
289
289
|
if (existsSync(imagePath) && screenshot.sha256) {
|
|
290
290
|
const storedSha = existingShaMap.get(signature);
|
|
291
291
|
if (storedSha === screenshot.sha256) {
|
|
292
|
-
logger.debug(`⚡ Skipping ${sanitizedName} - SHA match from metadata`);
|
|
293
292
|
downloadedCount++; // Count as "downloaded" since we have it
|
|
294
293
|
skippedCount++;
|
|
295
294
|
continue;
|
|
296
|
-
} else if (storedSha) {
|
|
297
|
-
logger.debug(`🔄 SHA mismatch for ${sanitizedName} - will re-download (stored: ${storedSha?.slice(0, 8)}..., remote: ${screenshot.sha256?.slice(0, 8)}...)`);
|
|
298
295
|
}
|
|
299
296
|
}
|
|
300
297
|
|
|
301
298
|
// Use original_url as the download URL
|
|
302
299
|
const downloadUrl = screenshot.original_url || screenshot.url;
|
|
303
300
|
if (!downloadUrl) {
|
|
304
|
-
|
|
301
|
+
output.warn(`⚠️ Screenshot ${sanitizedName} has no download URL - skipping`);
|
|
305
302
|
errorCount++;
|
|
306
303
|
continue;
|
|
307
304
|
}
|
|
@@ -319,12 +316,12 @@ export class TddService {
|
|
|
319
316
|
// Process downloads in batches
|
|
320
317
|
const actualDownloadsNeeded = screenshotsToProcess.length;
|
|
321
318
|
if (actualDownloadsNeeded > 0) {
|
|
322
|
-
|
|
319
|
+
output.info(`📥 Downloading ${actualDownloadsNeeded} new/updated screenshots in batches of ${batchSize}...`);
|
|
323
320
|
for (let i = 0; i < screenshotsToProcess.length; i += batchSize) {
|
|
324
321
|
const batch = screenshotsToProcess.slice(i, i + batchSize);
|
|
325
322
|
const batchNum = Math.floor(i / batchSize) + 1;
|
|
326
323
|
const totalBatches = Math.ceil(screenshotsToProcess.length / batchSize);
|
|
327
|
-
|
|
324
|
+
output.info(`📦 Processing batch ${batchNum}/${totalBatches} (${batch.length} screenshots)`);
|
|
328
325
|
|
|
329
326
|
// Download batch concurrently
|
|
330
327
|
const downloadPromises = batch.map(async ({
|
|
@@ -333,7 +330,6 @@ export class TddService {
|
|
|
333
330
|
downloadUrl
|
|
334
331
|
}) => {
|
|
335
332
|
try {
|
|
336
|
-
logger.debug(`📥 Downloading: ${sanitizedName}`);
|
|
337
333
|
const response = await fetchWithTimeout(downloadUrl);
|
|
338
334
|
if (!response.ok) {
|
|
339
335
|
throw new NetworkError(`Failed to download ${sanitizedName}: ${response.statusText}`);
|
|
@@ -341,13 +337,12 @@ export class TddService {
|
|
|
341
337
|
const arrayBuffer = await response.arrayBuffer();
|
|
342
338
|
const imageBuffer = Buffer.from(arrayBuffer);
|
|
343
339
|
writeFileSync(imagePath, imageBuffer);
|
|
344
|
-
logger.debug(`✓ Downloaded ${sanitizedName}.png`);
|
|
345
340
|
return {
|
|
346
341
|
success: true,
|
|
347
342
|
name: sanitizedName
|
|
348
343
|
};
|
|
349
344
|
} catch (error) {
|
|
350
|
-
|
|
345
|
+
output.warn(`⚠️ Failed to download ${sanitizedName}: ${error.message}`);
|
|
351
346
|
return {
|
|
352
347
|
success: false,
|
|
353
348
|
name: sanitizedName,
|
|
@@ -364,18 +359,18 @@ export class TddService {
|
|
|
364
359
|
// Show progress
|
|
365
360
|
const totalProcessed = downloadedCount + skippedCount + errorCount;
|
|
366
361
|
const progressPercent = Math.round(totalProcessed / totalScreenshots * 100);
|
|
367
|
-
|
|
362
|
+
output.info(`📊 Progress: ${totalProcessed}/${totalScreenshots} (${progressPercent}%) - ${batchSuccesses} downloaded, ${batchFailures} failed in this batch`);
|
|
368
363
|
}
|
|
369
364
|
}
|
|
370
365
|
|
|
371
366
|
// Check if we actually downloaded any screenshots
|
|
372
367
|
if (downloadedCount === 0 && skippedCount === 0) {
|
|
373
|
-
|
|
368
|
+
output.error('❌ No screenshots were successfully downloaded from the baseline build');
|
|
374
369
|
if (errorCount > 0) {
|
|
375
|
-
|
|
370
|
+
output.info(`💡 ${errorCount} screenshots had errors - check download URLs and network connection`);
|
|
376
371
|
}
|
|
377
|
-
|
|
378
|
-
|
|
372
|
+
output.info('💡 This usually means the build failed or screenshots have no download URLs');
|
|
373
|
+
output.info('💡 Try using a successful build ID, or run without --baseline-build to create local baselines');
|
|
379
374
|
return null;
|
|
380
375
|
}
|
|
381
376
|
|
|
@@ -398,7 +393,7 @@ export class TddService {
|
|
|
398
393
|
try {
|
|
399
394
|
sanitizedName = sanitizeScreenshotName(s.name);
|
|
400
395
|
} catch (error) {
|
|
401
|
-
|
|
396
|
+
output.warn(`Screenshot name sanitization failed for '${s.name}': ${error.message}`);
|
|
402
397
|
return null; // Skip invalid screenshots
|
|
403
398
|
}
|
|
404
399
|
let properties = validateScreenshotProperties(s.metadata || s.properties || {});
|
|
@@ -445,21 +440,176 @@ export class TddService {
|
|
|
445
440
|
if (skippedCount > 0) {
|
|
446
441
|
// All skipped (up-to-date)
|
|
447
442
|
if (actualDownloads === 0) {
|
|
448
|
-
|
|
443
|
+
output.info(`✅ All ${skippedCount} baselines up-to-date (matching local SHA)`);
|
|
449
444
|
} else {
|
|
450
445
|
// Mixed: some downloaded, some skipped
|
|
451
|
-
|
|
446
|
+
output.info(`✅ Downloaded ${actualDownloads} new screenshots, ${skippedCount} already up-to-date`);
|
|
452
447
|
}
|
|
453
448
|
} else {
|
|
454
449
|
// Fresh download
|
|
455
|
-
|
|
450
|
+
output.info(`✅ Downloaded ${downloadedCount}/${buildDetails.screenshots.length} screenshots successfully`);
|
|
456
451
|
}
|
|
457
452
|
if (errorCount > 0) {
|
|
458
|
-
|
|
453
|
+
output.warn(`⚠️ ${errorCount} screenshots failed to download`);
|
|
459
454
|
}
|
|
460
455
|
return this.baselineData;
|
|
461
456
|
} catch (error) {
|
|
462
|
-
|
|
457
|
+
output.error(`❌ Failed to download baseline: ${error.message}`);
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Download baselines using OAuth authentication
|
|
464
|
+
* Used when user is logged in via device flow but no API token is configured
|
|
465
|
+
* @param {string} buildId - Build ID to download from
|
|
466
|
+
* @param {string} organizationSlug - Organization slug
|
|
467
|
+
* @param {string} projectSlug - Project slug
|
|
468
|
+
* @param {Object} authService - Auth service for OAuth requests
|
|
469
|
+
* @returns {Promise<Object>} Download result
|
|
470
|
+
*/
|
|
471
|
+
async downloadBaselinesWithAuth(buildId, organizationSlug, projectSlug, authService) {
|
|
472
|
+
output.info(`Downloading baselines using OAuth from build ${buildId}...`);
|
|
473
|
+
try {
|
|
474
|
+
// Fetch build with screenshots via OAuth endpoint
|
|
475
|
+
let endpoint = `/api/build/${projectSlug}/${buildId}/tdd-baselines`;
|
|
476
|
+
let response = await authService.authenticatedRequest(endpoint, {
|
|
477
|
+
method: 'GET',
|
|
478
|
+
headers: {
|
|
479
|
+
'X-Organization': organizationSlug
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
let {
|
|
483
|
+
build,
|
|
484
|
+
screenshots
|
|
485
|
+
} = response;
|
|
486
|
+
if (!screenshots || screenshots.length === 0) {
|
|
487
|
+
output.warn('⚠️ No screenshots found in build');
|
|
488
|
+
return {
|
|
489
|
+
downloadedCount: 0,
|
|
490
|
+
skippedCount: 0,
|
|
491
|
+
errorCount: 0
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
output.info(`Using baseline from build: ${colors.cyan(build.name || 'Unknown')} (${build.id})`);
|
|
495
|
+
output.info(`Checking ${colors.cyan(screenshots.length)} baseline screenshots...`);
|
|
496
|
+
|
|
497
|
+
// Load existing baseline metadata for SHA comparison
|
|
498
|
+
let existingBaseline = await this.loadBaseline();
|
|
499
|
+
let existingShaMap = new Map();
|
|
500
|
+
if (existingBaseline) {
|
|
501
|
+
existingBaseline.screenshots.forEach(s => {
|
|
502
|
+
if (s.sha256 && s.signature) {
|
|
503
|
+
existingShaMap.set(s.signature, s.sha256);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Process and download screenshots
|
|
509
|
+
let downloadedCount = 0;
|
|
510
|
+
let skippedCount = 0;
|
|
511
|
+
let errorCount = 0;
|
|
512
|
+
let downloadedScreenshots = [];
|
|
513
|
+
for (let screenshot of screenshots) {
|
|
514
|
+
let sanitizedName;
|
|
515
|
+
try {
|
|
516
|
+
sanitizedName = sanitizeScreenshotName(screenshot.name);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
output.warn(`Screenshot name sanitization failed for '${screenshot.name}': ${error.message}`);
|
|
519
|
+
errorCount++;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
let properties = validateScreenshotProperties(screenshot.metadata || {});
|
|
523
|
+
let signature = generateScreenshotSignature(sanitizedName, properties);
|
|
524
|
+
let filename = signatureToFilename(signature);
|
|
525
|
+
let filePath = safePath(this.baselinePath, `${filename}.png`);
|
|
526
|
+
|
|
527
|
+
// Check if we can skip via SHA comparison
|
|
528
|
+
if (screenshot.sha256 && existingShaMap.get(signature) === screenshot.sha256) {
|
|
529
|
+
skippedCount++;
|
|
530
|
+
downloadedScreenshots.push({
|
|
531
|
+
name: sanitizedName,
|
|
532
|
+
sha256: screenshot.sha256,
|
|
533
|
+
signature,
|
|
534
|
+
path: filePath,
|
|
535
|
+
properties
|
|
536
|
+
});
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Download the screenshot
|
|
541
|
+
let downloadUrl = screenshot.original_url;
|
|
542
|
+
if (!downloadUrl) {
|
|
543
|
+
output.warn(`⚠️ No download URL for screenshot: ${sanitizedName}`);
|
|
544
|
+
errorCount++;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
let imageResponse = await fetchWithTimeout(downloadUrl, {}, 30000);
|
|
549
|
+
if (!imageResponse.ok) {
|
|
550
|
+
throw new Error(`HTTP ${imageResponse.status}`);
|
|
551
|
+
}
|
|
552
|
+
let imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
|
|
553
|
+
|
|
554
|
+
// Calculate SHA256 of downloaded content
|
|
555
|
+
let sha256 = crypto.createHash('sha256').update(imageBuffer).digest('hex');
|
|
556
|
+
writeFileSync(filePath, imageBuffer);
|
|
557
|
+
downloadedCount++;
|
|
558
|
+
downloadedScreenshots.push({
|
|
559
|
+
name: sanitizedName,
|
|
560
|
+
sha256,
|
|
561
|
+
signature,
|
|
562
|
+
path: filePath,
|
|
563
|
+
properties,
|
|
564
|
+
originalUrl: downloadUrl
|
|
565
|
+
});
|
|
566
|
+
} catch (error) {
|
|
567
|
+
output.warn(`⚠️ Failed to download ${sanitizedName}: ${error.message}`);
|
|
568
|
+
errorCount++;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Store baseline metadata
|
|
573
|
+
this.baselineData = {
|
|
574
|
+
buildId: build.id,
|
|
575
|
+
buildName: build.name,
|
|
576
|
+
branch: build.branch,
|
|
577
|
+
threshold: this.threshold,
|
|
578
|
+
screenshots: downloadedScreenshots
|
|
579
|
+
};
|
|
580
|
+
let metadataPath = join(this.baselinePath, 'metadata.json');
|
|
581
|
+
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
582
|
+
|
|
583
|
+
// Save baseline build metadata
|
|
584
|
+
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
585
|
+
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
586
|
+
buildId: build.id,
|
|
587
|
+
buildName: build.name,
|
|
588
|
+
branch: build.branch,
|
|
589
|
+
commitSha: build.commit_sha,
|
|
590
|
+
downloadedAt: new Date().toISOString()
|
|
591
|
+
}, null, 2));
|
|
592
|
+
|
|
593
|
+
// Summary
|
|
594
|
+
if (skippedCount > 0 && downloadedCount === 0) {
|
|
595
|
+
output.info(`✅ All ${skippedCount} baselines up-to-date (matching local SHA)`);
|
|
596
|
+
} else if (skippedCount > 0) {
|
|
597
|
+
output.info(`✅ Downloaded ${downloadedCount} new screenshots, ${skippedCount} already up-to-date`);
|
|
598
|
+
} else {
|
|
599
|
+
output.info(`✅ Downloaded ${downloadedCount}/${screenshots.length} screenshots successfully`);
|
|
600
|
+
}
|
|
601
|
+
if (errorCount > 0) {
|
|
602
|
+
output.warn(`⚠️ ${errorCount} screenshots failed to download`);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
downloadedCount,
|
|
606
|
+
skippedCount,
|
|
607
|
+
errorCount,
|
|
608
|
+
buildId: build.id,
|
|
609
|
+
buildName: build.name
|
|
610
|
+
};
|
|
611
|
+
} catch (error) {
|
|
612
|
+
output.error(`❌ OAuth download failed: ${error.message} (org=${organizationSlug}, project=${projectSlug}, build=${buildId})`);
|
|
463
613
|
throw error;
|
|
464
614
|
}
|
|
465
615
|
}
|
|
@@ -471,7 +621,7 @@ export class TddService {
|
|
|
471
621
|
async handleLocalBaselines() {
|
|
472
622
|
// Check if we're in baseline update mode - skip loading existing baselines
|
|
473
623
|
if (this.setBaseline) {
|
|
474
|
-
|
|
624
|
+
output.info('📁 Ready for new baseline creation - all screenshots will be treated as new baselines');
|
|
475
625
|
|
|
476
626
|
// Reset baseline data since we're creating new ones
|
|
477
627
|
this.baselineData = null;
|
|
@@ -480,21 +630,21 @@ export class TddService {
|
|
|
480
630
|
const baseline = await this.loadBaseline();
|
|
481
631
|
if (!baseline) {
|
|
482
632
|
if (this.config.apiKey) {
|
|
483
|
-
|
|
484
|
-
|
|
633
|
+
output.info('📥 No local baseline found, but API key available for future remote fetching');
|
|
634
|
+
output.info('🆕 Current run will create new local baselines');
|
|
485
635
|
} else {
|
|
486
|
-
|
|
636
|
+
output.info('📝 No local baseline found and no API token - all screenshots will be marked as new');
|
|
487
637
|
}
|
|
488
638
|
return null;
|
|
489
639
|
} else {
|
|
490
|
-
|
|
640
|
+
output.info(`✅ Using existing baseline: ${colors.cyan(baseline.buildName)}`);
|
|
491
641
|
return baseline;
|
|
492
642
|
}
|
|
493
643
|
}
|
|
494
644
|
async loadBaseline() {
|
|
495
645
|
// In baseline update mode, never load existing baselines
|
|
496
646
|
if (this.setBaseline) {
|
|
497
|
-
|
|
647
|
+
output.debug('tdd', 'baseline update mode - skipping loading');
|
|
498
648
|
return null;
|
|
499
649
|
}
|
|
500
650
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
@@ -507,7 +657,7 @@ export class TddService {
|
|
|
507
657
|
this.threshold = metadata.threshold || this.threshold;
|
|
508
658
|
return metadata;
|
|
509
659
|
} catch (error) {
|
|
510
|
-
|
|
660
|
+
output.error(`❌ Failed to load baseline metadata: ${error.message}`);
|
|
511
661
|
return null;
|
|
512
662
|
}
|
|
513
663
|
}
|
|
@@ -517,14 +667,14 @@ export class TddService {
|
|
|
517
667
|
try {
|
|
518
668
|
sanitizedName = sanitizeScreenshotName(name);
|
|
519
669
|
} catch (error) {
|
|
520
|
-
|
|
670
|
+
output.error(`Invalid screenshot name '${name}': ${error.message}`);
|
|
521
671
|
throw new Error(`Screenshot name validation failed: ${error.message}`);
|
|
522
672
|
}
|
|
523
673
|
let validatedProperties;
|
|
524
674
|
try {
|
|
525
675
|
validatedProperties = validateScreenshotProperties(properties);
|
|
526
676
|
} catch (error) {
|
|
527
|
-
|
|
677
|
+
output.warn(`Property validation failed for '${sanitizedName}': ${error.message}`);
|
|
528
678
|
validatedProperties = {};
|
|
529
679
|
}
|
|
530
680
|
|
|
@@ -552,13 +702,8 @@ export class TddService {
|
|
|
552
702
|
// Check if baseline exists
|
|
553
703
|
const baselineExists = existsSync(baselineImagePath);
|
|
554
704
|
if (!baselineExists) {
|
|
555
|
-
logger.debug(`No baseline found for ${sanitizedName} - creating baseline`);
|
|
556
|
-
logger.debug(`Path: ${baselineImagePath}`);
|
|
557
|
-
logger.debug(`Size: ${imageBuffer.length} bytes`);
|
|
558
|
-
|
|
559
705
|
// Copy current screenshot to baseline directory for future comparisons
|
|
560
706
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
561
|
-
logger.debug(`Created baseline: ${imageBuffer.length} bytes`);
|
|
562
707
|
|
|
563
708
|
// Update or create baseline metadata
|
|
564
709
|
if (!this.baselineData) {
|
|
@@ -589,7 +734,9 @@ export class TddService {
|
|
|
589
734
|
// Save updated metadata
|
|
590
735
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
591
736
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
592
|
-
|
|
737
|
+
|
|
738
|
+
// Baseline creation tracked by event handler
|
|
739
|
+
|
|
593
740
|
const result = {
|
|
594
741
|
id: generateComparisonId(signature),
|
|
595
742
|
name: sanitizedName,
|
|
@@ -606,13 +753,6 @@ export class TddService {
|
|
|
606
753
|
|
|
607
754
|
// Baseline exists - compare with it
|
|
608
755
|
try {
|
|
609
|
-
// Log file sizes for debugging
|
|
610
|
-
const baselineSize = readFileSync(baselineImagePath).length;
|
|
611
|
-
const currentSize = readFileSync(currentImagePath).length;
|
|
612
|
-
logger.debug(`Comparing ${sanitizedName}`);
|
|
613
|
-
logger.debug(`Baseline: ${baselineImagePath} (${baselineSize} bytes)`);
|
|
614
|
-
logger.debug(`Current: ${currentImagePath} (${currentSize} bytes)`);
|
|
615
|
-
|
|
616
756
|
// Try to compare - honeydiff will throw if dimensions don't match
|
|
617
757
|
const result = await compare(baselineImagePath, currentImagePath, {
|
|
618
758
|
colorThreshold: this.threshold,
|
|
@@ -639,7 +779,8 @@ export class TddService {
|
|
|
639
779
|
aaPixelsIgnored: result.aaPixelsIgnored,
|
|
640
780
|
aaPercentage: result.aaPercentage
|
|
641
781
|
};
|
|
642
|
-
|
|
782
|
+
|
|
783
|
+
// Result tracked by event handler
|
|
643
784
|
this.comparisons.push(comparison);
|
|
644
785
|
return comparison;
|
|
645
786
|
} else {
|
|
@@ -672,8 +813,8 @@ export class TddService {
|
|
|
672
813
|
intensityStats: result.intensityStats,
|
|
673
814
|
diffClusters: result.diffClusters
|
|
674
815
|
};
|
|
675
|
-
|
|
676
|
-
|
|
816
|
+
output.warn(`❌ ${colors.red('FAILED')} ${sanitizedName} - differences detected${diffInfo}`);
|
|
817
|
+
output.info(` Diff saved to: ${diffImagePath}`);
|
|
677
818
|
this.comparisons.push(comparison);
|
|
678
819
|
return comparison;
|
|
679
820
|
}
|
|
@@ -683,9 +824,11 @@ export class TddService {
|
|
|
683
824
|
if (isDimensionMismatch) {
|
|
684
825
|
// Different dimensions = different screenshot signature
|
|
685
826
|
// This shouldn't happen if signatures are working correctly, but handle gracefully
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
827
|
+
output.warn(`⚠️ Dimension mismatch for ${sanitizedName} - baseline file exists but has different dimensions`);
|
|
828
|
+
output.warn(` This indicates a signature collision. Creating new baseline with correct signature.`);
|
|
829
|
+
output.debug('tdd', 'dimension mismatch', {
|
|
830
|
+
error: error.message
|
|
831
|
+
});
|
|
689
832
|
|
|
690
833
|
// Create a new baseline for this screenshot (overwriting the incorrect one)
|
|
691
834
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
@@ -715,7 +858,7 @@ export class TddService {
|
|
|
715
858
|
}
|
|
716
859
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
717
860
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
718
|
-
|
|
861
|
+
output.info(`✅ Created new baseline for ${sanitizedName} (different dimensions)`);
|
|
719
862
|
const comparison = {
|
|
720
863
|
id: generateComparisonId(signature),
|
|
721
864
|
name: sanitizedName,
|
|
@@ -731,7 +874,7 @@ export class TddService {
|
|
|
731
874
|
}
|
|
732
875
|
|
|
733
876
|
// Handle other file errors or issues
|
|
734
|
-
|
|
877
|
+
output.error(`❌ Error comparing ${sanitizedName}: ${error.message}`);
|
|
735
878
|
const comparison = {
|
|
736
879
|
id: generateComparisonId(signature),
|
|
737
880
|
name: sanitizedName,
|
|
@@ -764,34 +907,34 @@ export class TddService {
|
|
|
764
907
|
}
|
|
765
908
|
async printResults() {
|
|
766
909
|
const results = this.getResults();
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
910
|
+
output.info('\n📊 TDD Results:');
|
|
911
|
+
output.info(`Total: ${colors.cyan(results.total)}`);
|
|
912
|
+
output.info(`Passed: ${colors.green(results.passed)}`);
|
|
770
913
|
if (results.failed > 0) {
|
|
771
|
-
|
|
914
|
+
output.info(`Failed: ${colors.red(results.failed)}`);
|
|
772
915
|
}
|
|
773
916
|
if (results.new > 0) {
|
|
774
|
-
|
|
917
|
+
output.info(`New: ${colors.yellow(results.new)}`);
|
|
775
918
|
}
|
|
776
919
|
if (results.errors > 0) {
|
|
777
|
-
|
|
920
|
+
output.info(`Errors: ${colors.red(results.errors)}`);
|
|
778
921
|
}
|
|
779
922
|
|
|
780
923
|
// Show failed comparisons
|
|
781
924
|
const failedComparisons = results.comparisons.filter(c => c.status === 'failed');
|
|
782
925
|
if (failedComparisons.length > 0) {
|
|
783
|
-
|
|
926
|
+
output.info('\n❌ Failed comparisons:');
|
|
784
927
|
failedComparisons.forEach(comp => {
|
|
785
|
-
|
|
928
|
+
output.info(` • ${comp.name}`);
|
|
786
929
|
});
|
|
787
930
|
}
|
|
788
931
|
|
|
789
932
|
// Show new screenshots
|
|
790
933
|
const newComparisons = results.comparisons.filter(c => c.status === 'new');
|
|
791
934
|
if (newComparisons.length > 0) {
|
|
792
|
-
|
|
935
|
+
output.info('\n📸 New screenshots:');
|
|
793
936
|
newComparisons.forEach(comp => {
|
|
794
|
-
|
|
937
|
+
output.info(` • ${comp.name}`);
|
|
795
938
|
});
|
|
796
939
|
}
|
|
797
940
|
|
|
@@ -813,7 +956,7 @@ export class TddService {
|
|
|
813
956
|
});
|
|
814
957
|
|
|
815
958
|
// Show report path (always clickable)
|
|
816
|
-
|
|
959
|
+
output.info(`\n🐻 View detailed report: ${colors.cyan('file://' + reportPath)}`);
|
|
817
960
|
|
|
818
961
|
// Auto-open if configured
|
|
819
962
|
if (this.config.tdd?.openReport) {
|
|
@@ -821,7 +964,7 @@ export class TddService {
|
|
|
821
964
|
}
|
|
822
965
|
return reportPath;
|
|
823
966
|
} catch (error) {
|
|
824
|
-
|
|
967
|
+
output.warn(`Failed to generate HTML report: ${error.message}`);
|
|
825
968
|
}
|
|
826
969
|
}
|
|
827
970
|
|
|
@@ -854,9 +997,9 @@ export class TddService {
|
|
|
854
997
|
break;
|
|
855
998
|
}
|
|
856
999
|
await execAsync(command);
|
|
857
|
-
|
|
858
|
-
} catch
|
|
859
|
-
|
|
1000
|
+
output.info('📖 Report opened in browser');
|
|
1001
|
+
} catch {
|
|
1002
|
+
// Browser open may fail silently
|
|
860
1003
|
}
|
|
861
1004
|
}
|
|
862
1005
|
|
|
@@ -866,7 +1009,7 @@ export class TddService {
|
|
|
866
1009
|
*/
|
|
867
1010
|
updateBaselines() {
|
|
868
1011
|
if (this.comparisons.length === 0) {
|
|
869
|
-
|
|
1012
|
+
output.warn('No comparisons found - nothing to update');
|
|
870
1013
|
return 0;
|
|
871
1014
|
}
|
|
872
1015
|
let updatedCount = 0;
|
|
@@ -888,7 +1031,7 @@ export class TddService {
|
|
|
888
1031
|
current
|
|
889
1032
|
} = comparison;
|
|
890
1033
|
if (!current || !existsSync(current)) {
|
|
891
|
-
|
|
1034
|
+
output.warn(`Current screenshot not found for ${name}, skipping`);
|
|
892
1035
|
continue;
|
|
893
1036
|
}
|
|
894
1037
|
|
|
@@ -897,7 +1040,7 @@ export class TddService {
|
|
|
897
1040
|
try {
|
|
898
1041
|
sanitizedName = sanitizeScreenshotName(name);
|
|
899
1042
|
} catch (error) {
|
|
900
|
-
|
|
1043
|
+
output.warn(`Skipping baseline update for invalid name '${name}': ${error.message}`);
|
|
901
1044
|
continue;
|
|
902
1045
|
}
|
|
903
1046
|
let validatedProperties = validateScreenshotProperties(comparison.properties || {});
|
|
@@ -923,9 +1066,9 @@ export class TddService {
|
|
|
923
1066
|
this.baselineData.screenshots.push(screenshotEntry);
|
|
924
1067
|
}
|
|
925
1068
|
updatedCount++;
|
|
926
|
-
|
|
1069
|
+
output.info(`✅ Updated baseline for ${sanitizedName}`);
|
|
927
1070
|
} catch (error) {
|
|
928
|
-
|
|
1071
|
+
output.error(`❌ Failed to update baseline for ${sanitizedName}: ${error.message}`);
|
|
929
1072
|
}
|
|
930
1073
|
}
|
|
931
1074
|
|
|
@@ -934,9 +1077,9 @@ export class TddService {
|
|
|
934
1077
|
try {
|
|
935
1078
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
936
1079
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
937
|
-
|
|
1080
|
+
output.info(`✅ Updated ${updatedCount} baseline(s)`);
|
|
938
1081
|
} catch (error) {
|
|
939
|
-
|
|
1082
|
+
output.error(`❌ Failed to save baseline metadata: ${error.message}`);
|
|
940
1083
|
}
|
|
941
1084
|
}
|
|
942
1085
|
return updatedCount;
|
|
@@ -947,7 +1090,7 @@ export class TddService {
|
|
|
947
1090
|
* @private
|
|
948
1091
|
*/
|
|
949
1092
|
createNewBaseline(name, imageBuffer, properties, currentImagePath, baselineImagePath) {
|
|
950
|
-
|
|
1093
|
+
output.info(`🐻 Creating baseline for ${name}`);
|
|
951
1094
|
|
|
952
1095
|
// Copy current screenshot to baseline directory
|
|
953
1096
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
@@ -995,7 +1138,7 @@ export class TddService {
|
|
|
995
1138
|
signature
|
|
996
1139
|
};
|
|
997
1140
|
this.comparisons.push(result);
|
|
998
|
-
|
|
1141
|
+
output.info(`✅ Baseline created for ${name}`);
|
|
999
1142
|
return result;
|
|
1000
1143
|
}
|
|
1001
1144
|
|
|
@@ -1004,7 +1147,7 @@ export class TddService {
|
|
|
1004
1147
|
* @private
|
|
1005
1148
|
*/
|
|
1006
1149
|
updateSingleBaseline(name, imageBuffer, properties, currentImagePath, baselineImagePath) {
|
|
1007
|
-
|
|
1150
|
+
output.info(`🐻 Setting baseline for ${name}`);
|
|
1008
1151
|
|
|
1009
1152
|
// Copy current screenshot to baseline directory
|
|
1010
1153
|
writeFileSync(baselineImagePath, imageBuffer);
|
|
@@ -1052,7 +1195,7 @@ export class TddService {
|
|
|
1052
1195
|
signature
|
|
1053
1196
|
};
|
|
1054
1197
|
this.comparisons.push(result);
|
|
1055
|
-
|
|
1198
|
+
output.info(`🐻 Baseline set for ${name}`);
|
|
1056
1199
|
return result;
|
|
1057
1200
|
}
|
|
1058
1201
|
|
|
@@ -1083,7 +1226,7 @@ export class TddService {
|
|
|
1083
1226
|
// Find the current screenshot file
|
|
1084
1227
|
const currentImagePath = safePath(this.currentPath, `${filename}.png`);
|
|
1085
1228
|
if (!existsSync(currentImagePath)) {
|
|
1086
|
-
|
|
1229
|
+
output.error(`Current screenshot not found at: ${currentImagePath}`);
|
|
1087
1230
|
throw new Error(`Current screenshot not found: ${sanitizedName} (looked at ${currentImagePath})`);
|
|
1088
1231
|
}
|
|
1089
1232
|
|
|
@@ -1105,7 +1248,7 @@ export class TddService {
|
|
|
1105
1248
|
|
|
1106
1249
|
// Verify the write
|
|
1107
1250
|
if (!existsSync(baselineImagePath)) {
|
|
1108
|
-
|
|
1251
|
+
output.error(`Baseline file does not exist after write!`);
|
|
1109
1252
|
}
|
|
1110
1253
|
|
|
1111
1254
|
// Update baseline metadata
|