@vizzly-testing/cli 0.35.2 → 0.35.3-beta.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 (43) hide show
  1. package/README.md +8 -5
  2. package/dist/api/client.js +20 -5
  3. package/dist/api/core.js +17 -3
  4. package/dist/api/endpoints.js +24 -3
  5. package/dist/api/index.js +1 -1
  6. package/dist/cli.js +25 -65
  7. package/dist/commands/baselines.js +0 -7
  8. package/dist/commands/comparisons.js +52 -45
  9. package/dist/commands/config-cmd.js +0 -7
  10. package/dist/commands/context.js +444 -238
  11. package/dist/commands/doctor.js +0 -8
  12. package/dist/commands/init.js +7 -12
  13. package/dist/commands/login.js +0 -13
  14. package/dist/commands/logout.js +0 -12
  15. package/dist/commands/orgs.js +0 -9
  16. package/dist/commands/run.js +84 -29
  17. package/dist/commands/status.js +276 -59
  18. package/dist/commands/tdd.js +19 -3
  19. package/dist/commands/whoami.js +0 -12
  20. package/dist/context/local-workspace-provider.js +293 -37
  21. package/dist/plugin-loader.js +1 -1
  22. package/dist/reporter/reporter-bundle.css +1 -1
  23. package/dist/reporter/reporter-bundle.iife.js +17 -17
  24. package/dist/server/routers/events.js +4 -4
  25. package/dist/test-runner/operations.js +70 -28
  26. package/dist/types/index.d.ts +4 -2
  27. package/dist/uploader/core.js +6 -6
  28. package/dist/uploader/operations.js +4 -1
  29. package/dist/utils/visual-context-normalizers.js +455 -0
  30. package/package.json +13 -13
  31. package/skills/vizzly/SKILL.md +68 -101
  32. package/skills/vizzly/agents/openai.yaml +2 -2
  33. package/skills/vizzly/references/cli-context.md +79 -27
  34. package/skills/vizzly/references/dynamic-content.md +28 -33
  35. package/skills/vizzly/references/sdks.md +26 -39
  36. package/skills/vizzly/references/setup-ci.md +44 -20
  37. package/dist/config/index.js +0 -13
  38. package/dist/project/index.js +0 -13
  39. package/dist/tdd/index.js +0 -19
  40. package/dist/tdd/services/baseline-downloader.js +0 -151
  41. package/dist/tdd/services/hotspot-service.js +0 -58
  42. package/dist/utils/environment.js +0 -109
  43. package/skills/vizzly/references/public-screenshots.md +0 -44
@@ -264,12 +264,4 @@ export async function doctorCommand(options = {}, globalOptions = {}, deps = {})
264
264
  output.cleanup();
265
265
  if (hasErrors) exit(1);
266
266
  }
267
- }
268
-
269
- /**
270
- * Validate doctor options (no specific validation needed)
271
- * @param {Object} options - Command options
272
- */
273
- export function validateDoctorOptions() {
274
- return []; // No specific validation for now
275
267
  }
@@ -37,7 +37,7 @@ function configureOutput(output, options) {
37
37
  export function getInitConfigPath(cwd = process.cwd()) {
38
38
  return path.join(cwd, 'vizzly.config.js');
39
39
  }
40
- export function getPackagedAgentSkillPath(baseDir = commandDir) {
40
+ function getPackagedAgentSkillPath(baseDir = commandDir) {
41
41
  return path.resolve(baseDir, '..', '..', 'skills', 'vizzly');
42
42
  }
43
43
  export function getProjectAgentSkillPath(cwd = process.cwd()) {
@@ -54,10 +54,10 @@ export async function fileExists(filePath, access = fs.access) {
54
54
  return false;
55
55
  }
56
56
  }
57
- export function getPluginsWithConfig(plugins = []) {
57
+ function getPluginsWithConfig(plugins = []) {
58
58
  return plugins.filter(plugin => plugin.configSchema);
59
59
  }
60
- export function getPluginConfigNames(plugins = []) {
60
+ function getPluginConfigNames(plugins = []) {
61
61
  return getPluginsWithConfig(plugins).map(plugin => plugin.name);
62
62
  }
63
63
  function formatObjectKey(key) {
@@ -115,7 +115,7 @@ export function formatPluginConfig(plugin, output = null) {
115
115
  return '';
116
116
  }
117
117
  }
118
- export function generatePluginConfigs(plugins = [], output = null) {
118
+ function generatePluginConfigs(plugins = [], output = null) {
119
119
  let sections = [];
120
120
  for (let plugin of plugins) {
121
121
  if (plugin.configSchema) {
@@ -212,7 +212,7 @@ async function shouldInstallAgentSkill(options, deps) {
212
212
  }
213
213
  let agentGuidanceStart = '<!-- vizzly-agent-guidance -->';
214
214
  let agentGuidanceEnd = '<!-- /vizzly-agent-guidance -->';
215
- export function createAgentGuidanceContent() {
215
+ function createAgentGuidanceContent() {
216
216
  return `${agentGuidanceStart}
217
217
  ## Visual Testing With Vizzly
218
218
 
@@ -232,7 +232,7 @@ function appendSection(content, section) {
232
232
  }
233
233
  return `${content.replace(/\s*$/, '')}\n\n${section}`;
234
234
  }
235
- export async function upsertProjectAgentGuidance({
235
+ async function upsertProjectAgentGuidance({
236
236
  cwd,
237
237
  access = fs.access,
238
238
  readFile = fs.readFile,
@@ -362,16 +362,11 @@ async function loadInitPlugins(options, deps) {
362
362
  }
363
363
  try {
364
364
  let config = await deps.loadConfig(options.config, {});
365
- return await deps.loadPlugins(options.config, config, null);
365
+ return await deps.loadPlugins(options.config, config);
366
366
  } catch {
367
367
  return [];
368
368
  }
369
369
  }
370
-
371
- // Export factory function for CLI
372
- export function createInitCommand(options) {
373
- return () => init(options);
374
- }
375
370
  export async function init(options = {}, deps = {}) {
376
371
  let resolvedDeps = createInitDeps(deps);
377
372
  configureOutput(resolvedDeps.output, options);
@@ -236,17 +236,4 @@ export async function loginCommand(options = {}, globalOptions = {}, deps = {})
236
236
  exit(1);
237
237
  }
238
238
  }
239
- }
240
-
241
- /**
242
- * Validate login options
243
- * @param {Object} options - Command options
244
- */
245
- export function validateLoginOptions() {
246
- let errors = [];
247
-
248
- // No specific validation needed for login command
249
- // OAuth device flow handles everything via browser
250
-
251
- return errors;
252
239
  }
@@ -70,16 +70,4 @@ export async function logoutCommand(options = {}, globalOptions = {}, deps = {})
70
70
  output.cleanup();
71
71
  exit(1);
72
72
  }
73
- }
74
-
75
- /**
76
- * Validate logout options
77
- * @param {Object} options - Command options
78
- */
79
- export function validateLogoutOptions() {
80
- let errors = [];
81
-
82
- // No specific validation needed for logout command
83
-
84
- return errors;
85
73
  }
@@ -82,13 +82,4 @@ export async function orgsCommand(_options = {}, globalOptions = {}, deps = {})
82
82
  output.cleanup();
83
83
  exit(1);
84
84
  }
85
- }
86
-
87
- /**
88
- * Validate orgs options
89
- * @param {Object} _options - Command options
90
- * @returns {string[]} Validation errors
91
- */
92
- export function validateOrgsOptions(_options = {}) {
93
- return [];
94
85
  }
@@ -44,8 +44,23 @@ export async function resolveBuildDisplayUrl({
44
44
  }
45
45
  return undefined;
46
46
  }
47
- function buildContextCommand(buildId) {
48
- return `vizzly context build ${buildId} --agent`;
47
+
48
+ /**
49
+ * Build the follow-up command for a cloud run.
50
+ *
51
+ * JSON consumers need a self-contained command that returns structured
52
+ * evidence when executed. Human output keeps the existing readable summary.
53
+ *
54
+ * @param {string} buildId - Cloud build ID to inspect.
55
+ * @param {Object} options - Command output options.
56
+ * @param {boolean} [options.structured=false] - Include machine-readable JSON.
57
+ * @returns {string} Executable build context command.
58
+ */
59
+ function buildContextCommand(buildId, {
60
+ structured = false
61
+ } = {}) {
62
+ let jsonFlag = structured ? ' --json' : '';
63
+ return `vizzly context build ${buildId} --agent${jsonFlag} --source cloud`;
49
64
  }
50
65
 
51
66
  /**
@@ -91,6 +106,7 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
91
106
  let startTime = null;
92
107
  let isTddMode = false;
93
108
  let config = null;
109
+ let result = null;
94
110
 
95
111
  // Ensure cleanup on exit
96
112
  let cleanup = async () => {
@@ -254,7 +270,6 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
254
270
  output.info('Starting test execution...');
255
271
  startTime = Date.now();
256
272
  isTddMode = runOptions.tdd || false;
257
- let result;
258
273
  try {
259
274
  result = await runTests({
260
275
  runOptions,
@@ -327,7 +342,9 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
327
342
  commit,
328
343
  message
329
344
  },
330
- contextCommand: result.buildId ? buildContextCommand(result.buildId) : null,
345
+ contextCommand: result.buildId ? buildContextCommand(result.buildId, {
346
+ structured: true
347
+ }) : null,
331
348
  exitCode: 0
332
349
  };
333
350
  output.data(jsonResult);
@@ -396,7 +413,8 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
396
413
  exitCode
397
414
  };
398
415
  } else {
399
- // Setup or other error - VizzlyError.getUserMessage() provides context
416
+ // This should only be reached for failures outside the SDK paths that
417
+ // runTests handles by disabling Vizzly and running the child command.
400
418
  if (globalOptions.json) {
401
419
  let executionTimeMs = Date.now() - (startTime || Date.now());
402
420
  output.data({
@@ -443,9 +461,22 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
443
461
  getTokenContext
444
462
  });
445
463
  let exitCode = buildResult.failedComparisons > 0 ? 1 : 0;
464
+ let comparisons = {};
465
+ if (buildResult.totalComparisons !== undefined) {
466
+ comparisons.total = buildResult.totalComparisons;
467
+ }
468
+ if (buildResult.newComparisons !== undefined) {
469
+ comparisons.new = buildResult.newComparisons;
470
+ }
471
+ if (buildResult.failedComparisons !== undefined) {
472
+ comparisons.changed = buildResult.failedComparisons;
473
+ }
474
+ if (buildResult.identicalComparisons !== undefined) {
475
+ comparisons.identical = buildResult.identicalComparisons;
476
+ }
446
477
  let jsonResult = {
447
478
  buildId: result.buildId,
448
- status: buildResult.failedComparisons > 0 ? 'failed' : 'completed',
479
+ status: buildResult.status,
449
480
  url: displayUrl,
450
481
  screenshotsCaptured: result.screenshotsCaptured || 0,
451
482
  executionTimeMs,
@@ -454,16 +485,20 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
454
485
  commit,
455
486
  message
456
487
  },
457
- comparisons: {
458
- total: buildResult.totalComparisons || 0,
459
- new: buildResult.newComparisons || 0,
460
- changed: buildResult.failedComparisons || 0,
461
- identical: buildResult.identicalComparisons || 0
462
- },
463
- approvalStatus: buildResult.approvalStatus || 'pending',
464
- contextCommand: buildContextCommand(result.buildId),
488
+ contextCommand: buildContextCommand(result.buildId, {
489
+ structured: true
490
+ }),
465
491
  exitCode
466
492
  };
493
+ if (Object.keys(comparisons).length > 0) {
494
+ jsonResult.comparisons = comparisons;
495
+ }
496
+ if (buildResult.approvalStatus !== undefined) {
497
+ jsonResult.approvalStatus = buildResult.approvalStatus;
498
+ }
499
+ if (buildResult.build?.conclusion !== undefined) {
500
+ jsonResult.conclusion = buildResult.build.conclusion;
501
+ }
467
502
  output.data(jsonResult);
468
503
  output.cleanup();
469
504
  return {
@@ -492,28 +527,48 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
492
527
  } catch (error) {
493
528
  output.stopSpinner();
494
529
 
495
- // Don't fail CI for Vizzly infrastructure issues (5xx errors)
496
- let status = error.context?.status;
497
- if (status >= 500) {
498
- if (globalOptions.json) {
499
- output.data({
500
- buildId: null,
501
- status: 'skipped',
502
- message: 'Vizzly API unavailable - visual tests skipped',
503
- executionTimeMs: Date.now() - (startTime || Date.now())
530
+ // Once the user's tests have passed, no Vizzly-side error can change the
531
+ // test result. Keep that outcome while reporting that the API-backed build
532
+ // status is unavailable instead of inventing a terminal build state.
533
+ if (result) {
534
+ let apiBuild = error.context?.build;
535
+ let buildStatus = apiBuild?.status ?? null;
536
+ let exitCode = buildStatus === 'failed' ? 1 : 0;
537
+ let errorDetails = {
538
+ code: error.code || 'VIZZLY_STATUS_UNAVAILABLE',
539
+ message: error.getUserMessage ? error.getUserMessage() : error.message
540
+ };
541
+ let unavailableResult = {
542
+ buildId: result.buildId || null,
543
+ status: buildStatus,
544
+ testsPassed: true,
545
+ error: errorDetails,
546
+ executionTimeMs: Date.now() - (startTime || Date.now()),
547
+ exitCode
548
+ };
549
+ if (apiBuild?.conclusion !== undefined) {
550
+ unavailableResult.conclusion = apiBuild.conclusion;
551
+ }
552
+ if (result.buildId) {
553
+ unavailableResult.contextCommand = buildContextCommand(result.buildId, {
554
+ structured: true
504
555
  });
556
+ }
557
+ if (globalOptions.json) {
558
+ output.data(unavailableResult);
559
+ } else if (buildStatus === 'failed') {
560
+ output.error(errorDetails.message);
505
561
  } else {
506
- output.warn('Vizzly API unavailable - visual tests skipped. Your tests still ran.');
562
+ output.warn(`Tests passed, but Vizzly could not confirm the build status: ${errorDetails.message}`);
507
563
  }
508
564
  output.cleanup();
509
- output.debug('api', 'API error details:', {
565
+ output.debug('run', 'Vizzly SDK error details', {
510
566
  error: error.message
511
567
  });
512
568
  return {
513
- success: true,
514
- result: {
515
- skipped: true
516
- }
569
+ success: exitCode === 0,
570
+ exitCode,
571
+ result: unavailableResult
517
572
  };
518
573
  }
519
574