@vizzly-testing/cli 0.35.2 → 0.35.3-beta.1
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 -5
- package/dist/api/client.js +20 -5
- package/dist/api/core.js +17 -3
- package/dist/api/endpoints.js +24 -3
- package/dist/api/index.js +1 -1
- package/dist/cli.js +25 -65
- package/dist/commands/baselines.js +0 -7
- package/dist/commands/comparisons.js +52 -45
- package/dist/commands/config-cmd.js +0 -7
- package/dist/commands/context.js +444 -238
- package/dist/commands/doctor.js +0 -8
- package/dist/commands/init.js +7 -12
- package/dist/commands/login.js +0 -13
- package/dist/commands/logout.js +0 -12
- package/dist/commands/orgs.js +0 -9
- package/dist/commands/run.js +84 -29
- package/dist/commands/status.js +276 -59
- package/dist/commands/tdd.js +19 -3
- package/dist/commands/whoami.js +0 -12
- package/dist/context/local-workspace-provider.js +293 -37
- package/dist/plugin-loader.js +1 -1
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +17 -17
- package/dist/server/routers/events.js +4 -4
- package/dist/test-runner/operations.js +70 -28
- package/dist/types/index.d.ts +4 -2
- package/dist/uploader/core.js +6 -6
- package/dist/uploader/operations.js +4 -1
- package/dist/utils/visual-context-normalizers.js +455 -0
- package/package.json +13 -13
- package/skills/vizzly/SKILL.md +68 -101
- package/skills/vizzly/agents/openai.yaml +2 -2
- package/skills/vizzly/references/cli-context.md +79 -27
- package/skills/vizzly/references/dynamic-content.md +28 -33
- package/skills/vizzly/references/sdks.md +26 -39
- package/skills/vizzly/references/setup-ci.md +44 -20
- package/dist/config/index.js +0 -13
- package/dist/project/index.js +0 -13
- package/dist/tdd/index.js +0 -19
- package/dist/tdd/services/baseline-downloader.js +0 -151
- package/dist/tdd/services/hotspot-service.js +0 -58
- package/dist/utils/environment.js +0 -109
- package/skills/vizzly/references/public-screenshots.md +0 -44
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Baseline Downloader
|
|
3
|
-
*
|
|
4
|
-
* Functions for downloading baseline images from the cloud API.
|
|
5
|
-
* These are lower-level utilities - orchestration happens in TddService.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { fetchWithTimeout } from '../../utils/fetch-utils.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Download a single baseline image
|
|
13
|
-
*
|
|
14
|
-
* @param {string} url - URL to download from
|
|
15
|
-
* @param {string} destPath - Destination file path
|
|
16
|
-
* @param {Object} options - Options
|
|
17
|
-
* @param {number} options.timeout - Request timeout in ms (default: 30000)
|
|
18
|
-
* @returns {Promise<{ success: boolean, error?: string }>}
|
|
19
|
-
*/
|
|
20
|
-
export async function downloadBaselineImage(url, destPath, options = {}) {
|
|
21
|
-
let {
|
|
22
|
-
timeout = 30000
|
|
23
|
-
} = options;
|
|
24
|
-
try {
|
|
25
|
-
let response = await fetchWithTimeout(url, {
|
|
26
|
-
timeout
|
|
27
|
-
});
|
|
28
|
-
if (!response.ok) {
|
|
29
|
-
return {
|
|
30
|
-
success: false,
|
|
31
|
-
error: `HTTP ${response.status}: ${response.statusText}`
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
let buffer = Buffer.from(await response.arrayBuffer());
|
|
35
|
-
writeFileSync(destPath, buffer);
|
|
36
|
-
return {
|
|
37
|
-
success: true
|
|
38
|
-
};
|
|
39
|
-
} catch (error) {
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
error: error.message
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if a baseline already exists with matching SHA
|
|
49
|
-
*
|
|
50
|
-
* @param {string} filePath - Path to the baseline file
|
|
51
|
-
* @param {string} expectedSha - Expected SHA256 hash
|
|
52
|
-
* @param {Map<string, string>} shaMap - Map of filename -> sha256
|
|
53
|
-
* @returns {boolean}
|
|
54
|
-
*/
|
|
55
|
-
export function baselineMatchesSha(filePath, expectedSha, shaMap) {
|
|
56
|
-
if (!existsSync(filePath) || !expectedSha) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
let filename = filePath.split('/').pop();
|
|
60
|
-
let storedSha = shaMap.get(filename);
|
|
61
|
-
return storedSha === expectedSha;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Download multiple baselines in batches
|
|
66
|
-
*
|
|
67
|
-
* @param {Array} screenshots - Screenshots to download
|
|
68
|
-
* @param {Object} options - Options
|
|
69
|
-
* @param {string} options.baselinePath - Path to baselines directory
|
|
70
|
-
* @param {Map<string, string>} options.existingShaMap - Existing SHA map for skip logic
|
|
71
|
-
* @param {number} options.batchSize - Concurrent downloads (default: 5)
|
|
72
|
-
* @param {Function} options.onProgress - Progress callback (downloaded, skipped, errors, total)
|
|
73
|
-
* @param {Function} options.getFilePath - Function to get file path for a screenshot
|
|
74
|
-
* @returns {Promise<{ downloaded: number, skipped: number, errors: number }>}
|
|
75
|
-
*/
|
|
76
|
-
export async function downloadBaselinesInBatches(screenshots, options = {}) {
|
|
77
|
-
let {
|
|
78
|
-
existingShaMap = new Map(),
|
|
79
|
-
batchSize = 5,
|
|
80
|
-
onProgress,
|
|
81
|
-
getFilePath
|
|
82
|
-
} = options;
|
|
83
|
-
let downloaded = 0;
|
|
84
|
-
let skipped = 0;
|
|
85
|
-
let errors = 0;
|
|
86
|
-
let total = screenshots.length;
|
|
87
|
-
|
|
88
|
-
// Process in batches
|
|
89
|
-
for (let i = 0; i < screenshots.length; i += batchSize) {
|
|
90
|
-
let batch = screenshots.slice(i, i + batchSize);
|
|
91
|
-
let batchPromises = batch.map(async screenshot => {
|
|
92
|
-
let filePath = getFilePath(screenshot);
|
|
93
|
-
let url = screenshot.original_url;
|
|
94
|
-
if (!url) {
|
|
95
|
-
errors++;
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Skip if SHA matches
|
|
100
|
-
if (baselineMatchesSha(filePath, screenshot.sha256, existingShaMap)) {
|
|
101
|
-
skipped++;
|
|
102
|
-
downloaded++;
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
let result = await downloadBaselineImage(url, filePath);
|
|
106
|
-
if (result.success) {
|
|
107
|
-
downloaded++;
|
|
108
|
-
} else {
|
|
109
|
-
errors++;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
await Promise.all(batchPromises);
|
|
113
|
-
if (onProgress) {
|
|
114
|
-
onProgress(downloaded, skipped, errors, total);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
downloaded,
|
|
119
|
-
skipped,
|
|
120
|
-
errors
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Build baseline metadata entry for a downloaded screenshot
|
|
126
|
-
*
|
|
127
|
-
* @param {Object} screenshot - Screenshot data from API
|
|
128
|
-
* @param {string} filename - Local filename
|
|
129
|
-
* @param {string} filePath - Full file path
|
|
130
|
-
* @param {Object} buildInfo - Build information
|
|
131
|
-
* @returns {Object} Metadata entry
|
|
132
|
-
*/
|
|
133
|
-
export function buildBaselineMetadataEntry(screenshot, filename, filePath, buildInfo = {}) {
|
|
134
|
-
return {
|
|
135
|
-
name: screenshot.name,
|
|
136
|
-
originalName: screenshot.name,
|
|
137
|
-
sha256: screenshot.sha256,
|
|
138
|
-
id: screenshot.id,
|
|
139
|
-
filename,
|
|
140
|
-
path: filePath,
|
|
141
|
-
browser: screenshot.browser || screenshot.metadata?.browser,
|
|
142
|
-
viewport_width: screenshot.viewport_width || screenshot.metadata?.viewport?.width || screenshot.properties?.viewport?.width,
|
|
143
|
-
originalUrl: screenshot.original_url,
|
|
144
|
-
fileSize: screenshot.file_size,
|
|
145
|
-
dimensions: screenshot.dimensions,
|
|
146
|
-
// Build info for tracking
|
|
147
|
-
buildId: buildInfo.buildId,
|
|
148
|
-
commitSha: buildInfo.commitSha,
|
|
149
|
-
approvalStatus: screenshot.approval_status
|
|
150
|
-
};
|
|
151
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hotspot Service
|
|
3
|
-
*
|
|
4
|
-
* Functions for downloading and managing hotspot data from the cloud.
|
|
5
|
-
* Hotspots identify regions that frequently change due to dynamic content.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { saveHotspotMetadata } from '../metadata/hotspot-metadata.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Download hotspots for screenshots from cloud API
|
|
12
|
-
*
|
|
13
|
-
* @param {Object} options
|
|
14
|
-
* @param {Object} options.api - ApiService instance
|
|
15
|
-
* @param {string} options.workingDir - Working directory
|
|
16
|
-
* @param {string[]} options.screenshotNames - Names of screenshots to get hotspots for
|
|
17
|
-
* @returns {Promise<{ success: boolean, count: number, regionCount: number, error?: string }>}
|
|
18
|
-
*/
|
|
19
|
-
export async function downloadHotspots(options) {
|
|
20
|
-
let {
|
|
21
|
-
api,
|
|
22
|
-
workingDir,
|
|
23
|
-
screenshotNames
|
|
24
|
-
} = options;
|
|
25
|
-
if (!screenshotNames || screenshotNames.length === 0) {
|
|
26
|
-
return {
|
|
27
|
-
success: true,
|
|
28
|
-
count: 0,
|
|
29
|
-
regionCount: 0
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
let response = await api.getHotspots(screenshotNames);
|
|
34
|
-
if (!response?.hotspots) {
|
|
35
|
-
return {
|
|
36
|
-
success: false,
|
|
37
|
-
error: 'API returned no hotspot data'
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Save hotspots to disk
|
|
42
|
-
saveHotspotMetadata(workingDir, response.hotspots, response.summary);
|
|
43
|
-
|
|
44
|
-
// Calculate stats
|
|
45
|
-
let count = Object.keys(response.hotspots).length;
|
|
46
|
-
let regionCount = Object.values(response.hotspots).reduce((sum, h) => sum + (h.regions?.length || 0), 0);
|
|
47
|
-
return {
|
|
48
|
-
success: true,
|
|
49
|
-
count,
|
|
50
|
-
regionCount
|
|
51
|
-
};
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: error.message
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment detection utilities for handling CI/interactive environments
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Check if running in a CI environment
|
|
7
|
-
* Based on common CI environment variables
|
|
8
|
-
* @returns {boolean} True if running in CI
|
|
9
|
-
*/
|
|
10
|
-
export function isCI() {
|
|
11
|
-
return Boolean(process.env.CI ||
|
|
12
|
-
// Generic CI flag
|
|
13
|
-
process.env.CONTINUOUS_INTEGRATION ||
|
|
14
|
-
// TravisCI, CircleCI
|
|
15
|
-
process.env.BUILD_NUMBER ||
|
|
16
|
-
// Jenkins
|
|
17
|
-
process.env.GITHUB_ACTIONS ||
|
|
18
|
-
// GitHub Actions
|
|
19
|
-
process.env.GITLAB_CI ||
|
|
20
|
-
// GitLab CI
|
|
21
|
-
process.env.CIRCLECI ||
|
|
22
|
-
// CircleCI
|
|
23
|
-
process.env.TRAVIS ||
|
|
24
|
-
// TravisCI
|
|
25
|
-
process.env.APPVEYOR ||
|
|
26
|
-
// AppVeyor
|
|
27
|
-
process.env.CODEBUILD_BUILD_ID ||
|
|
28
|
-
// AWS CodeBuild
|
|
29
|
-
process.env.TEAMCITY_VERSION ||
|
|
30
|
-
// TeamCity
|
|
31
|
-
process.env.TF_BUILD ||
|
|
32
|
-
// Azure DevOps
|
|
33
|
-
process.env.DRONE ||
|
|
34
|
-
// Drone CI
|
|
35
|
-
process.env.BITBUCKET_BUILD_NUMBER // Bitbucket Pipelines
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Check if stdout supports interactive features
|
|
41
|
-
* @returns {boolean} True if interactive features are supported
|
|
42
|
-
*/
|
|
43
|
-
export function isInteractiveTerminal() {
|
|
44
|
-
return Boolean(process.stdout.isTTY && process.stdin.isTTY && (!process.env.TERM || process.env.TERM !== 'dumb'));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Determine if interactive mode should be enabled
|
|
49
|
-
* Takes into account CI detection, TTY support, and explicit flags
|
|
50
|
-
* @param {Object} options
|
|
51
|
-
* @param {boolean} [options.noInteractive] - Explicit flag to disable interactive mode
|
|
52
|
-
* @param {boolean} [options.interactive] - Explicit flag to force interactive mode
|
|
53
|
-
* @returns {boolean} True if interactive mode should be enabled
|
|
54
|
-
*/
|
|
55
|
-
export function shouldUseInteractiveMode(options = {}) {
|
|
56
|
-
// Explicit flags take priority
|
|
57
|
-
// Commander.js sets interactive=false when --no-interactive is used
|
|
58
|
-
if (options.interactive === false) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
if (options.interactive === true) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Legacy support for explicit noInteractive flag
|
|
66
|
-
if (options.noInteractive === true) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Auto-detect based on environment
|
|
71
|
-
return !isCI() && isInteractiveTerminal();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get environment type for logging/debugging
|
|
76
|
-
* @returns {string} Environment type description
|
|
77
|
-
*/
|
|
78
|
-
export function getEnvironmentType() {
|
|
79
|
-
if (isCI()) {
|
|
80
|
-
return 'CI';
|
|
81
|
-
}
|
|
82
|
-
if (!isInteractiveTerminal()) {
|
|
83
|
-
return 'non-interactive';
|
|
84
|
-
}
|
|
85
|
-
return 'interactive';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get detailed environment info for debugging
|
|
90
|
-
* @returns {Object} Environment details
|
|
91
|
-
*/
|
|
92
|
-
export function getEnvironmentDetails() {
|
|
93
|
-
return {
|
|
94
|
-
isCI: isCI(),
|
|
95
|
-
isInteractiveTerminal: isInteractiveTerminal(),
|
|
96
|
-
environmentType: getEnvironmentType(),
|
|
97
|
-
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
98
|
-
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
99
|
-
ciVars: {
|
|
100
|
-
CI: process.env.CI,
|
|
101
|
-
CONTINUOUS_INTEGRATION: process.env.CONTINUOUS_INTEGRATION,
|
|
102
|
-
GITHUB_ACTIONS: process.env.GITHUB_ACTIONS,
|
|
103
|
-
GITLAB_CI: process.env.GITLAB_CI,
|
|
104
|
-
CIRCLECI: process.env.CIRCLECI,
|
|
105
|
-
TRAVIS: process.env.TRAVIS
|
|
106
|
-
},
|
|
107
|
-
termType: process.env.TERM
|
|
108
|
-
};
|
|
109
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Public Screenshots
|
|
2
|
-
|
|
3
|
-
Public screenshots are Vizzly's path for stable image URLs in docs and manuals.
|
|
4
|
-
|
|
5
|
-
Use this when a user asks for hotlinked UI screenshots, generated manual images, docs that stay current with UI, or CDN-like screenshot URLs.
|
|
6
|
-
|
|
7
|
-
## Mental Model
|
|
8
|
-
|
|
9
|
-
Do not use arbitrary build screenshot URLs as stable docs assets. Use Public Properties.
|
|
10
|
-
|
|
11
|
-
The flow:
|
|
12
|
-
|
|
13
|
-
1. Capture screenshots with stable metadata.
|
|
14
|
-
2. Configure project Public Properties in Vizzly.
|
|
15
|
-
3. Approve/publish the baseline build.
|
|
16
|
-
4. Use the Public URLs tab to copy the stable URL.
|
|
17
|
-
|
|
18
|
-
Example capture:
|
|
19
|
-
|
|
20
|
-
```javascript
|
|
21
|
-
await vizzlyScreenshot('watering-widget', screenshot, {
|
|
22
|
-
properties: {
|
|
23
|
-
manual: 'plant-care',
|
|
24
|
-
component: 'watering-widget',
|
|
25
|
-
viewport: 'desktop'
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Configure a Public Property like `manual=plant-care`. Vizzly publishes matching approved baseline screenshots and keeps stable URLs for each screenshot/property identity.
|
|
31
|
-
|
|
32
|
-
## Good Uses
|
|
33
|
-
|
|
34
|
-
- Product manuals
|
|
35
|
-
- User docs
|
|
36
|
-
- Component catalogs
|
|
37
|
-
- In-app UI examples
|
|
38
|
-
- Screenshots that should update when approved UI changes
|
|
39
|
-
|
|
40
|
-
## Cautions
|
|
41
|
-
|
|
42
|
-
- Public screenshots are public. Do not publish private user data, secrets, or internal-only UI.
|
|
43
|
-
- A new unapproved build does not automatically become the public docs image.
|
|
44
|
-
- Use stable names and properties so URLs do not churn.
|