@vizzly-testing/cli 0.24.0 → 0.25.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/dist/client/index.js +5 -0
- package/dist/plugin-api.js +26 -0
- package/dist/plugin-loader.js +10 -5
- package/dist/server/handlers/api-handler.js +24 -4
- package/dist/server/handlers/tdd-handler.js +8 -0
- package/dist/utils/config-loader.js +6 -1
- package/dist/utils/environment-config.js +9 -0
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -191,6 +191,7 @@ function createSimpleClient(serverUrl) {
|
|
|
191
191
|
let isFilePath = typeof imageBuffer === 'string';
|
|
192
192
|
let image = isFilePath ? imageBuffer : imageBuffer.toString('base64');
|
|
193
193
|
let type = isFilePath ? 'file-path' : 'base64';
|
|
194
|
+
let httpStart = Date.now();
|
|
194
195
|
const {
|
|
195
196
|
status,
|
|
196
197
|
json
|
|
@@ -202,6 +203,10 @@ function createSimpleClient(serverUrl) {
|
|
|
202
203
|
properties: options,
|
|
203
204
|
fullPage: options.fullPage || false
|
|
204
205
|
}, DEFAULT_TIMEOUT_MS);
|
|
206
|
+
let httpMs = Date.now() - httpStart;
|
|
207
|
+
if (shouldLogClient('debug')) {
|
|
208
|
+
console.debug(`[vizzly-client] ${name} HTTP completed in ${httpMs}ms`);
|
|
209
|
+
}
|
|
205
210
|
if (status < 200 || status >= 300) {
|
|
206
211
|
// In TDD mode, if we get 422 (visual difference), don't throw
|
|
207
212
|
// This allows all screenshots in the test to be captured and compared
|
package/dist/plugin-api.js
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* exposed to plugins to prevent coupling to implementation details.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from './utils/git.js';
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* Creates a stable plugin services object from the internal services
|
|
14
16
|
*
|
|
15
17
|
* Only exposes:
|
|
18
|
+
* - git: Git information detection (branch, commit, PR number, etc.)
|
|
16
19
|
* - testRunner: Build lifecycle management (createBuild, finalizeBuild, events)
|
|
17
20
|
* - serverManager: Screenshot server control (start, stop)
|
|
18
21
|
*
|
|
@@ -25,6 +28,29 @@ export function createPluginServices(services) {
|
|
|
25
28
|
serverManager
|
|
26
29
|
} = services;
|
|
27
30
|
return Object.freeze({
|
|
31
|
+
// Git detection utilities - provides correct git info from CI environments
|
|
32
|
+
git: Object.freeze({
|
|
33
|
+
/**
|
|
34
|
+
* Detect git information for build creation
|
|
35
|
+
* Handles CI environment variables correctly (GitHub Actions, GitLab, etc.)
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} [options] - Detection options
|
|
38
|
+
* @param {string} [options.buildPrefix] - Prefix for generated build name
|
|
39
|
+
* @returns {Promise<Object>} Git info: { branch, commit, message, prNumber, buildName }
|
|
40
|
+
*/
|
|
41
|
+
async detect(options = {}) {
|
|
42
|
+
let [branch, commit, message] = await Promise.all([detectBranch(), detectCommit(), detectCommitMessage()]);
|
|
43
|
+
let prNumber = detectPullRequestNumber();
|
|
44
|
+
let buildName = await generateBuildNameWithGit(options.buildPrefix);
|
|
45
|
+
return {
|
|
46
|
+
branch,
|
|
47
|
+
commit,
|
|
48
|
+
message,
|
|
49
|
+
prNumber,
|
|
50
|
+
buildName
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
28
54
|
testRunner: Object.freeze({
|
|
29
55
|
// EventEmitter methods for build lifecycle events
|
|
30
56
|
once: testRunner.once.bind(testRunner),
|
package/dist/plugin-loader.js
CHANGED
|
@@ -68,8 +68,10 @@ async function discoverInstalledPlugins() {
|
|
|
68
68
|
const packageJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
69
69
|
|
|
70
70
|
// Check if package has a plugin field
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Support both new `vizzlyPlugin` and legacy `vizzly.plugin` for backwards compatibility
|
|
72
|
+
const pluginField = packageJson.vizzlyPlugin || packageJson.vizzly?.plugin;
|
|
73
|
+
if (pluginField) {
|
|
74
|
+
const pluginRelativePath = pluginField;
|
|
73
75
|
|
|
74
76
|
// Security: Ensure plugin path is relative and doesn't traverse up
|
|
75
77
|
if (pluginRelativePath.startsWith('/') || pluginRelativePath.includes('..')) {
|
|
@@ -174,11 +176,14 @@ function resolvePluginPath(pluginSpec, configPath) {
|
|
|
174
176
|
try {
|
|
175
177
|
const packageJsonPath = resolve(process.cwd(), 'node_modules', pluginSpec, 'package.json');
|
|
176
178
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
177
|
-
|
|
179
|
+
|
|
180
|
+
// Support both new `vizzlyPlugin` and legacy `vizzly.plugin`
|
|
181
|
+
const pluginField = packageJson.vizzlyPlugin || packageJson.vizzly?.plugin;
|
|
182
|
+
if (pluginField) {
|
|
178
183
|
const packageDir = dirname(packageJsonPath);
|
|
179
|
-
return resolve(packageDir,
|
|
184
|
+
return resolve(packageDir, pluginField);
|
|
180
185
|
}
|
|
181
|
-
throw new Error('Package does not specify a vizzly.plugin field');
|
|
186
|
+
throw new Error('Package does not specify a vizzlyPlugin or vizzly.plugin field');
|
|
182
187
|
} catch (error) {
|
|
183
188
|
throw new Error(`Cannot resolve plugin package ${pluginSpec}: ${error.message}`);
|
|
184
189
|
}
|
|
@@ -48,6 +48,10 @@ export const createApiHandler = (client, {
|
|
|
48
48
|
let screenshotCount = 0;
|
|
49
49
|
let uploadPromises = [];
|
|
50
50
|
const handleScreenshot = async (buildId, name, image, properties = {}, type) => {
|
|
51
|
+
let handlerStart = Date.now();
|
|
52
|
+
output.debug('upload', `${name} received`, {
|
|
53
|
+
buildId: buildId?.slice(0, 8)
|
|
54
|
+
});
|
|
51
55
|
if (vizzlyDisabled) {
|
|
52
56
|
output.debug('upload', `${name} (disabled)`);
|
|
53
57
|
return {
|
|
@@ -124,27 +128,39 @@ export const createApiHandler = (client, {
|
|
|
124
128
|
};
|
|
125
129
|
}
|
|
126
130
|
screenshotCount++;
|
|
131
|
+
let uploadStart = Date.now();
|
|
127
132
|
|
|
128
133
|
// Fire upload in background - DON'T AWAIT!
|
|
129
134
|
let uploadPromise = uploadScreenshot(client, buildId, name, imageBuffer, properties ?? {}).then(result => {
|
|
135
|
+
let duration = Date.now() - uploadStart;
|
|
130
136
|
if (!result.skipped) {
|
|
131
|
-
output.debug('upload', name
|
|
137
|
+
output.debug('upload', `${name} completed`, {
|
|
138
|
+
ms: duration
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
output.debug('upload', `${name} skipped (dedup)`, {
|
|
142
|
+
ms: duration
|
|
143
|
+
});
|
|
132
144
|
}
|
|
133
145
|
return {
|
|
134
146
|
success: true,
|
|
135
147
|
name,
|
|
136
|
-
result
|
|
148
|
+
result,
|
|
149
|
+
duration
|
|
137
150
|
};
|
|
138
151
|
}).catch(uploadError => {
|
|
152
|
+
let duration = Date.now() - uploadStart;
|
|
139
153
|
output.debug('upload', `${name} failed`, {
|
|
140
|
-
error: uploadError.message
|
|
154
|
+
error: uploadError.message,
|
|
155
|
+
ms: duration
|
|
141
156
|
});
|
|
142
157
|
vizzlyDisabled = true;
|
|
143
158
|
output.warn('Vizzly disabled due to upload error - continuing tests without visual testing');
|
|
144
159
|
return {
|
|
145
160
|
success: false,
|
|
146
161
|
name,
|
|
147
|
-
error: uploadError
|
|
162
|
+
error: uploadError,
|
|
163
|
+
duration
|
|
148
164
|
};
|
|
149
165
|
});
|
|
150
166
|
|
|
@@ -152,6 +168,10 @@ export const createApiHandler = (client, {
|
|
|
152
168
|
uploadPromises.push(uploadPromise);
|
|
153
169
|
|
|
154
170
|
// Return immediately - test continues without waiting!
|
|
171
|
+
let handlerMs = Date.now() - handlerStart;
|
|
172
|
+
output.debug('upload', `${name} handler returning`, {
|
|
173
|
+
ms: handlerMs
|
|
174
|
+
});
|
|
155
175
|
return {
|
|
156
176
|
statusCode: 200,
|
|
157
177
|
body: {
|
|
@@ -268,6 +268,9 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
270
|
const handleScreenshot = async (_buildId, name, image, properties = {}, type) => {
|
|
271
|
+
let handlerStart = Date.now();
|
|
272
|
+
output.debug('tdd', `${name} received`);
|
|
273
|
+
|
|
271
274
|
// Validate and sanitize screenshot name
|
|
272
275
|
let sanitizedName;
|
|
273
276
|
try {
|
|
@@ -444,6 +447,11 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
444
447
|
}
|
|
445
448
|
|
|
446
449
|
// Match or new baseline
|
|
450
|
+
let handlerMs = Date.now() - handlerStart;
|
|
451
|
+
output.debug('tdd', `${name} handler returning`, {
|
|
452
|
+
ms: handlerMs,
|
|
453
|
+
status: comparison.status
|
|
454
|
+
});
|
|
447
455
|
return {
|
|
448
456
|
statusCode: 200,
|
|
449
457
|
body: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { cosmiconfigSync } from 'cosmiconfig';
|
|
3
3
|
import { validateVizzlyConfigWithDefaults } from './config-schema.js';
|
|
4
|
-
import { getApiToken, getApiUrl, getParallelId } from './environment-config.js';
|
|
4
|
+
import { getApiToken, getApiUrl, getBuildName, getParallelId } from './environment-config.js';
|
|
5
5
|
import { getProjectMapping } from './global-config.js';
|
|
6
6
|
import * as output from './output.js';
|
|
7
7
|
const DEFAULT_CONFIG = {
|
|
@@ -98,12 +98,17 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
|
98
98
|
// 4. Override with environment variables (higher priority than fallbacks)
|
|
99
99
|
const envApiKey = getApiToken();
|
|
100
100
|
const envApiUrl = getApiUrl();
|
|
101
|
+
const envBuildName = getBuildName();
|
|
101
102
|
const envParallelId = getParallelId();
|
|
102
103
|
if (envApiKey) {
|
|
103
104
|
config.apiKey = envApiKey;
|
|
104
105
|
output.debug('config', 'using token from environment');
|
|
105
106
|
}
|
|
106
107
|
if (envApiUrl !== 'https://app.vizzly.dev') config.apiUrl = envApiUrl;
|
|
108
|
+
if (envBuildName) {
|
|
109
|
+
config.build.name = envBuildName;
|
|
110
|
+
output.debug('config', 'using build name from environment');
|
|
111
|
+
}
|
|
107
112
|
if (envParallelId) config.parallelId = envParallelId;
|
|
108
113
|
|
|
109
114
|
// 5. Apply CLI overrides (highest priority)
|
|
@@ -77,6 +77,14 @@ export function getParallelId() {
|
|
|
77
77
|
return process.env.VIZZLY_PARALLEL_ID;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Get build name from environment
|
|
82
|
+
* @returns {string|undefined} Build name
|
|
83
|
+
*/
|
|
84
|
+
export function getBuildName() {
|
|
85
|
+
return process.env.VIZZLY_BUILD_NAME;
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
/**
|
|
81
89
|
* Check if TDD mode is enabled
|
|
82
90
|
* @returns {boolean} Whether TDD mode is enabled
|
|
@@ -107,6 +115,7 @@ export function getAllEnvironmentConfig() {
|
|
|
107
115
|
enabled: isVizzlyEnabled(),
|
|
108
116
|
serverUrl: getServerUrl(),
|
|
109
117
|
buildId: getBuildId(),
|
|
118
|
+
buildName: getBuildName(),
|
|
110
119
|
parallelId: getParallelId(),
|
|
111
120
|
tddMode: isTddMode()
|
|
112
121
|
};
|