@vizzly-testing/cli 0.3.2 → 0.5.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/README.md +50 -28
- package/dist/cli.js +34 -30
- package/dist/client/index.js +1 -1
- package/dist/commands/run.js +38 -11
- package/dist/commands/tdd.js +30 -18
- package/dist/commands/upload.js +56 -5
- package/dist/server/handlers/api-handler.js +83 -0
- package/dist/server/handlers/tdd-handler.js +138 -0
- package/dist/server/http-server.js +132 -0
- package/dist/services/api-service.js +22 -2
- package/dist/services/server-manager.js +45 -29
- package/dist/services/test-runner.js +66 -69
- package/dist/services/uploader.js +11 -4
- package/dist/types/commands/run.d.ts +4 -1
- package/dist/types/commands/tdd.d.ts +5 -1
- package/dist/types/sdk/index.d.ts +6 -6
- package/dist/types/server/handlers/api-handler.d.ts +49 -0
- package/dist/types/server/handlers/tdd-handler.d.ts +85 -0
- package/dist/types/server/http-server.d.ts +5 -0
- package/dist/types/services/api-service.d.ts +1 -0
- package/dist/types/services/server-manager.d.ts +148 -3
- package/dist/types/services/test-runner.d.ts +1 -0
- package/dist/types/services/uploader.d.ts +2 -1
- package/dist/types/utils/ci-env.d.ts +55 -0
- package/dist/types/utils/config-helpers.d.ts +1 -1
- package/dist/types/utils/console-ui.d.ts +1 -1
- package/dist/types/utils/git.d.ts +12 -0
- package/dist/utils/ci-env.js +293 -0
- package/dist/utils/console-ui.js +4 -14
- package/dist/utils/git.js +38 -0
- package/docs/api-reference.md +17 -5
- package/docs/getting-started.md +1 -1
- package/docs/tdd-mode.md +9 -9
- package/docs/test-integration.md +9 -17
- package/docs/upload-command.md +7 -0
- package/package.json +4 -5
- package/dist/screenshot-wrapper.js +0 -68
- package/dist/server/index.js +0 -522
- package/dist/services/service-utils.js +0 -171
- package/dist/types/index.js +0 -153
- package/dist/types/screenshot-wrapper.d.ts +0 -27
- package/dist/types/server/index.d.ts +0 -38
- package/dist/types/services/service-utils.d.ts +0 -45
- package/dist/types/types/index.d.ts +0 -373
- package/dist/types/utils/diagnostics.d.ts +0 -69
- package/dist/types/utils/error-messages.d.ts +0 -42
- package/dist/types/utils/framework-detector.d.ts +0 -5
- package/dist/types/utils/help.d.ts +0 -11
- package/dist/types/utils/image-comparison.d.ts +0 -42
- package/dist/types/utils/package.d.ts +0 -1
- package/dist/types/utils/project-detection.d.ts +0 -19
- package/dist/types/utils/ui-helpers.d.ts +0 -23
- package/dist/utils/diagnostics.js +0 -184
- package/dist/utils/error-messages.js +0 -34
- package/dist/utils/framework-detector.js +0 -40
- package/dist/utils/help.js +0 -66
- package/dist/utils/image-comparison.js +0 -172
- package/dist/utils/package.js +0 -9
- package/dist/utils/project-detection.js +0 -145
- package/dist/utils/ui-helpers.js +0 -86
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ vizzly upload ./screenshots --build-name "Release v1.2.3"
|
|
|
55
55
|
vizzly run "npm test"
|
|
56
56
|
|
|
57
57
|
# Use TDD mode for local development
|
|
58
|
-
vizzly
|
|
58
|
+
vizzly tdd "npm test"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
### In your test code
|
|
@@ -81,15 +81,14 @@ await vizzlyScreenshot('homepage', screenshot, {
|
|
|
81
81
|
```bash
|
|
82
82
|
vizzly upload <directory> # Upload screenshots from directory
|
|
83
83
|
vizzly upload ./screenshots --wait # Wait for processing
|
|
84
|
+
vizzly upload ./screenshots --upload-all # Upload all without deduplication
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
### Run Tests with Integration
|
|
87
88
|
```bash
|
|
88
89
|
vizzly run "npm test" # Run with Vizzly integration
|
|
89
|
-
vizzly run "npm test" --tdd # Local TDD mode
|
|
90
90
|
vizzly run "pytest" --port 3002 # Custom port
|
|
91
91
|
vizzly run "npm test" --wait # Wait for build completion
|
|
92
|
-
vizzly run "npm test" --eager # Create build immediately
|
|
93
92
|
vizzly run "npm test" --allow-no-token # Run without API token
|
|
94
93
|
```
|
|
95
94
|
|
|
@@ -108,19 +107,36 @@ vizzly run "npm test" --allow-no-token # Run without API token
|
|
|
108
107
|
|
|
109
108
|
**Processing Options:**
|
|
110
109
|
- `--wait` - Wait for build completion and exit with appropriate code
|
|
111
|
-
- `--eager` - Create build immediately (default: lazy creation)
|
|
112
110
|
- `--threshold <number>` - Comparison threshold (0-1, default: 0.01)
|
|
113
|
-
- `--batch-size <n>` - Upload batch size used with `--wait`
|
|
114
111
|
- `--upload-timeout <ms>` - Upload wait timeout in ms
|
|
112
|
+
- `--upload-all` - Upload all screenshots without SHA deduplication
|
|
115
113
|
|
|
116
114
|
**Development & Testing:**
|
|
117
|
-
- `--tdd` - Enable TDD mode with local comparisons
|
|
118
115
|
- `--allow-no-token` - Allow running without API token (useful for local development)
|
|
119
116
|
- `--token <token>` - API token override
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
## TDD Command
|
|
119
|
+
|
|
120
|
+
For local visual testing with immediate feedback, use the dedicated `tdd` command:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# First run - creates local baselines
|
|
124
|
+
vizzly tdd "npm test"
|
|
125
|
+
|
|
126
|
+
# Make changes and test - fails if visual differences detected
|
|
127
|
+
vizzly tdd "npm test"
|
|
128
|
+
|
|
129
|
+
# Accept changes as new baseline
|
|
130
|
+
vizzly tdd "npm test" --set-baseline
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**TDD Command Options:**
|
|
134
|
+
- `--set-baseline` - Accept current screenshots as new baseline
|
|
135
|
+
- `--baseline-build <id>` - Use specific build as baseline (requires API token)
|
|
136
|
+
- `--baseline-comparison <id>` - Use specific comparison as baseline (requires API token)
|
|
137
|
+
- `--threshold <number>` - Comparison threshold (0-1, default: 0.1)
|
|
138
|
+
- `--port <port>` - Server port (default: 47392)
|
|
139
|
+
- `--timeout <ms>` - Server timeout (default: 30000)
|
|
124
140
|
|
|
125
141
|
### Setup and Status Commands
|
|
126
142
|
```bash
|
|
@@ -176,25 +192,7 @@ VIZZLY_TOKEN=your-token vizzly doctor --api
|
|
|
176
192
|
vizzly doctor --json
|
|
177
193
|
```
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
TDD mode enables fast local development by comparing screenshots locally without uploading to Vizzly:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
# First run - creates local baselines (no token needed)
|
|
185
|
-
npx vizzly tdd "npm test"
|
|
186
|
-
|
|
187
|
-
# Make changes and test - fails if visual differences detected
|
|
188
|
-
npx vizzly tdd "npm test"
|
|
189
|
-
|
|
190
|
-
# Accept changes as new baseline
|
|
191
|
-
npx vizzly tdd "npm test" --set-baseline
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
- **🐻 Auto-baseline creation**: Creates baselines locally when none exist
|
|
195
|
-
- **🐻 No token required**: Works entirely offline for local development
|
|
196
|
-
- **🐻 Tests fail on differences**: Immediate feedback when visuals change
|
|
197
|
-
- **🐻 Accept changes**: Use `--set-baseline` to update baselines
|
|
195
|
+
The dedicated `tdd` command provides fast local development with immediate visual feedback. See the [TDD Mode Guide](./docs/tdd-mode.md) for complete details on local visual testing.
|
|
198
196
|
|
|
199
197
|
## Configuration
|
|
200
198
|
|
|
@@ -279,6 +277,10 @@ For CI/CD pipelines, use the `--wait` flag to wait for visual comparison results
|
|
|
279
277
|
run: npx vizzly run "npm test" --wait
|
|
280
278
|
env:
|
|
281
279
|
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
280
|
+
# Optional: Provide correct git information from GitHub context
|
|
281
|
+
VIZZLY_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
282
|
+
VIZZLY_COMMIT_SHA: ${{ github.event.head_commit.id }}
|
|
283
|
+
VIZZLY_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
282
284
|
```
|
|
283
285
|
|
|
284
286
|
### GitLab CI
|
|
@@ -321,10 +323,30 @@ Check if Vizzly is enabled in the current environment.
|
|
|
321
323
|
|
|
322
324
|
## Environment Variables
|
|
323
325
|
|
|
326
|
+
### Core Configuration
|
|
324
327
|
- `VIZZLY_TOKEN`: API authentication token. Example: `export VIZZLY_TOKEN=your-token`.
|
|
325
328
|
- `VIZZLY_API_URL`: Override API base URL. Default: `https://vizzly.dev`.
|
|
326
329
|
- `VIZZLY_LOG_LEVEL`: Logger level. One of `debug`, `info`, `warn`, `error`. Example: `export VIZZLY_LOG_LEVEL=debug`.
|
|
327
330
|
|
|
331
|
+
### Git Information Override
|
|
332
|
+
For enhanced CI/CD integration, you can override git detection with these environment variables:
|
|
333
|
+
|
|
334
|
+
- `VIZZLY_COMMIT_SHA`: Override detected commit SHA. Useful in CI environments.
|
|
335
|
+
- `VIZZLY_COMMIT_MESSAGE`: Override detected commit message. Useful in CI environments.
|
|
336
|
+
- `VIZZLY_BRANCH`: Override detected branch name. Useful in CI environments.
|
|
337
|
+
- `VIZZLY_PR_NUMBER`: Override detected pull request number. Useful for PR-specific builds.
|
|
338
|
+
|
|
339
|
+
**Example for GitHub Actions:**
|
|
340
|
+
```yaml
|
|
341
|
+
env:
|
|
342
|
+
VIZZLY_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
343
|
+
VIZZLY_COMMIT_SHA: ${{ github.event.head_commit.id }}
|
|
344
|
+
VIZZLY_BRANCH: ${{ github.head_ref || github.ref_name }}
|
|
345
|
+
VIZZLY_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
These variables take highest priority over both CLI arguments and automatic git detection.
|
|
349
|
+
|
|
328
350
|
## Contributing
|
|
329
351
|
|
|
330
352
|
We welcome contributions! Whether you're fixing bugs, adding features, or improving documentation, your help makes Vizzly better for everyone.
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ program.command('init').description('Initialize Vizzly in your project').option(
|
|
|
16
16
|
...options
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
|
-
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory or file').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', v => parseInt(v, 10)).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', v => parseInt(v, 10)).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').action(async (path, options) => {
|
|
19
|
+
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory or file').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', v => parseInt(v, 10)).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', v => parseInt(v, 10)).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--upload-all', 'Upload all screenshots without SHA deduplication').action(async (path, options) => {
|
|
20
20
|
const globalOptions = program.opts();
|
|
21
21
|
|
|
22
22
|
// Validate options
|
|
@@ -38,36 +38,29 @@ program.command('tdd').description('Run tests in TDD mode with local visual comp
|
|
|
38
38
|
validationErrors.forEach(error => console.error(` - ${error}`));
|
|
39
39
|
process.exit(1);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Forward --tdd flag to TDD command (shortcut)
|
|
47
|
-
if (options.tdd) {
|
|
48
|
-
// Forward to tdd command with appropriate options
|
|
49
|
-
const tddOptions = {
|
|
50
|
-
port: options.port,
|
|
51
|
-
branch: options.branch,
|
|
52
|
-
environment: options.environment,
|
|
53
|
-
threshold: options.threshold,
|
|
54
|
-
token: options.token,
|
|
55
|
-
timeout: options.timeout,
|
|
56
|
-
baselineBuild: options.baselineBuild,
|
|
57
|
-
baselineComparison: options.baselineComparison,
|
|
58
|
-
allowNoToken: options.allowNoToken
|
|
59
|
-
};
|
|
41
|
+
const {
|
|
42
|
+
result,
|
|
43
|
+
cleanup
|
|
44
|
+
} = await tddCommand(command, options, globalOptions);
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
// Set up cleanup on process signals
|
|
47
|
+
const handleCleanup = async () => {
|
|
48
|
+
await cleanup();
|
|
49
|
+
};
|
|
50
|
+
process.once('SIGINT', () => {
|
|
51
|
+
handleCleanup().then(() => process.exit(1));
|
|
52
|
+
});
|
|
53
|
+
process.once('SIGTERM', () => {
|
|
54
|
+
handleCleanup().then(() => process.exit(1));
|
|
55
|
+
});
|
|
56
|
+
if (result && !result.success && result.exitCode > 0) {
|
|
57
|
+
await cleanup();
|
|
58
|
+
process.exit(result.exitCode);
|
|
70
59
|
}
|
|
60
|
+
await cleanup();
|
|
61
|
+
});
|
|
62
|
+
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').action(async (command, options) => {
|
|
63
|
+
const globalOptions = program.opts();
|
|
71
64
|
|
|
72
65
|
// Validate options
|
|
73
66
|
const validationErrors = validateRunOptions(command, options);
|
|
@@ -76,7 +69,18 @@ program.command('run').description('Run tests with Vizzly integration').argument
|
|
|
76
69
|
validationErrors.forEach(error => console.error(` - ${error}`));
|
|
77
70
|
process.exit(1);
|
|
78
71
|
}
|
|
79
|
-
|
|
72
|
+
try {
|
|
73
|
+
const result = await runCommand(command, options, globalOptions);
|
|
74
|
+
if (result && !result.success && result.exitCode > 0) {
|
|
75
|
+
process.exit(result.exitCode);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Command failed:', error.message);
|
|
79
|
+
if (globalOptions.verbose) {
|
|
80
|
+
console.error('Stack trace:', error.stack);
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
80
84
|
});
|
|
81
85
|
program.command('status').description('Check the status of a build').argument('<build-id>', 'Build ID to check status for').action(async (buildId, options) => {
|
|
82
86
|
const globalOptions = program.opts();
|
package/dist/client/index.js
CHANGED
|
@@ -66,7 +66,7 @@ function createSimpleClient(serverUrl) {
|
|
|
66
66
|
buildId: getBuildId(),
|
|
67
67
|
name,
|
|
68
68
|
image: imageBuffer.toString('base64'),
|
|
69
|
-
properties: options
|
|
69
|
+
properties: options,
|
|
70
70
|
threshold: options.threshold || 0,
|
|
71
71
|
variant: options.variant,
|
|
72
72
|
fullPage: options.fullPage || false
|
package/dist/commands/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
2
|
import { ConsoleUI } from '../utils/console-ui.js';
|
|
3
3
|
import { createServiceContainer } from '../container/index.js';
|
|
4
|
-
import { detectBranch, detectCommit,
|
|
4
|
+
import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Run command implementation
|
|
@@ -55,14 +55,14 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
55
55
|
// Collect git metadata and build info
|
|
56
56
|
const branch = await detectBranch(options.branch);
|
|
57
57
|
const commit = await detectCommit(options.commit);
|
|
58
|
-
const message = options.message || (await
|
|
58
|
+
const message = options.message || (await detectCommitMessage());
|
|
59
59
|
const buildName = await generateBuildNameWithGit(options.buildName);
|
|
60
|
+
const pullRequestNumber = detectPullRequestNumber();
|
|
60
61
|
if (globalOptions.verbose) {
|
|
61
62
|
ui.info('Configuration loaded', {
|
|
62
63
|
testCommand,
|
|
63
64
|
port: config.server.port,
|
|
64
65
|
timeout: config.server.timeout,
|
|
65
|
-
tddMode: options.tdd || false,
|
|
66
66
|
branch,
|
|
67
67
|
commit: commit?.substring(0, 7),
|
|
68
68
|
message,
|
|
@@ -76,9 +76,10 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
76
76
|
ui.startSpinner('Initializing test runner...');
|
|
77
77
|
const configWithVerbose = {
|
|
78
78
|
...config,
|
|
79
|
-
verbose: globalOptions.verbose
|
|
79
|
+
verbose: globalOptions.verbose,
|
|
80
|
+
uploadAll: options.uploadAll || false
|
|
80
81
|
};
|
|
81
|
-
const command =
|
|
82
|
+
const command = 'run';
|
|
82
83
|
const container = await createServiceContainer(configWithVerbose, command);
|
|
83
84
|
testRunner = await container.get('testRunner'); // Assign to outer scope variable
|
|
84
85
|
ui.stopSpinner();
|
|
@@ -112,21 +113,31 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
112
113
|
});
|
|
113
114
|
testRunner.on('build-created', buildInfo => {
|
|
114
115
|
buildUrl = buildInfo.url;
|
|
116
|
+
// Debug: Log build creation details
|
|
117
|
+
if (globalOptions.verbose) {
|
|
118
|
+
ui.info(`Build created: ${buildInfo.buildId} - ${buildInfo.name}`);
|
|
119
|
+
}
|
|
115
120
|
// Use UI for consistent formatting
|
|
116
121
|
if (buildUrl) {
|
|
117
122
|
ui.info(`Vizzly: ${buildUrl}`);
|
|
118
123
|
}
|
|
119
124
|
});
|
|
125
|
+
testRunner.on('build-failed', buildError => {
|
|
126
|
+
ui.error('Failed to create build', buildError);
|
|
127
|
+
});
|
|
120
128
|
testRunner.on('error', error => {
|
|
129
|
+
ui.stopSpinner(); // Stop spinner to ensure error is visible
|
|
121
130
|
ui.error('Test runner error occurred', error, 0); // Don't exit immediately, let runner handle it
|
|
122
131
|
});
|
|
132
|
+
testRunner.on('build-finalize-failed', errorInfo => {
|
|
133
|
+
ui.warning(`Failed to finalize build ${errorInfo.buildId}: ${errorInfo.error}`);
|
|
134
|
+
});
|
|
123
135
|
|
|
124
136
|
// Prepare run options
|
|
125
137
|
const runOptions = {
|
|
126
138
|
testCommand,
|
|
127
139
|
port: config.server.port,
|
|
128
140
|
timeout: config.server.timeout,
|
|
129
|
-
tdd: options.tdd || false,
|
|
130
141
|
buildName,
|
|
131
142
|
branch,
|
|
132
143
|
commit,
|
|
@@ -135,9 +146,9 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
135
146
|
threshold: config.comparison.threshold,
|
|
136
147
|
eager: config.eager || false,
|
|
137
148
|
allowNoToken: config.allowNoToken || false,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
wait: config.wait || options.wait || false,
|
|
150
|
+
uploadAll: options.uploadAll || false,
|
|
151
|
+
pullRequestNumber
|
|
141
152
|
};
|
|
142
153
|
|
|
143
154
|
// Start test run
|
|
@@ -172,13 +183,29 @@ export async function runCommand(testCommand, options = {}, globalOptions = {})
|
|
|
172
183
|
|
|
173
184
|
// Exit with appropriate code based on comparison results
|
|
174
185
|
if (buildResult.failedComparisons > 0) {
|
|
175
|
-
ui.error(`${buildResult.failedComparisons} visual comparisons failed`, {},
|
|
186
|
+
ui.error(`${buildResult.failedComparisons} visual comparisons failed`, {}, 0);
|
|
187
|
+
// Return error status without calling process.exit in tests
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
exitCode: 1
|
|
191
|
+
};
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
}
|
|
179
195
|
ui.cleanup();
|
|
180
196
|
} catch (error) {
|
|
181
|
-
ui.
|
|
197
|
+
ui.stopSpinner(); // Ensure spinner is stopped before showing error
|
|
198
|
+
|
|
199
|
+
// Provide more context about where the error occurred
|
|
200
|
+
let errorContext = 'Test run failed';
|
|
201
|
+
if (error.message && error.message.includes('build')) {
|
|
202
|
+
errorContext = 'Build creation failed';
|
|
203
|
+
} else if (error.message && error.message.includes('screenshot')) {
|
|
204
|
+
errorContext = 'Screenshot processing failed';
|
|
205
|
+
} else if (error.message && error.message.includes('server')) {
|
|
206
|
+
errorContext = 'Server startup failed';
|
|
207
|
+
}
|
|
208
|
+
ui.error(errorContext, error);
|
|
182
209
|
} finally {
|
|
183
210
|
// Remove event listeners to prevent memory leaks
|
|
184
211
|
process.removeListener('SIGINT', sigintHandler);
|
package/dist/commands/tdd.js
CHANGED
|
@@ -8,6 +8,7 @@ import { detectBranch, detectCommit } from '../utils/git.js';
|
|
|
8
8
|
* @param {string} testCommand - Test command to execute
|
|
9
9
|
* @param {Object} options - Command options
|
|
10
10
|
* @param {Object} globalOptions - Global CLI options
|
|
11
|
+
* @returns {Promise<{result: Object, cleanup: Function}>} Result and cleanup function
|
|
11
12
|
*/
|
|
12
13
|
export async function tddCommand(testCommand, options = {}, globalOptions = {}) {
|
|
13
14
|
// Create UI handler
|
|
@@ -17,20 +18,17 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
17
18
|
color: !globalOptions.noColor
|
|
18
19
|
});
|
|
19
20
|
let testRunner = null;
|
|
21
|
+
let isCleanedUp = false;
|
|
20
22
|
|
|
21
|
-
//
|
|
23
|
+
// Create cleanup function that can be called by the caller
|
|
22
24
|
const cleanup = async () => {
|
|
25
|
+
if (isCleanedUp) return;
|
|
26
|
+
isCleanedUp = true;
|
|
23
27
|
ui.cleanup();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const sigintHandler = async () => {
|
|
28
|
-
await cleanup();
|
|
29
|
-
process.exit(1);
|
|
28
|
+
if (testRunner?.cancel) {
|
|
29
|
+
await testRunner.cancel();
|
|
30
|
+
}
|
|
30
31
|
};
|
|
31
|
-
const exitHandler = () => ui.cleanup();
|
|
32
|
-
process.on('SIGINT', sigintHandler);
|
|
33
|
-
process.on('exit', exitHandler);
|
|
34
32
|
try {
|
|
35
33
|
// Load configuration with CLI overrides
|
|
36
34
|
const allOptions = {
|
|
@@ -166,17 +164,31 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
166
164
|
}
|
|
167
165
|
ui.success('TDD test run completed');
|
|
168
166
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
// Determine success based on comparison results
|
|
168
|
+
const hasFailures = result.failed || result.comparisons && result.comparisons.some(c => c.status === 'failed');
|
|
169
|
+
if (hasFailures) {
|
|
170
|
+
ui.error('Visual differences detected in TDD mode', {}, 0);
|
|
172
171
|
}
|
|
173
|
-
|
|
172
|
+
|
|
173
|
+
// Return result and cleanup function
|
|
174
|
+
return {
|
|
175
|
+
result: {
|
|
176
|
+
success: !hasFailures,
|
|
177
|
+
exitCode: hasFailures ? 1 : 0,
|
|
178
|
+
...result
|
|
179
|
+
},
|
|
180
|
+
cleanup
|
|
181
|
+
};
|
|
174
182
|
} catch (error) {
|
|
175
183
|
ui.error('TDD test run failed', error);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
return {
|
|
185
|
+
result: {
|
|
186
|
+
success: false,
|
|
187
|
+
exitCode: 1,
|
|
188
|
+
error: error.message
|
|
189
|
+
},
|
|
190
|
+
cleanup
|
|
191
|
+
};
|
|
180
192
|
}
|
|
181
193
|
}
|
|
182
194
|
|
package/dist/commands/upload.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
2
|
import { ConsoleUI } from '../utils/console-ui.js';
|
|
3
3
|
import { createServiceContainer } from '../container/index.js';
|
|
4
|
-
import { detectBranch, detectCommit,
|
|
4
|
+
import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
|
|
5
5
|
import { ApiService } from '../services/api-service.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -49,6 +49,9 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
49
49
|
|
|
50
50
|
// Note: ConsoleUI handles cleanup via global process listeners
|
|
51
51
|
|
|
52
|
+
let buildId = null;
|
|
53
|
+
let config = null;
|
|
54
|
+
const uploadStartTime = Date.now();
|
|
52
55
|
try {
|
|
53
56
|
ui.info('Starting upload process...');
|
|
54
57
|
|
|
@@ -57,7 +60,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
57
60
|
...globalOptions,
|
|
58
61
|
...options
|
|
59
62
|
};
|
|
60
|
-
|
|
63
|
+
config = await loadConfig(globalOptions.config, allOptions);
|
|
61
64
|
|
|
62
65
|
// Validate API token
|
|
63
66
|
if (!config.apiKey) {
|
|
@@ -68,8 +71,9 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
68
71
|
// Collect git metadata if not provided
|
|
69
72
|
const branch = await detectBranch(options.branch);
|
|
70
73
|
const commit = await detectCommit(options.commit);
|
|
71
|
-
const message = options.message || (await
|
|
74
|
+
const message = options.message || (await detectCommitMessage());
|
|
72
75
|
const buildName = await generateBuildNameWithGit(options.buildName);
|
|
76
|
+
const pullRequestNumber = detectPullRequestNumber();
|
|
73
77
|
ui.info(`Uploading screenshots from: ${screenshotsPath}`);
|
|
74
78
|
if (globalOptions.verbose) {
|
|
75
79
|
ui.info('Configuration loaded', {
|
|
@@ -94,21 +98,54 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
94
98
|
message,
|
|
95
99
|
environment: config.build.environment,
|
|
96
100
|
threshold: config.comparison.threshold,
|
|
101
|
+
uploadAll: options.uploadAll || false,
|
|
97
102
|
metadata: options.metadata ? JSON.parse(options.metadata) : {},
|
|
103
|
+
pullRequestNumber,
|
|
98
104
|
onProgress: progressData => {
|
|
99
105
|
const {
|
|
100
106
|
message: progressMessage,
|
|
101
107
|
current,
|
|
102
108
|
total,
|
|
103
|
-
phase
|
|
109
|
+
phase,
|
|
110
|
+
buildId: progressBuildId
|
|
104
111
|
} = progressData;
|
|
105
|
-
|
|
112
|
+
|
|
113
|
+
// Track buildId when it becomes available
|
|
114
|
+
if (progressBuildId) {
|
|
115
|
+
buildId = progressBuildId;
|
|
116
|
+
}
|
|
117
|
+
let displayMessage = progressMessage;
|
|
118
|
+
if (!displayMessage && phase) {
|
|
119
|
+
if (current !== undefined && total !== undefined) {
|
|
120
|
+
displayMessage = `${phase}: ${current}/${total}`;
|
|
121
|
+
} else {
|
|
122
|
+
displayMessage = phase;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
ui.progress(displayMessage || 'Processing...', current, total);
|
|
106
126
|
}
|
|
107
127
|
};
|
|
108
128
|
|
|
109
129
|
// Start upload
|
|
110
130
|
ui.progress('Starting upload...');
|
|
111
131
|
const result = await uploader.upload(uploadOptions);
|
|
132
|
+
buildId = result.buildId; // Ensure we have the buildId
|
|
133
|
+
|
|
134
|
+
// Mark build as completed
|
|
135
|
+
if (result.buildId) {
|
|
136
|
+
ui.progress('Finalizing build...');
|
|
137
|
+
try {
|
|
138
|
+
const apiService = new ApiService({
|
|
139
|
+
baseUrl: config.apiUrl,
|
|
140
|
+
token: config.apiKey,
|
|
141
|
+
command: 'upload'
|
|
142
|
+
});
|
|
143
|
+
const executionTime = Date.now() - uploadStartTime;
|
|
144
|
+
await apiService.finalizeBuild(result.buildId, true, executionTime);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
ui.warning(`Failed to finalize build: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
112
149
|
ui.success('Upload completed successfully');
|
|
113
150
|
|
|
114
151
|
// Show Vizzly summary
|
|
@@ -138,6 +175,20 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
138
175
|
}
|
|
139
176
|
ui.cleanup();
|
|
140
177
|
} catch (error) {
|
|
178
|
+
// Mark build as failed if we have a buildId and config
|
|
179
|
+
if (buildId && config) {
|
|
180
|
+
try {
|
|
181
|
+
const apiService = new ApiService({
|
|
182
|
+
baseUrl: config.apiUrl,
|
|
183
|
+
token: config.apiKey,
|
|
184
|
+
command: 'upload'
|
|
185
|
+
});
|
|
186
|
+
const executionTime = Date.now() - uploadStartTime;
|
|
187
|
+
await apiService.finalizeBuild(buildId, false, executionTime);
|
|
188
|
+
} catch {
|
|
189
|
+
// Silent fail on cleanup
|
|
190
|
+
}
|
|
191
|
+
}
|
|
141
192
|
ui.error('Upload failed', error);
|
|
142
193
|
}
|
|
143
194
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { createServiceLogger } from '../../utils/logger-factory.js';
|
|
3
|
+
const logger = createServiceLogger('API-HANDLER');
|
|
4
|
+
export const createApiHandler = apiService => {
|
|
5
|
+
let vizzlyDisabled = false;
|
|
6
|
+
let screenshotCount = 0;
|
|
7
|
+
const handleScreenshot = async (buildId, name, image, properties = {}) => {
|
|
8
|
+
if (vizzlyDisabled) {
|
|
9
|
+
logger.debug(`Screenshot captured (Vizzly disabled): ${name}`);
|
|
10
|
+
return {
|
|
11
|
+
statusCode: 200,
|
|
12
|
+
body: {
|
|
13
|
+
success: true,
|
|
14
|
+
disabled: true,
|
|
15
|
+
count: ++screenshotCount,
|
|
16
|
+
message: `Vizzly disabled - ${screenshotCount} screenshots captured but not uploaded`
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (!buildId) {
|
|
21
|
+
return {
|
|
22
|
+
statusCode: 400,
|
|
23
|
+
body: {
|
|
24
|
+
error: 'Build ID is required for screenshot upload'
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (!apiService) {
|
|
29
|
+
return {
|
|
30
|
+
statusCode: 500,
|
|
31
|
+
body: {
|
|
32
|
+
error: 'API service not available'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const imageBuffer = Buffer.from(image, 'base64');
|
|
38
|
+
const result = await apiService.uploadScreenshot(buildId, name, imageBuffer, properties ?? {});
|
|
39
|
+
if (result.skipped) {
|
|
40
|
+
logger.debug(`Screenshot already exists, skipped: ${name}`);
|
|
41
|
+
} else {
|
|
42
|
+
logger.debug(`Screenshot uploaded: ${name}`);
|
|
43
|
+
}
|
|
44
|
+
if (!result.skipped) {
|
|
45
|
+
screenshotCount++;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
statusCode: 200,
|
|
49
|
+
body: {
|
|
50
|
+
success: true,
|
|
51
|
+
name,
|
|
52
|
+
skipped: result.skipped,
|
|
53
|
+
count: screenshotCount
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
} catch (uploadError) {
|
|
57
|
+
logger.error(`❌ Failed to upload screenshot ${name}:`, uploadError.message);
|
|
58
|
+
vizzlyDisabled = true;
|
|
59
|
+
const disabledMessage = '⚠️ Vizzly disabled due to upload error - continuing tests without visual testing';
|
|
60
|
+
logger.warn(disabledMessage);
|
|
61
|
+
return {
|
|
62
|
+
statusCode: 200,
|
|
63
|
+
body: {
|
|
64
|
+
success: true,
|
|
65
|
+
name,
|
|
66
|
+
disabled: true,
|
|
67
|
+
message: disabledMessage
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const getScreenshotCount = () => screenshotCount;
|
|
73
|
+
const cleanup = () => {
|
|
74
|
+
vizzlyDisabled = false;
|
|
75
|
+
screenshotCount = 0;
|
|
76
|
+
logger.debug('API handler cleanup completed');
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
handleScreenshot,
|
|
80
|
+
getScreenshotCount,
|
|
81
|
+
cleanup
|
|
82
|
+
};
|
|
83
|
+
};
|