@vizzly-testing/cli 0.21.2 → 0.22.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.
package/dist/cli.js CHANGED
@@ -16,6 +16,8 @@ import { validateWhoamiOptions, whoamiCommand } from './commands/whoami.js';
16
16
  import { createPluginServices } from './plugin-api.js';
17
17
  import { loadPlugins } from './plugin-loader.js';
18
18
  import { createServices } from './services/index.js';
19
+ import { generateStaticReport, getReportFileUrl } from './services/static-report-generator.js';
20
+ import { openBrowser } from './utils/browser.js';
19
21
  import { colors } from './utils/colors.js';
20
22
  import { loadConfig } from './utils/config-loader.js';
21
23
  import { getContext } from './utils/context.js';
@@ -274,7 +276,7 @@ program.command('upload').description('Upload screenshots to Vizzly').argument('
274
276
  const tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual comparisons');
275
277
 
276
278
  // TDD Start - Background server
277
- tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server', '47392').option('--open', 'Open dashboard in browser').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--token <token>', 'API token override').option('--daemon-child', 'Internal: run as daemon child process').action(async options => {
279
+ tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server', '47392').option('--open', 'Open dashboard in browser').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--fail-on-diff', 'Fail tests when visual differences are detected').option('--token <token>', 'API token override').option('--daemon-child', 'Internal: run as daemon child process').action(async options => {
278
280
  const globalOptions = program.opts();
279
281
 
280
282
  // If this is a daemon child process, run the server directly
@@ -298,7 +300,7 @@ tddCmd.command('status').description('Check TDD server status').action(async opt
298
300
  });
299
301
 
300
302
  // TDD Run - One-off test run with ephemeral server (generates static report)
301
- tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual comparisons').option('--port <port>', 'Port for TDD server', '47392').option('--branch <branch>', 'Git branch override').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--set-baseline', 'Accept current screenshots as new baseline (overwrites existing)').action(async (command, options) => {
303
+ tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual comparisons').option('--port <port>', 'Port for TDD server', '47392').option('--branch <branch>', 'Git branch override').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--set-baseline', 'Accept current screenshots as new baseline (overwrites existing)').option('--fail-on-diff', 'Fail tests when visual differences are detected').option('--no-open', 'Skip opening report in browser').action(async (command, options) => {
302
304
  const globalOptions = program.opts();
303
305
 
304
306
  // Validate options
@@ -315,21 +317,49 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
315
317
  cleanup
316
318
  } = await tddCommand(command, options, globalOptions);
317
319
 
318
- // Set up cleanup on process signals
320
+ // Track cleanup state to prevent double cleanup
321
+ let isCleaningUp = false;
319
322
  const handleCleanup = async () => {
323
+ if (isCleaningUp) return;
324
+ isCleaningUp = true;
320
325
  await cleanup();
321
326
  };
322
- process.once('SIGINT', () => {
323
- handleCleanup().then(() => process.exit(1));
324
- });
325
- process.once('SIGTERM', () => {
326
- handleCleanup().then(() => process.exit(1));
327
- });
328
- if (result && !result.success && result.exitCode > 0) {
329
- await cleanup();
330
- process.exit(result.exitCode);
327
+
328
+ // Set up cleanup on process signals
329
+ const sigintHandler = () => {
330
+ handleCleanup().then(() => process.exit(result?.exitCode || 0));
331
+ };
332
+ const sigtermHandler = () => {
333
+ handleCleanup().then(() => process.exit(result?.exitCode || 0));
334
+ };
335
+ process.once('SIGINT', sigintHandler);
336
+ process.once('SIGTERM', sigtermHandler);
337
+
338
+ // If there are comparisons, generate static report
339
+ const hasComparisons = result?.comparisons?.length > 0;
340
+ if (hasComparisons) {
341
+ // Note: Tests have completed at this point, so report-data.json is stable.
342
+ // The report reflects the final state of all comparisons.
343
+ const reportResult = await generateStaticReport(process.cwd());
344
+ if (reportResult.success) {
345
+ const reportUrl = getReportFileUrl(reportResult.reportPath);
346
+ output.print(` ${colors.brand.textTertiary('→')} Report: ${colors.blue(reportUrl)}`);
347
+ output.blank();
348
+
349
+ // Open report in browser unless --no-open
350
+ if (options.open !== false) {
351
+ await openBrowser(reportUrl);
352
+ }
353
+ } else {
354
+ output.warn(`Failed to generate static report: ${reportResult.error}`);
355
+ }
331
356
  }
332
- await cleanup();
357
+
358
+ // Remove signal handlers before normal cleanup to prevent double cleanup
359
+ process.off('SIGINT', sigintHandler);
360
+ process.off('SIGTERM', sigtermHandler);
361
+ await handleCleanup();
362
+ process.exit(result?.exitCode || 0);
333
363
  });
334
364
  program.command('run').description('Run tests with Vizzly integration').argument('<command>', 'Test command to run').option('--port <port>', 'Port for screenshot server', '47392').option('-b, --build-name <name>', 'Custom build name').option('--branch <branch>', 'Git branch override').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--allow-no-token', 'Allow running without API token').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (command, options) => {
335
365
  const globalOptions = program.opts();
@@ -356,8 +356,8 @@ export function isVizzlyReady() {
356
356
  * @param {boolean} [config.enabled] - Enable/disable screenshots
357
357
  */
358
358
  export function configure(config = {}) {
359
- if (config.serverUrl) {
360
- currentClient = createSimpleClient(config.serverUrl);
359
+ if ('serverUrl' in config) {
360
+ currentClient = config.serverUrl ? createSimpleClient(config.serverUrl) : null;
361
361
  }
362
362
  if (typeof config.enabled === 'boolean') {
363
363
  setVizzlyEnabled(config.enabled);
@@ -56,7 +56,7 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
56
56
  // CLI entry point
57
57
  'tdd', 'start', '--daemon-child',
58
58
  // Special flag for child process
59
- '--port', port.toString(), ...(options.open ? ['--open'] : []), ...(options.baselineBuild ? ['--baseline-build', options.baselineBuild] : []), ...(options.baselineComparison ? ['--baseline-comparison', options.baselineComparison] : []), ...(options.environment ? ['--environment', options.environment] : []), ...(options.threshold !== undefined ? ['--threshold', options.threshold.toString()] : []), ...(options.timeout ? ['--timeout', options.timeout] : []), ...(options.token ? ['--token', options.token] : []), ...(globalOptions.json ? ['--json'] : []), ...(globalOptions.verbose ? ['--verbose'] : []), ...(globalOptions.noColor ? ['--no-color'] : [])], {
59
+ '--port', port.toString(), ...(options.open ? ['--open'] : []), ...(options.baselineBuild ? ['--baseline-build', options.baselineBuild] : []), ...(options.baselineComparison ? ['--baseline-comparison', options.baselineComparison] : []), ...(options.environment ? ['--environment', options.environment] : []), ...(options.threshold !== undefined ? ['--threshold', options.threshold.toString()] : []), ...(options.timeout ? ['--timeout', options.timeout] : []), ...(options.failOnDiff ? ['--fail-on-diff'] : []), ...(options.token ? ['--token', options.token] : []), ...(globalOptions.json ? ['--json'] : []), ...(globalOptions.verbose ? ['--verbose'] : []), ...(globalOptions.noColor ? ['--no-color'] : [])], {
60
60
  detached: true,
61
61
  stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
62
62
  cwd: process.cwd()
@@ -199,7 +199,8 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
199
199
  const serverInfo = {
200
200
  pid: process.pid,
201
201
  port: port,
202
- startTime: Date.now()
202
+ startTime: Date.now(),
203
+ failOnDiff: options.failOnDiff || false
203
204
  };
204
205
  writeFileSync(join(vizzlyDir, 'server.json'), JSON.stringify(serverInfo, null, 2));
205
206