@vizzly-testing/cli 0.5.0 → 0.7.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 +55 -9
- package/dist/cli.js +15 -2
- package/dist/commands/finalize.js +72 -0
- package/dist/commands/run.js +59 -19
- package/dist/commands/tdd.js +6 -13
- package/dist/commands/upload.js +1 -0
- package/dist/server/handlers/tdd-handler.js +82 -8
- package/dist/services/api-service.js +14 -0
- package/dist/services/html-report-generator.js +377 -0
- package/dist/services/report-generator/report.css +355 -0
- package/dist/services/report-generator/viewer.js +100 -0
- package/dist/services/server-manager.js +3 -2
- package/dist/services/tdd-service.js +436 -66
- package/dist/services/test-runner.js +56 -28
- package/dist/services/uploader.js +3 -2
- package/dist/types/commands/finalize.d.ts +13 -0
- package/dist/types/server/handlers/tdd-handler.d.ts +18 -1
- package/dist/types/services/api-service.d.ts +6 -0
- package/dist/types/services/html-report-generator.d.ts +52 -0
- package/dist/types/services/report-generator/viewer.d.ts +0 -0
- package/dist/types/services/server-manager.d.ts +19 -1
- package/dist/types/services/tdd-service.d.ts +24 -3
- package/dist/types/services/uploader.d.ts +2 -1
- package/dist/types/utils/config-loader.d.ts +3 -0
- package/dist/types/utils/environment-config.d.ts +5 -0
- package/dist/types/utils/security.d.ts +29 -0
- package/dist/utils/config-loader.js +11 -1
- package/dist/utils/environment-config.js +9 -0
- package/dist/utils/security.js +154 -0
- package/docs/api-reference.md +27 -0
- package/docs/tdd-mode.md +58 -12
- package/docs/test-integration.md +69 -0
- package/package.json +3 -2
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for path sanitization and validation
|
|
3
|
+
* Protects against path traversal attacks and ensures safe file operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { resolve, normalize, isAbsolute, join } from 'path';
|
|
7
|
+
import { createServiceLogger } from './logger-factory.js';
|
|
8
|
+
const logger = createServiceLogger('SECURITY');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sanitizes a screenshot name to prevent path traversal and ensure safe file naming
|
|
12
|
+
* @param {string} name - Original screenshot name
|
|
13
|
+
* @param {number} maxLength - Maximum allowed length (default: 255)
|
|
14
|
+
* @returns {string} Sanitized screenshot name
|
|
15
|
+
*/
|
|
16
|
+
export function sanitizeScreenshotName(name, maxLength = 255) {
|
|
17
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
18
|
+
throw new Error('Screenshot name must be a non-empty string');
|
|
19
|
+
}
|
|
20
|
+
if (name.length > maxLength) {
|
|
21
|
+
throw new Error(`Screenshot name exceeds maximum length of ${maxLength} characters`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Block directory traversal patterns
|
|
25
|
+
if (name.includes('..') || name.includes('/') || name.includes('\\')) {
|
|
26
|
+
throw new Error('Screenshot name contains invalid path characters');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Block absolute paths
|
|
30
|
+
if (isAbsolute(name)) {
|
|
31
|
+
throw new Error('Screenshot name cannot be an absolute path');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Allow only safe characters: alphanumeric, hyphens, underscores, and dots
|
|
35
|
+
// Replace other characters with underscores
|
|
36
|
+
let sanitized = name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
37
|
+
|
|
38
|
+
// Prevent names that start with dots (hidden files)
|
|
39
|
+
if (sanitized.startsWith('.')) {
|
|
40
|
+
sanitized = 'file_' + sanitized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Ensure we have a valid filename
|
|
44
|
+
if (sanitized.length === 0 || sanitized === '.' || sanitized === '..') {
|
|
45
|
+
sanitized = 'unnamed_screenshot';
|
|
46
|
+
}
|
|
47
|
+
return sanitized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validates that a path stays within the allowed working directory bounds
|
|
52
|
+
* @param {string} targetPath - Path to validate
|
|
53
|
+
* @param {string} workingDir - Working directory that serves as the security boundary
|
|
54
|
+
* @returns {string} Resolved and normalized path if valid
|
|
55
|
+
* @throws {Error} If path is invalid or outside bounds
|
|
56
|
+
*/
|
|
57
|
+
export function validatePathSecurity(targetPath, workingDir) {
|
|
58
|
+
if (typeof targetPath !== 'string' || targetPath.length === 0) {
|
|
59
|
+
throw new Error('Path must be a non-empty string');
|
|
60
|
+
}
|
|
61
|
+
if (typeof workingDir !== 'string' || workingDir.length === 0) {
|
|
62
|
+
throw new Error('Working directory must be a non-empty string');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Normalize and resolve both paths
|
|
66
|
+
let resolvedWorkingDir = resolve(normalize(workingDir));
|
|
67
|
+
let resolvedTargetPath = resolve(normalize(targetPath));
|
|
68
|
+
|
|
69
|
+
// Ensure the target path starts with the working directory
|
|
70
|
+
if (!resolvedTargetPath.startsWith(resolvedWorkingDir)) {
|
|
71
|
+
logger.warn(`Path traversal attempt blocked: ${targetPath} (resolved: ${resolvedTargetPath}) is outside working directory: ${resolvedWorkingDir}`);
|
|
72
|
+
throw new Error('Path is outside the allowed working directory');
|
|
73
|
+
}
|
|
74
|
+
return resolvedTargetPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Safely constructs a path within the working directory
|
|
79
|
+
* @param {string} workingDir - Base working directory
|
|
80
|
+
* @param {...string} pathSegments - Path segments to join
|
|
81
|
+
* @returns {string} Safely constructed path
|
|
82
|
+
* @throws {Error} If resulting path would be outside working directory
|
|
83
|
+
*/
|
|
84
|
+
export function safePath(workingDir, ...pathSegments) {
|
|
85
|
+
if (pathSegments.length === 0) {
|
|
86
|
+
return validatePathSecurity(workingDir, workingDir);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Sanitize each path segment
|
|
90
|
+
let sanitizedSegments = pathSegments.map(segment => {
|
|
91
|
+
if (typeof segment !== 'string') {
|
|
92
|
+
throw new Error('Path segment must be a string');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Block directory traversal in segments
|
|
96
|
+
if (segment.includes('..')) {
|
|
97
|
+
throw new Error('Path segment contains directory traversal sequence');
|
|
98
|
+
}
|
|
99
|
+
return segment;
|
|
100
|
+
});
|
|
101
|
+
let targetPath = join(workingDir, ...sanitizedSegments);
|
|
102
|
+
return validatePathSecurity(targetPath, workingDir);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates screenshot properties object for safe values
|
|
107
|
+
* @param {Object} properties - Properties to validate
|
|
108
|
+
* @returns {Object} Validated properties object
|
|
109
|
+
*/
|
|
110
|
+
export function validateScreenshotProperties(properties = {}) {
|
|
111
|
+
if (properties === null || typeof properties !== 'object') {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
let validated = {};
|
|
115
|
+
|
|
116
|
+
// Validate common properties with safe constraints
|
|
117
|
+
if (properties.browser && typeof properties.browser === 'string') {
|
|
118
|
+
try {
|
|
119
|
+
validated.browser = sanitizeScreenshotName(properties.browser, 50);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// Skip invalid browser names, don't include them
|
|
122
|
+
logger.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (properties.viewport && typeof properties.viewport === 'object') {
|
|
126
|
+
let viewport = {};
|
|
127
|
+
if (typeof properties.viewport.width === 'number' && properties.viewport.width > 0 && properties.viewport.width <= 10000) {
|
|
128
|
+
viewport.width = Math.floor(properties.viewport.width);
|
|
129
|
+
}
|
|
130
|
+
if (typeof properties.viewport.height === 'number' && properties.viewport.height > 0 && properties.viewport.height <= 10000) {
|
|
131
|
+
viewport.height = Math.floor(properties.viewport.height);
|
|
132
|
+
}
|
|
133
|
+
if (Object.keys(viewport).length > 0) {
|
|
134
|
+
validated.viewport = viewport;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Allow other safe string properties but sanitize them
|
|
139
|
+
for (let [key, value] of Object.entries(properties)) {
|
|
140
|
+
if (key === 'browser' || key === 'viewport') continue; // Already handled
|
|
141
|
+
|
|
142
|
+
if (typeof key === 'string' && key.length <= 50 && /^[a-zA-Z0-9_-]+$/.test(key)) {
|
|
143
|
+
if (typeof value === 'string' && value.length <= 200) {
|
|
144
|
+
// Store sanitized version of string values
|
|
145
|
+
validated[key] = value.replace(/[<>&"']/g, ''); // Basic HTML entity prevention
|
|
146
|
+
} else if (typeof value === 'number' && !isNaN(value) && isFinite(value)) {
|
|
147
|
+
validated[key] = value;
|
|
148
|
+
} else if (typeof value === 'boolean') {
|
|
149
|
+
validated[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return validated;
|
|
154
|
+
}
|
package/docs/api-reference.md
CHANGED
|
@@ -289,6 +289,7 @@ Upload screenshots from a directory.
|
|
|
289
289
|
- `--token <token>` - API token override
|
|
290
290
|
- `--wait` - Wait for build completion
|
|
291
291
|
- `--upload-all` - Upload all screenshots without SHA deduplication
|
|
292
|
+
- `--parallel-id <id>` - Unique identifier for parallel test execution
|
|
292
293
|
|
|
293
294
|
**Exit Codes:**
|
|
294
295
|
- `0` - Success (all approved or no changes)
|
|
@@ -321,6 +322,9 @@ Run tests with Vizzly integration.
|
|
|
321
322
|
- `--upload-timeout <ms>` - Upload wait timeout in ms (default: from config or 30000)
|
|
322
323
|
- `--upload-all` - Upload all screenshots without SHA deduplication
|
|
323
324
|
|
|
325
|
+
*Parallel Execution:*
|
|
326
|
+
- `--parallel-id <id>` - Unique identifier for parallel test execution
|
|
327
|
+
|
|
324
328
|
*Development & Testing:*
|
|
325
329
|
- `--allow-no-token` - Allow running without API token
|
|
326
330
|
- `--token <token>` - API token override
|
|
@@ -403,6 +407,26 @@ Check build status.
|
|
|
403
407
|
- `1` - Build has changes requiring review
|
|
404
408
|
- `2` - Build failed or error
|
|
405
409
|
|
|
410
|
+
### `vizzly finalize <parallel-id>`
|
|
411
|
+
|
|
412
|
+
Finalize a parallel build after all shards complete.
|
|
413
|
+
|
|
414
|
+
**Arguments:**
|
|
415
|
+
- `<parallel-id>` - Parallel ID to finalize
|
|
416
|
+
|
|
417
|
+
**Description:**
|
|
418
|
+
When using parallel execution with `--parallel-id`, all test shards contribute screenshots to the same shared build. After all shards complete successfully, use this command to finalize the build and trigger comparison processing.
|
|
419
|
+
|
|
420
|
+
**Example:**
|
|
421
|
+
```bash
|
|
422
|
+
# After all parallel shards complete
|
|
423
|
+
vizzly finalize "ci-run-123-attempt-1"
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Exit Codes:**
|
|
427
|
+
- `0` - Build finalized successfully
|
|
428
|
+
- `1` - Finalization failed or error
|
|
429
|
+
|
|
406
430
|
### `vizzly doctor`
|
|
407
431
|
|
|
408
432
|
Run environment diagnostics.
|
|
@@ -486,6 +510,9 @@ Configuration loaded via cosmiconfig in this order:
|
|
|
486
510
|
- `VIZZLY_API_URL` - API base URL override
|
|
487
511
|
- `VIZZLY_LOG_LEVEL` - Logger level (`debug`, `info`, `warn`, `error`)
|
|
488
512
|
|
|
513
|
+
**Parallel Builds:**
|
|
514
|
+
- `VIZZLY_PARALLEL_ID` - Unique identifier for parallel test execution
|
|
515
|
+
|
|
489
516
|
**Git Information Override (CI/CD Enhancement):**
|
|
490
517
|
- `VIZZLY_COMMIT_SHA` - Override detected commit SHA
|
|
491
518
|
- `VIZZLY_COMMIT_MESSAGE` - Override detected commit message
|
package/docs/tdd-mode.md
CHANGED
|
@@ -8,7 +8,7 @@ TDD Mode transforms your visual testing workflow by:
|
|
|
8
8
|
|
|
9
9
|
- **Local comparison** - Compares screenshots on your machine using `odiff`
|
|
10
10
|
- **Fast feedback** - No network uploads during development
|
|
11
|
-
- **Immediate results** - Tests fail instantly when visual differences are detected
|
|
11
|
+
- **Immediate results** - Tests fail instantly when visual differences are detected
|
|
12
12
|
- **Auto-baseline creation** - Creates baselines locally when none exist
|
|
13
13
|
- **No token required** - Works entirely offline for local development
|
|
14
14
|
|
|
@@ -43,7 +43,7 @@ npx vizzly tdd "npm test"
|
|
|
43
43
|
🐻 **Comparison behavior:**
|
|
44
44
|
- Compares new screenshots against local baselines
|
|
45
45
|
- **Tests fail immediately** when visual differences detected
|
|
46
|
-
-
|
|
46
|
+
- Generates interactive HTML report for visual analysis
|
|
47
47
|
- Creates diff images in `.vizzly/diffs/`
|
|
48
48
|
|
|
49
49
|
### 3. Accept Changes (Update Baseline)
|
|
@@ -56,7 +56,7 @@ npx vizzly tdd "npm test" --set-baseline
|
|
|
56
56
|
|
|
57
57
|
🐻 **Baseline update behavior:**
|
|
58
58
|
- Skips all comparisons
|
|
59
|
-
- Sets current screenshots as new baselines
|
|
59
|
+
- Sets current screenshots as new baselines
|
|
60
60
|
- All tests pass (baseline accepted)
|
|
61
61
|
- Future runs use updated baselines
|
|
62
62
|
|
|
@@ -73,7 +73,7 @@ npx vizzly run "npm test" --wait
|
|
|
73
73
|
TDD Mode creates a local development environment:
|
|
74
74
|
|
|
75
75
|
1. **Downloads baselines** - Gets approved screenshots from Vizzly
|
|
76
|
-
2. **Runs tests** - Executes your test suite normally
|
|
76
|
+
2. **Runs tests** - Executes your test suite normally
|
|
77
77
|
3. **Captures screenshots** - Collects new screenshots via `vizzlyScreenshot()`
|
|
78
78
|
4. **Compares locally** - Uses `odiff` for pixel-perfect comparison
|
|
79
79
|
5. **Fails immediately** - Tests fail when differences exceed threshold
|
|
@@ -89,9 +89,11 @@ TDD Mode creates a `.vizzly/` directory:
|
|
|
89
89
|
│ ├── homepage.png
|
|
90
90
|
│ ├── dashboard.png
|
|
91
91
|
│ └── metadata.json # Baseline build information
|
|
92
|
-
├── current/ # Current test screenshots
|
|
92
|
+
├── current/ # Current test screenshots
|
|
93
93
|
│ ├── homepage.png
|
|
94
94
|
│ └── dashboard.png
|
|
95
|
+
├── report/ # Interactive HTML report
|
|
96
|
+
│ └── index.html # Visual comparison interface
|
|
95
97
|
└── diffs/ # Visual diff images (when differences found)
|
|
96
98
|
├── homepage.png # Red overlay showing differences
|
|
97
99
|
└── dashboard.png
|
|
@@ -99,6 +101,50 @@ TDD Mode creates a `.vizzly/` directory:
|
|
|
99
101
|
|
|
100
102
|
**Important**: Add `.vizzly/` to your `.gitignore` file as it contains local development artifacts.
|
|
101
103
|
|
|
104
|
+
## Interactive HTML Report
|
|
105
|
+
|
|
106
|
+
Each TDD run automatically generates a comprehensive HTML report at `.vizzly/report/index.html`. This report provides advanced visual comparison tools to analyze differences:
|
|
107
|
+
|
|
108
|
+
### 🐻 **Viewing Modes**
|
|
109
|
+
|
|
110
|
+
**Overlay Mode** (Default)
|
|
111
|
+
- Shows current screenshot as base layer
|
|
112
|
+
- Click to toggle diff overlay on/off
|
|
113
|
+
- Perfect for spotting subtle changes
|
|
114
|
+
|
|
115
|
+
**Side-by-Side Mode**
|
|
116
|
+
- Displays baseline and current screenshots horizontally
|
|
117
|
+
- Easy to compare layout and content changes
|
|
118
|
+
- Great for reviewing larger modifications
|
|
119
|
+
|
|
120
|
+
**Onion Skin Mode**
|
|
121
|
+
- Drag across image to reveal baseline underneath
|
|
122
|
+
- Interactive reveal lets you control comparison area
|
|
123
|
+
- Ideal for precise change inspection
|
|
124
|
+
|
|
125
|
+
**Toggle Mode**
|
|
126
|
+
- Click image to switch between baseline and current
|
|
127
|
+
- Quick back-and-forth comparison
|
|
128
|
+
- Simple way to see before/after
|
|
129
|
+
|
|
130
|
+
### 🐻 **Report Features**
|
|
131
|
+
|
|
132
|
+
- **Dark Theme** - Easy on the eyes during long debugging sessions
|
|
133
|
+
- **Mobile Responsive** - Works on any screen size
|
|
134
|
+
- **Clickable File Paths** - Click from terminal to open instantly
|
|
135
|
+
- **Clean Status Display** - Shows "Visual differences detected" instead of technical metrics
|
|
136
|
+
- **Test Summary** - Total, passed, failed counts and pass rate
|
|
137
|
+
|
|
138
|
+
### 🐻 **Opening the Report**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Report path is shown after each run
|
|
142
|
+
🐻 View detailed report: file:///path/to/.vizzly/report/index.html
|
|
143
|
+
|
|
144
|
+
# Click the link in your terminal, or open manually
|
|
145
|
+
open .vizzly/report/index.html # macOS
|
|
146
|
+
```
|
|
147
|
+
|
|
102
148
|
## Command Options
|
|
103
149
|
|
|
104
150
|
### Basic TDD Mode
|
|
@@ -122,7 +168,7 @@ vizzly tdd "npm test" --set-baseline
|
|
|
122
168
|
VIZZLY_TOKEN=your-token vizzly tdd "npm test" --baseline-build build-abc123
|
|
123
169
|
```
|
|
124
170
|
|
|
125
|
-
**`--baseline-comparison <id>`** - Use specific comparison as baseline
|
|
171
|
+
**`--baseline-comparison <id>`** - Use specific comparison as baseline
|
|
126
172
|
```bash
|
|
127
173
|
VIZZLY_TOKEN=your-token vizzly tdd "npm test" --baseline-comparison comparison-xyz789
|
|
128
174
|
```
|
|
@@ -272,14 +318,14 @@ jobs:
|
|
|
272
318
|
- uses: actions/checkout@v4
|
|
273
319
|
- uses: actions/setup-node@v4
|
|
274
320
|
- run: npm ci
|
|
275
|
-
|
|
321
|
+
|
|
276
322
|
# Use TDD mode for PR builds (faster, no uploads)
|
|
277
323
|
- name: TDD Visual Tests (PR)
|
|
278
324
|
if: github.event_name == 'pull_request'
|
|
279
325
|
run: npx vizzly tdd "npm test"
|
|
280
326
|
env:
|
|
281
327
|
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
282
|
-
|
|
328
|
+
|
|
283
329
|
# Upload full build for main branch
|
|
284
330
|
- name: Full Visual Tests (main)
|
|
285
331
|
if: github.ref == 'refs/heads/main'
|
|
@@ -295,7 +341,7 @@ jobs:
|
|
|
295
341
|
- **Immediate feedback** - See results in seconds
|
|
296
342
|
- **No API rate limits** - Test as often as needed
|
|
297
343
|
|
|
298
|
-
### Development Experience
|
|
344
|
+
### Development Experience
|
|
299
345
|
- **Fast iteration** - Make changes and test immediately
|
|
300
346
|
- **Visual debugging** - See exact pixel differences
|
|
301
347
|
- **Offline capable** - Works without internet (after initial baseline download)
|
|
@@ -354,7 +400,7 @@ npm install odiff-bin
|
|
|
354
400
|
|
|
355
401
|
### Use TDD Mode For
|
|
356
402
|
- **Local development** - Fast iteration on UI changes
|
|
357
|
-
- **Bug fixing** - Verify visual fixes immediately
|
|
403
|
+
- **Bug fixing** - Verify visual fixes immediately
|
|
358
404
|
- **PR validation** - Quick checks without uploading
|
|
359
405
|
- **Debugging** - Understand exactly what changed visually
|
|
360
406
|
|
|
@@ -372,5 +418,5 @@ npm install odiff-bin
|
|
|
372
418
|
## Next Steps
|
|
373
419
|
|
|
374
420
|
- Learn about [Test Integration](./test-integration.md) for screenshot capture
|
|
375
|
-
- Explore [Upload Command](./upload-command.md) for direct uploads
|
|
376
|
-
- Check the [API Reference](./api-reference.md) for programmatic usage
|
|
421
|
+
- Explore [Upload Command](./upload-command.md) for direct uploads
|
|
422
|
+
- Check the [API Reference](./api-reference.md) for programmatic usage
|
package/docs/test-integration.md
CHANGED
|
@@ -209,6 +209,15 @@ vizzly run "npm test" --upload-all
|
|
|
209
209
|
vizzly run "npm test" --threshold 0.02
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
### Parallel Execution
|
|
213
|
+
|
|
214
|
+
**`--parallel-id <id>`** - Unique identifier for parallel test execution
|
|
215
|
+
```bash
|
|
216
|
+
vizzly run "npm test" --parallel-id "ci-run-123"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
When using parallel execution, multiple test runners can contribute screenshots to the same build. This is particularly useful for CI/CD pipelines with parallel jobs.
|
|
220
|
+
|
|
212
221
|
### Development Options
|
|
213
222
|
|
|
214
223
|
For TDD mode, use the dedicated `vizzly tdd` command. See [TDD Mode Guide](./tdd-mode.md) for details.
|
|
@@ -283,6 +292,43 @@ jobs:
|
|
|
283
292
|
|
|
284
293
|
**Enhanced Git Information:** The `VIZZLY_*` environment variables ensure accurate git metadata is captured in your builds, avoiding issues with merge commits that can occur in CI environments.
|
|
285
294
|
|
|
295
|
+
### Parallel Builds in CI
|
|
296
|
+
|
|
297
|
+
For parallel test execution, use `--parallel-id` to ensure all shards contribute to the same build:
|
|
298
|
+
|
|
299
|
+
```yaml
|
|
300
|
+
# GitHub Actions with parallel matrix
|
|
301
|
+
jobs:
|
|
302
|
+
e2e-tests:
|
|
303
|
+
strategy:
|
|
304
|
+
matrix:
|
|
305
|
+
shard: [1/4, 2/4, 3/4, 4/4]
|
|
306
|
+
steps:
|
|
307
|
+
- name: Run tests with Vizzly
|
|
308
|
+
run: |
|
|
309
|
+
npx vizzly run "npm test -- --shard=${{ matrix.shard }}" \
|
|
310
|
+
--parallel-id="${{ github.run_id }}-${{ github.run_attempt }}"
|
|
311
|
+
env:
|
|
312
|
+
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
313
|
+
|
|
314
|
+
finalize-e2e:
|
|
315
|
+
needs: e2e-tests
|
|
316
|
+
runs-on: ubuntu-latest
|
|
317
|
+
if: always() && needs.e2e-tests.result == 'success'
|
|
318
|
+
steps:
|
|
319
|
+
- name: Finalize parallel build
|
|
320
|
+
run: |
|
|
321
|
+
npx vizzly finalize "${{ github.run_id }}-${{ github.run_attempt }}"
|
|
322
|
+
env:
|
|
323
|
+
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**How Parallel Builds Work:**
|
|
327
|
+
1. All shards with the same `--parallel-id` contribute to one shared build
|
|
328
|
+
2. First shard creates the build, subsequent shards add screenshots to it
|
|
329
|
+
3. After all shards complete, use `vizzly finalize` to trigger comparison processing
|
|
330
|
+
4. Use GitHub's run ID + attempt for uniqueness across workflow runs
|
|
331
|
+
|
|
286
332
|
### GitLab CI
|
|
287
333
|
|
|
288
334
|
```yaml
|
|
@@ -294,6 +340,29 @@ visual-tests:
|
|
|
294
340
|
- npx vizzly run "npm test" --wait
|
|
295
341
|
variables:
|
|
296
342
|
VIZZLY_TOKEN: $VIZZLY_TOKEN
|
|
343
|
+
|
|
344
|
+
# Parallel execution example
|
|
345
|
+
visual-tests-parallel:
|
|
346
|
+
stage: test
|
|
347
|
+
parallel:
|
|
348
|
+
matrix:
|
|
349
|
+
- SHARD: "1/4"
|
|
350
|
+
- SHARD: "2/4"
|
|
351
|
+
- SHARD: "3/4"
|
|
352
|
+
- SHARD: "4/4"
|
|
353
|
+
script:
|
|
354
|
+
- npm ci
|
|
355
|
+
- npx vizzly run "npm test -- --shard=$SHARD" --parallel-id "$CI_PIPELINE_ID-$CI_JOB_ID"
|
|
356
|
+
variables:
|
|
357
|
+
VIZZLY_TOKEN: $VIZZLY_TOKEN
|
|
358
|
+
|
|
359
|
+
finalize-visual-tests:
|
|
360
|
+
stage: finalize
|
|
361
|
+
needs: ["visual-tests-parallel"]
|
|
362
|
+
script:
|
|
363
|
+
- npx vizzly finalize "$CI_PIPELINE_ID-$CI_JOB_ID"
|
|
364
|
+
variables:
|
|
365
|
+
VIZZLY_TOKEN: $VIZZLY_TOKEN
|
|
297
366
|
```
|
|
298
367
|
|
|
299
368
|
## Advanced Usage
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Visual review platform for UI developers and designers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"visual-testing",
|
|
@@ -53,9 +53,10 @@
|
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
55
|
"start": "node src/index.js",
|
|
56
|
-
"build": "npm run clean && npm run compile && npm run types",
|
|
56
|
+
"build": "npm run clean && npm run compile && npm run copy-assets && npm run types",
|
|
57
57
|
"clean": "rimraf dist",
|
|
58
58
|
"compile": "babel src --out-dir dist --ignore '**/*.test.js'",
|
|
59
|
+
"copy-assets": "cp src/services/report-generator/report.css dist/services/report-generator/",
|
|
59
60
|
"types": "tsc --emitDeclarationOnly --outDir dist/types",
|
|
60
61
|
"prepublishOnly": "npm test && npm run build",
|
|
61
62
|
"test": "vitest run",
|