@vizzly-testing/cli 0.19.2 → 0.20.1-beta.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.
Files changed (76) hide show
  1. package/dist/api/client.js +134 -0
  2. package/dist/api/core.js +341 -0
  3. package/dist/api/endpoints.js +314 -0
  4. package/dist/api/index.js +19 -0
  5. package/dist/auth/client.js +91 -0
  6. package/dist/auth/core.js +176 -0
  7. package/dist/auth/index.js +30 -0
  8. package/dist/auth/operations.js +148 -0
  9. package/dist/cli.js +1 -1
  10. package/dist/client/index.js +0 -1
  11. package/dist/commands/doctor.js +3 -3
  12. package/dist/commands/finalize.js +41 -15
  13. package/dist/commands/login.js +7 -6
  14. package/dist/commands/logout.js +4 -4
  15. package/dist/commands/project.js +5 -4
  16. package/dist/commands/run.js +158 -90
  17. package/dist/commands/status.js +22 -18
  18. package/dist/commands/tdd.js +105 -78
  19. package/dist/commands/upload.js +61 -26
  20. package/dist/commands/whoami.js +4 -4
  21. package/dist/config/core.js +438 -0
  22. package/dist/config/index.js +13 -0
  23. package/dist/config/operations.js +327 -0
  24. package/dist/index.js +1 -1
  25. package/dist/project/core.js +295 -0
  26. package/dist/project/index.js +13 -0
  27. package/dist/project/operations.js +393 -0
  28. package/dist/report-generator/core.js +315 -0
  29. package/dist/report-generator/index.js +8 -0
  30. package/dist/report-generator/operations.js +196 -0
  31. package/dist/reporter/reporter-bundle.iife.js +16 -16
  32. package/dist/screenshot-server/core.js +157 -0
  33. package/dist/screenshot-server/index.js +11 -0
  34. package/dist/screenshot-server/operations.js +183 -0
  35. package/dist/sdk/index.js +3 -2
  36. package/dist/server/handlers/api-handler.js +14 -5
  37. package/dist/server/handlers/tdd-handler.js +80 -48
  38. package/dist/server-manager/core.js +183 -0
  39. package/dist/server-manager/index.js +81 -0
  40. package/dist/server-manager/operations.js +208 -0
  41. package/dist/services/build-manager.js +2 -69
  42. package/dist/services/index.js +21 -48
  43. package/dist/services/screenshot-server.js +40 -74
  44. package/dist/services/server-manager.js +45 -80
  45. package/dist/services/static-report-generator.js +21 -163
  46. package/dist/services/test-runner.js +90 -249
  47. package/dist/services/uploader.js +56 -358
  48. package/dist/tdd/core/hotspot-coverage.js +112 -0
  49. package/dist/tdd/core/signature.js +101 -0
  50. package/dist/tdd/index.js +19 -0
  51. package/dist/tdd/metadata/baseline-metadata.js +103 -0
  52. package/dist/tdd/metadata/hotspot-metadata.js +93 -0
  53. package/dist/tdd/services/baseline-downloader.js +151 -0
  54. package/dist/tdd/services/baseline-manager.js +166 -0
  55. package/dist/tdd/services/comparison-service.js +230 -0
  56. package/dist/tdd/services/hotspot-service.js +71 -0
  57. package/dist/tdd/services/result-service.js +123 -0
  58. package/dist/tdd/tdd-service.js +1081 -0
  59. package/dist/test-runner/core.js +255 -0
  60. package/dist/test-runner/index.js +13 -0
  61. package/dist/test-runner/operations.js +483 -0
  62. package/dist/types/client.d.ts +4 -2
  63. package/dist/types/index.d.ts +5 -0
  64. package/dist/uploader/core.js +396 -0
  65. package/dist/uploader/index.js +11 -0
  66. package/dist/uploader/operations.js +412 -0
  67. package/dist/utils/config-schema.js +8 -3
  68. package/package.json +7 -12
  69. package/dist/services/api-service.js +0 -412
  70. package/dist/services/auth-service.js +0 -226
  71. package/dist/services/config-service.js +0 -369
  72. package/dist/services/html-report-generator.js +0 -455
  73. package/dist/services/project-service.js +0 -326
  74. package/dist/services/report-generator/report.css +0 -411
  75. package/dist/services/report-generator/viewer.js +0 -102
  76. package/dist/services/tdd-service.js +0 -1429
@@ -1,20 +1,41 @@
1
1
  /**
2
2
  * Test Runner Service
3
3
  * Orchestrates the test execution flow
4
+ *
5
+ * This class is a thin wrapper around the functional operations in
6
+ * src/test-runner/. It maintains backwards compatibility while
7
+ * delegating to pure functions for testability.
4
8
  */
5
9
 
6
10
  import { spawn } from 'node:child_process';
7
11
  import { EventEmitter } from 'node:events';
12
+ import { createBuild as createApiBuild, createApiClient, finalizeBuild as finalizeApiBuild, getBuild } from '../api/index.js';
8
13
  import { VizzlyError } from '../errors/vizzly-error.js';
14
+ import { cancelTests, createBuild, finalizeBuild, initializeDaemon, runTests } from '../test-runner/index.js';
9
15
  import * as output from '../utils/output.js';
16
+ import { createBuildObject } from './build-manager.js';
10
17
  export class TestRunner extends EventEmitter {
11
- constructor(config, buildManager, serverManager, tddService) {
18
+ constructor(config, serverManager, options = {}) {
12
19
  super();
13
20
  this.config = config;
14
- this.buildManager = buildManager;
15
21
  this.serverManager = serverManager;
16
- this.tddService = tddService;
17
22
  this.testProcess = null;
23
+
24
+ // Simple buildManager using pure function
25
+ this.buildManager = {
26
+ createBuild: buildOptions => createBuildObject(buildOptions)
27
+ };
28
+
29
+ // Dependency injection for testing - defaults to real implementations
30
+ this.deps = options.deps || {
31
+ spawn,
32
+ createApiClient,
33
+ createApiBuild,
34
+ getBuild,
35
+ finalizeApiBuild,
36
+ output,
37
+ createError: (message, code) => new VizzlyError(message, code)
38
+ };
18
39
  }
19
40
 
20
41
  /**
@@ -22,264 +43,86 @@ export class TestRunner extends EventEmitter {
22
43
  * @param {Object} options - Options for server initialization
23
44
  */
24
45
  async initialize(options) {
25
- const {
26
- tdd,
27
- daemon
28
- } = options;
29
- if (!tdd || !daemon) {
30
- throw new VizzlyError('Initialize method is only for TDD daemon mode', 'INVALID_MODE');
31
- }
32
- try {
33
- // Start server manager for daemon mode
34
- await this.serverManager.start(null, tdd, options.setBaseline);
35
- this.emit('server-ready', {
36
- port: options.port,
37
- mode: 'daemon',
38
- tdd: true
39
- });
40
- } catch (error) {
41
- output.error('Failed to initialize TDD daemon server:', error);
42
- throw error;
43
- }
46
+ await initializeDaemon({
47
+ initOptions: options,
48
+ deps: {
49
+ serverManager: this.serverManager,
50
+ createError: this.deps.createError,
51
+ output: this.deps.output,
52
+ onServerReady: data => this.emit('server-ready', data)
53
+ }
54
+ });
44
55
  }
45
56
  async run(options) {
46
- const {
47
- testCommand,
48
- tdd,
49
- allowNoToken
50
- } = options;
51
- const startTime = Date.now();
52
- let buildId = null;
53
- if (!testCommand) {
54
- throw new VizzlyError('No test command provided', 'TEST_COMMAND_MISSING');
55
- }
56
-
57
- // If no token is allowed and not in TDD mode, just run the command without Vizzly integration
58
- if (allowNoToken && !this.config.apiKey && !tdd) {
59
- const env = {
60
- ...process.env,
61
- VIZZLY_ENABLED: 'false'
62
- };
63
- await this.executeTestCommand(testCommand, env);
64
- return {
65
- testsPassed: 1,
66
- testsFailed: 0,
67
- screenshotsCaptured: 0
68
- };
69
- }
70
- let buildUrl = null;
71
- let screenshotCount = 0;
72
- let testSuccess = false;
73
- let testError = null;
74
- try {
75
- // Create build based on mode
76
- buildId = await this.createBuild(options, tdd);
77
- if (!tdd && buildId) {
78
- // Get build URL for API mode
79
- const apiService = await this.createApiService();
80
- if (apiService) {
81
- try {
82
- const build = await apiService.getBuild(buildId);
83
- buildUrl = build.url;
84
- if (buildUrl) {
85
- output.info(`Build URL: ${buildUrl}`);
86
- }
87
- } catch (error) {
88
- output.debug('build', 'could not retrieve url', {
89
- error: error.message
90
- });
91
- }
92
- }
93
- }
94
-
95
- // Start server with appropriate handler
96
- await this.serverManager.start(buildId, tdd, options.setBaseline);
97
- const env = {
98
- ...process.env,
99
- VIZZLY_SERVER_URL: `http://localhost:${this.config.server.port}`,
100
- VIZZLY_BUILD_ID: buildId,
101
- VIZZLY_ENABLED: 'true',
102
- VIZZLY_SET_BASELINE: options.setBaseline || options['set-baseline'] ? 'true' : 'false'
103
- };
104
- try {
105
- await this.executeTestCommand(testCommand, env);
106
- testSuccess = true;
107
- } catch (error) {
108
- testError = error;
109
- testSuccess = false;
110
- }
111
- } catch (error) {
112
- // Error in setup phase
113
- testError = error;
114
- testSuccess = false;
115
- }
116
-
117
- // Get TDD results before stopping the server (comparisons, screenshot count)
118
- let tddResults = null;
119
- if (tdd) {
120
- try {
121
- tddResults = await this.serverManager.getTddResults();
122
- if (tddResults) {
123
- screenshotCount = tddResults.total || 0;
124
- }
125
- } catch (tddError) {
126
- output.debug('tdd', 'failed to get results', {
127
- error: tddError.message
128
- });
129
- }
130
- }
131
-
132
- // Always finalize the build and stop the server (cleanup phase)
133
- try {
134
- const executionTime = Date.now() - startTime;
135
- if (buildId) {
136
- try {
137
- await this.finalizeBuild(buildId, tdd, testSuccess, executionTime);
138
- } catch (finalizeError) {
139
- output.error('Failed to finalize build:', finalizeError);
140
- }
141
- }
142
-
143
- // In API mode, get actual screenshot count from handler after flush
144
- if (!tdd && this.serverManager.server?.getScreenshotCount) {
145
- screenshotCount = this.serverManager.server.getScreenshotCount(buildId) || 0;
146
- }
147
- } finally {
148
- // Always stop the server, even if finalization fails
149
- try {
150
- await this.serverManager.stop();
151
- } catch (stopError) {
152
- output.error('Failed to stop server:', stopError);
57
+ let result = await runTests({
58
+ runOptions: options,
59
+ config: this.config,
60
+ deps: {
61
+ serverManager: this.serverManager,
62
+ buildManager: this.buildManager,
63
+ spawn: (command, spawnOptions) => {
64
+ let proc = this.deps.spawn(command, spawnOptions);
65
+ this.testProcess = proc;
66
+ return proc;
67
+ },
68
+ createApiClient: this.deps.createApiClient,
69
+ createApiBuild: this.deps.createApiBuild,
70
+ getBuild: this.deps.getBuild,
71
+ finalizeApiBuild: this.deps.finalizeApiBuild,
72
+ createError: this.deps.createError,
73
+ output: this.deps.output,
74
+ onBuildCreated: data => this.emit('build-created', data),
75
+ onServerReady: data => this.emit('server-ready', data),
76
+ onFinalizeFailed: data => this.emit('build-finalize-failed', data)
153
77
  }
154
- }
155
-
156
- // If there was a test error, throw it now (after cleanup)
157
- if (testError) {
158
- output.error('Test run failed:', testError);
159
- throw testError;
160
- }
161
- return {
162
- buildId: buildId,
163
- url: buildUrl,
164
- testsPassed: testSuccess ? 1 : 0,
165
- testsFailed: testSuccess ? 0 : 1,
166
- screenshotsCaptured: screenshotCount,
167
- comparisons: tddResults?.comparisons || null,
168
- failed: (tddResults?.failed || 0) > 0
169
- };
78
+ });
79
+ return result;
170
80
  }
171
81
  async createBuild(options, tdd) {
172
- if (tdd) {
173
- // TDD mode: create local build
174
- const build = await this.buildManager.createBuild(options);
175
- output.debug('build', `created ${build.id.substring(0, 8)}`);
176
- return build.id;
177
- } else {
178
- // API mode: create build via API
179
- const apiService = await this.createApiService();
180
- if (apiService) {
181
- const buildPayload = {
182
- name: options.buildName || `Test Run ${new Date().toISOString()}`,
183
- branch: options.branch || 'main',
184
- environment: options.environment || 'test',
185
- commit_sha: options.commit,
186
- commit_message: options.message,
187
- github_pull_request_number: options.pullRequestNumber,
188
- parallel_id: options.parallelId
189
- };
190
-
191
- // Only include metadata if we have meaningful config to send
192
- if (this.config.comparison?.threshold != null) {
193
- buildPayload.metadata = {
194
- comparison: {
195
- threshold: this.config.comparison.threshold
196
- }
197
- };
198
- }
199
- const buildResult = await apiService.createBuild(buildPayload);
200
- output.debug('build', `created ${buildResult.id}`);
201
-
202
- // Emit build created event
203
- this.emit('build-created', {
204
- buildId: buildResult.id,
205
- url: buildResult.url,
206
- name: buildResult.name || options.buildName
207
- });
208
- return buildResult.id;
209
- } else {
210
- throw new VizzlyError('No API key available for build creation', 'API_KEY_MISSING');
82
+ return createBuild({
83
+ runOptions: options,
84
+ tdd,
85
+ config: this.config,
86
+ deps: {
87
+ buildManager: this.buildManager,
88
+ createApiClient: this.deps.createApiClient,
89
+ createApiBuild: this.deps.createApiBuild,
90
+ output: this.deps.output
211
91
  }
212
- }
213
- }
214
- async createApiService() {
215
- if (!this.config.apiKey) return null;
216
- const {
217
- ApiService
218
- } = await import('./api-service.js');
219
- return new ApiService({
220
- ...this.config,
221
- command: 'run',
222
- uploadAll: this.config.uploadAll
223
92
  });
224
93
  }
225
94
  async finalizeBuild(buildId, isTddMode, success, executionTime) {
226
- if (!buildId) {
227
- return;
228
- }
229
- try {
230
- if (isTddMode) {
231
- // TDD mode: use server handler to finalize (local-only)
232
- if (this.serverManager.server?.finishBuild) {
233
- await this.serverManager.server.finishBuild(buildId);
234
- output.debug('build', `finalized`, {
235
- success
236
- });
237
- }
238
- } else {
239
- // API mode: flush uploads first, then finalize build
240
- if (this.serverManager.server?.finishBuild) {
241
- await this.serverManager.server.finishBuild(buildId);
242
- }
243
-
244
- // Then update build status via API
245
- const apiService = await this.createApiService();
246
- if (apiService) {
247
- await apiService.finalizeBuild(buildId, success, executionTime);
248
- output.debug('build', 'finalized via api', {
249
- success
250
- });
251
- } else {
252
- output.warn(`No API service available to finalize build ${buildId}`);
253
- }
95
+ await finalizeBuild({
96
+ buildId,
97
+ tdd: isTddMode,
98
+ success,
99
+ executionTime,
100
+ config: this.config,
101
+ deps: {
102
+ serverManager: this.serverManager,
103
+ createApiClient: this.deps.createApiClient,
104
+ finalizeApiBuild: this.deps.finalizeApiBuild,
105
+ output: this.deps.output,
106
+ onFinalizeFailed: data => this.emit('build-finalize-failed', data)
254
107
  }
255
- } catch (error) {
256
- // Don't fail the entire run if build finalization fails
257
- output.warn(`Failed to finalize build ${buildId}:`, error.message);
258
- // Emit event for UI handling
259
- this.emit('build-finalize-failed', {
260
- buildId,
261
- error: error.message,
262
- stack: error.stack
263
- });
264
- }
108
+ });
265
109
  }
266
110
  async executeTestCommand(testCommand, env) {
267
111
  return new Promise((resolve, reject) => {
268
- // Use shell to execute the full command string
269
- this.testProcess = spawn(testCommand, {
112
+ let proc = this.deps.spawn(testCommand, {
270
113
  env,
271
114
  stdio: 'inherit',
272
115
  shell: true
273
116
  });
274
- this.testProcess.on('error', error => {
275
- reject(new VizzlyError(`Failed to run test command: ${error.message}`), 'TEST_COMMAND_FAILED');
117
+ this.testProcess = proc;
118
+ proc.on('error', error => {
119
+ reject(this.deps.createError(`Failed to run test command: ${error.message}`, 'TEST_COMMAND_FAILED'));
276
120
  });
277
- this.testProcess.on('exit', (code, signal) => {
278
- // If process was killed by SIGINT, treat as interruption
121
+ proc.on('exit', (code, signal) => {
279
122
  if (signal === 'SIGINT') {
280
- reject(new VizzlyError('Test command was interrupted', 'TEST_COMMAND_INTERRUPTED'));
123
+ reject(this.deps.createError('Test command was interrupted', 'TEST_COMMAND_INTERRUPTED'));
281
124
  } else if (code !== 0) {
282
- reject(new VizzlyError(`Test command exited with code ${code}`, 'TEST_COMMAND_FAILED'));
125
+ reject(this.deps.createError(`Test command exited with code ${code}`, 'TEST_COMMAND_FAILED'));
283
126
  } else {
284
127
  resolve();
285
128
  }
@@ -287,13 +130,11 @@ export class TestRunner extends EventEmitter {
287
130
  });
288
131
  }
289
132
  async cancel() {
290
- if (this.testProcess && !this.testProcess.killed) {
291
- this.testProcess.kill('SIGKILL');
292
- }
293
-
294
- // Stop server manager if running
295
- if (this.serverManager) {
296
- await this.serverManager.stop();
297
- }
133
+ await cancelTests({
134
+ testProcess: this.testProcess,
135
+ deps: {
136
+ serverManager: this.serverManager
137
+ }
138
+ });
298
139
  }
299
140
  }