@vizzly-testing/cli 0.35.3-beta.4 → 0.36.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 +8 -7
- package/dist/api/core.js +8 -8
- package/dist/api/endpoints.js +10 -7
- package/dist/client/index.js +0 -6
- package/dist/commands/run.js +55 -3
- package/dist/reporter/reporter-bundle.iife.js +51 -51
- package/dist/sdk/index.js +0 -10
- package/dist/server/handlers/api-handler.js +105 -195
- package/dist/server/handlers/tdd-handler.js +13 -54
- package/dist/server/routers/screenshot.js +6 -3
- package/dist/test-runner/core.js +7 -1
- package/dist/test-runner/operations.js +22 -7
- package/dist/types/client.d.ts +12 -5
- package/dist/uploader/core.js +4 -3
- package/dist/utils/screenshot-options.js +4 -84
- package/dist/utils/security.js +27 -42
- package/package.json +1 -1
package/dist/types/client.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export function autoDiscoverTddServer(
|
|
|
33
33
|
*/
|
|
34
34
|
export interface ScreenshotResult {
|
|
35
35
|
success: boolean;
|
|
36
|
+
/** Cloud capture accepted locally; upload completion is reported by flush. */
|
|
37
|
+
queued?: boolean;
|
|
36
38
|
status?:
|
|
37
39
|
| 'passed'
|
|
38
40
|
| 'failed'
|
|
@@ -67,16 +69,17 @@ export interface ScreenshotResult {
|
|
|
67
69
|
* @example
|
|
68
70
|
* // With properties and comparison settings
|
|
69
71
|
* await vizzlyScreenshot('checkout-form', screenshot, {
|
|
70
|
-
* properties: { browser: 'chrome',
|
|
72
|
+
* properties: { browser: 'chrome', theme: 'dark', locale: 'en-US' },
|
|
71
73
|
* threshold: 5,
|
|
72
74
|
* minClusterSize: 10,
|
|
73
75
|
* fullPage: true,
|
|
74
76
|
* requestTimeout: 5000
|
|
75
77
|
* });
|
|
76
78
|
*
|
|
77
|
-
* `properties` is the user metadata bag
|
|
78
|
-
*
|
|
79
|
-
*
|
|
79
|
+
* `properties` is the user metadata bag and every key is preserved. Vizzly
|
|
80
|
+
* reads width and height from the captured image. Comparison options are sent
|
|
81
|
+
* separately, while `requestTimeout` stays on the client request and `buildId`
|
|
82
|
+
* only routes the screenshot to a build.
|
|
80
83
|
*/
|
|
81
84
|
export function vizzlyScreenshot(
|
|
82
85
|
name: string,
|
|
@@ -94,11 +97,15 @@ export function vizzlyScreenshot(
|
|
|
94
97
|
): Promise<ScreenshotResult | null>;
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
|
-
*
|
|
100
|
+
* Results returned by vizzlyFlush. Cloud counts cover the whole run, including
|
|
101
|
+
* earlier flushes; success is false if any capture failed. Uploaded and reused
|
|
102
|
+
* counts are separate. TDD mode reports local comparison results instead.
|
|
98
103
|
*/
|
|
99
104
|
export interface FlushResult {
|
|
100
105
|
success: boolean;
|
|
101
106
|
uploaded?: number;
|
|
107
|
+
reused?: number;
|
|
108
|
+
failures?: Array<{ name: string; error: string }>;
|
|
102
109
|
flushed?: boolean;
|
|
103
110
|
total?: number;
|
|
104
111
|
passed?: number;
|
package/dist/uploader/core.js
CHANGED
|
@@ -200,12 +200,13 @@ export function buildFileMetadata(filePath, buffer) {
|
|
|
200
200
|
* @returns {Object} Screenshot format for SHA check
|
|
201
201
|
*/
|
|
202
202
|
export function fileToScreenshotFormat(file) {
|
|
203
|
+
let browser = extractBrowserFromFilename(file.filename);
|
|
203
204
|
return {
|
|
204
205
|
sha256: file.sha256,
|
|
205
206
|
name: file.filename.replace(/\.png$/, ''),
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
properties: browser ? {
|
|
208
|
+
browser
|
|
209
|
+
} : {}
|
|
209
210
|
};
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -1,82 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot option names that are part of the SDK/config contract, not the
|
|
3
|
-
* user's arbitrary metadata bag.
|
|
4
|
-
*/
|
|
5
|
-
export let RESERVED_PROPERTY_OPTIONS = Object.freeze({
|
|
6
|
-
threshold: {
|
|
7
|
-
message: 'Move "threshold" out of properties; properties is only for user metadata.'
|
|
8
|
-
},
|
|
9
|
-
minClusterSize: {
|
|
10
|
-
message: 'Move "minClusterSize" out of properties; properties is only for user metadata.'
|
|
11
|
-
},
|
|
12
|
-
min_cluster_size: {
|
|
13
|
-
message: 'Move "min_cluster_size" out of properties; properties is only for user metadata.'
|
|
14
|
-
},
|
|
15
|
-
fullPage: {
|
|
16
|
-
message: 'Move "fullPage" out of properties; properties is only for user metadata.'
|
|
17
|
-
},
|
|
18
|
-
full_page: {
|
|
19
|
-
message: 'Move "full_page" out of properties; properties is only for user metadata.'
|
|
20
|
-
},
|
|
21
|
-
captureMode: {
|
|
22
|
-
message: 'Move "captureMode" out of properties; properties is only for user metadata.'
|
|
23
|
-
},
|
|
24
|
-
capture_mode: {
|
|
25
|
-
message: 'Move "capture_mode" out of properties; properties is only for user metadata.'
|
|
26
|
-
},
|
|
27
|
-
deviceScaleFactor: {
|
|
28
|
-
message: 'Move "deviceScaleFactor" out of properties; properties is only for user metadata.'
|
|
29
|
-
},
|
|
30
|
-
device_scale_factor: {
|
|
31
|
-
message: 'Move "device_scale_factor" out of properties; properties is only for user metadata.'
|
|
32
|
-
},
|
|
33
|
-
pixelRatio: {
|
|
34
|
-
message: 'Move "pixelRatio" out of properties; properties is only for user metadata.'
|
|
35
|
-
},
|
|
36
|
-
dpr: {
|
|
37
|
-
message: 'Move "dpr" out of properties; properties is only for user metadata.'
|
|
38
|
-
},
|
|
39
|
-
selector: {
|
|
40
|
-
message: 'Move "selector" out of properties; properties is only for user metadata.'
|
|
41
|
-
},
|
|
42
|
-
component: {
|
|
43
|
-
message: 'Move "component" out of properties; properties is only for user metadata.'
|
|
44
|
-
},
|
|
45
|
-
elementSelector: {
|
|
46
|
-
message: 'Move "elementSelector" out of properties; properties is only for user metadata.'
|
|
47
|
-
},
|
|
48
|
-
element_selector: {
|
|
49
|
-
message: 'Move "element_selector" out of properties; properties is only for user metadata.'
|
|
50
|
-
},
|
|
51
|
-
buildId: {
|
|
52
|
-
message: 'Move "buildId" out of properties; properties is only for user metadata.'
|
|
53
|
-
},
|
|
54
|
-
build_id: {
|
|
55
|
-
message: 'Move "build_id" out of properties; properties is only for user metadata.'
|
|
56
|
-
},
|
|
57
|
-
requestTimeout: {
|
|
58
|
-
message: 'Move "requestTimeout" out of properties; properties is only for user metadata.'
|
|
59
|
-
},
|
|
60
|
-
request_timeout: {
|
|
61
|
-
message: 'Move "request_timeout" out of properties; properties is only for user metadata.'
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
1
|
export let SCREENSHOT_OPTION_NAMES = Object.freeze(['threshold', 'minClusterSize', 'fullPage', 'captureMode', 'deviceScaleFactor', 'selector']);
|
|
65
2
|
export function getScreenshotOptionsPayload(options = {}) {
|
|
66
3
|
return Object.fromEntries(SCREENSHOT_OPTION_NAMES.filter(option => options[option] !== undefined).map(option => [option, options[option]]));
|
|
67
4
|
}
|
|
68
|
-
function createReservedPropertyWarning(option) {
|
|
69
|
-
return {
|
|
70
|
-
code: 'reserved-property-option',
|
|
71
|
-
option,
|
|
72
|
-
message: RESERVED_PROPERTY_OPTIONS[option].message
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
5
|
|
|
76
6
|
/**
|
|
77
|
-
*
|
|
78
|
-
* Reserved names found inside properties are discarded instead of being promoted
|
|
79
|
-
* into options.
|
|
7
|
+
* Keep user properties separate from the Vizzly options sent beside them.
|
|
80
8
|
*/
|
|
81
9
|
export function normalizeScreenshotOptions(options = {}) {
|
|
82
10
|
let {
|
|
@@ -90,16 +18,7 @@ export function normalizeScreenshotOptions(options = {}) {
|
|
|
90
18
|
deviceScaleFactor,
|
|
91
19
|
selector
|
|
92
20
|
} = options;
|
|
93
|
-
let warnings = [];
|
|
94
|
-
let normalizedProperties = {};
|
|
95
21
|
let sourceProperties = properties && typeof properties === 'object' && !Array.isArray(properties) ? properties : {};
|
|
96
|
-
for (let [key, value] of Object.entries(sourceProperties)) {
|
|
97
|
-
if (RESERVED_PROPERTY_OPTIONS[key]) {
|
|
98
|
-
warnings.push(createReservedPropertyWarning(key));
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
normalizedProperties[key] = value;
|
|
102
|
-
}
|
|
103
22
|
return {
|
|
104
23
|
buildId,
|
|
105
24
|
requestTimeout,
|
|
@@ -109,8 +28,9 @@ export function normalizeScreenshotOptions(options = {}) {
|
|
|
109
28
|
captureMode,
|
|
110
29
|
deviceScaleFactor,
|
|
111
30
|
selector,
|
|
112
|
-
properties:
|
|
113
|
-
|
|
31
|
+
properties: {
|
|
32
|
+
...sourceProperties
|
|
33
|
+
}
|
|
114
34
|
};
|
|
115
35
|
}
|
|
116
36
|
export function createScreenshotProperties(options = {}) {
|
package/dist/utils/security.js
CHANGED
|
@@ -166,55 +166,40 @@ export function safePath(workingDir, ...pathSegments) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
169
|
+
* Validate user metadata without interpreting names as Vizzly options or
|
|
170
|
+
* rewriting values. Rendering code must escape strings for its output context.
|
|
171
|
+
* Reject unsafe object keys and values that cannot be represented as JSON.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} [properties={}] - User screenshot metadata.
|
|
174
|
+
* @returns {Object} A copy preserving the supplied JSON values.
|
|
175
|
+
* @throws {Error} Metadata contains unsafe keys, cycles, or non-JSON values.
|
|
172
176
|
*/
|
|
173
177
|
export function validateScreenshotProperties(properties = {}) {
|
|
174
|
-
if (properties
|
|
178
|
+
if (!properties || typeof properties !== 'object' || Array.isArray(properties)) {
|
|
175
179
|
return {};
|
|
176
180
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
// Extract browser name without version (e.g., "Chrome/139.0.7258.138" -> "Chrome")
|
|
183
|
-
const browserName = properties.browser.split('/')[0];
|
|
184
|
-
validated.browser = sanitizeScreenshotName(browserName, 50);
|
|
185
|
-
} catch (error) {
|
|
186
|
-
// Skip invalid browser names, don't include them
|
|
187
|
-
output.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (properties.viewport && typeof properties.viewport === 'object') {
|
|
191
|
-
const viewport = {};
|
|
192
|
-
if (typeof properties.viewport.width === 'number' && properties.viewport.width > 0 && properties.viewport.width <= 10000) {
|
|
193
|
-
viewport.width = Math.floor(properties.viewport.width);
|
|
194
|
-
}
|
|
195
|
-
if (typeof properties.viewport.height === 'number' && properties.viewport.height > 0 && properties.viewport.height <= 10000) {
|
|
196
|
-
viewport.height = Math.floor(properties.viewport.height);
|
|
181
|
+
let ancestors = new Set();
|
|
182
|
+
function copy(value) {
|
|
183
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
184
|
+
return value;
|
|
197
185
|
}
|
|
198
|
-
if (
|
|
199
|
-
|
|
186
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
187
|
+
if (typeof value !== 'object' || ancestors.has(value)) {
|
|
188
|
+
throw new Error('Screenshot properties must contain JSON values');
|
|
200
189
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// Preserve safe URL/query characters like '&' in metadata values.
|
|
210
|
-
// Rendering layers should escape for HTML instead of mutating payload data here.
|
|
211
|
-
validated[key] = value.replace(/[<>"']/g, '');
|
|
212
|
-
} else if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) {
|
|
213
|
-
validated[key] = value;
|
|
214
|
-
} else if (typeof value === 'boolean') {
|
|
215
|
-
validated[key] = value;
|
|
190
|
+
ancestors.add(value);
|
|
191
|
+
let result;
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
result = value.map(copy);
|
|
194
|
+
} else {
|
|
195
|
+
let entries = Object.entries(value);
|
|
196
|
+
if (entries.some(([key]) => ['__proto__', 'constructor', 'prototype'].includes(key))) {
|
|
197
|
+
throw new Error('Screenshot properties contain an unsafe key');
|
|
216
198
|
}
|
|
199
|
+
result = Object.fromEntries(entries.map(([key, item]) => [key, copy(item)]));
|
|
217
200
|
}
|
|
201
|
+
ancestors.delete(value);
|
|
202
|
+
return result;
|
|
218
203
|
}
|
|
219
|
-
return
|
|
204
|
+
return copy(properties);
|
|
220
205
|
}
|