@vizzly-testing/cli 0.11.0 → 0.11.2

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.
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: Debug Visual Regression
3
+ description: Analyze visual regression test failures in Vizzly. Use when the user mentions failing visual tests, screenshot differences, visual bugs, diffs, or asks to debug/investigate/analyze visual changes. Works with both local TDD and cloud modes.
4
+ allowed-tools: Read, WebFetch, mcp__plugin_vizzly_vizzly__read_comparison_details, mcp__plugin_vizzly_vizzly__search_comparisons, mcp__plugin_vizzly_vizzly__accept_baseline, mcp__plugin_vizzly_vizzly__approve_comparison, mcp__plugin_vizzly_vizzly__reject_comparison
5
+ ---
6
+
7
+ # Debug Visual Regression
8
+
9
+ Automatically analyze visual regression failures when the user mentions them. This Skill helps identify the root cause of visual differences and suggests whether to accept or fix changes.
10
+
11
+ ## When to Use This Skill
12
+
13
+ Activate this Skill when the user:
14
+ - Mentions "failing visual test" or "screenshot failure"
15
+ - Asks "what's wrong with my visual tests?"
16
+ - Says "the homepage screenshot is different" or similar
17
+ - Wants to understand why a visual comparison failed
18
+ - Asks to "debug", "analyze", or "investigate" visual changes
19
+ - Mentions specific screenshot names that are failing
20
+
21
+ ## How This Skill Works
22
+
23
+ 1. **Automatically detect the mode** (local TDD or cloud)
24
+ 2. **Fetch comparison details** using the screenshot name or comparison ID
25
+ 3. **View the actual images** to perform visual analysis
26
+ 4. **Provide detailed insights** on what changed and why
27
+ 5. **Suggest next steps** (accept, reject, or fix)
28
+
29
+ ## Instructions
30
+
31
+ ### Step 1: Call the Unified MCP Tool
32
+
33
+ Use `read_comparison_details` with the identifier:
34
+ - Pass screenshot name (e.g., "homepage_desktop") for local mode
35
+ - Pass comparison ID (e.g., "cmp_abc123") for cloud mode
36
+ - The tool automatically detects which mode to use
37
+ - Returns a response with `mode` field indicating local or cloud
38
+
39
+ ### Step 2: Check the Mode in Response
40
+
41
+ The response will contain a `mode` field:
42
+ - **Local mode** (`mode: "local"`): Returns filesystem paths (`baselinePath`, `currentPath`, `diffPath`)
43
+ - **Cloud mode** (`mode: "cloud"`): Returns URLs (`baselineUrl`, `currentUrl`, `diffUrl`)
44
+
45
+ ### Step 3: Analyze Comparison Data
46
+
47
+ Examine the comparison details:
48
+ - Diff percentage and threshold
49
+ - Status (failed/new/passed)
50
+ - Image references (paths or URLs depending on mode)
51
+ - Viewport and browser information
52
+
53
+ ### Step 4: View the Actual Images
54
+
55
+ **CRITICAL:** You MUST view the baseline and current images to provide accurate analysis.
56
+
57
+ **If mode is "local":**
58
+ - Response contains filesystem paths (`baselinePath`, `currentPath`, `diffPath`)
59
+ - **Use the Read tool to view ONLY baselinePath and currentPath**
60
+ - **DO NOT read diffPath** - it causes API errors
61
+
62
+ **If mode is "cloud":**
63
+ - Response contains public URLs (`baselineUrl`, `currentUrl`, `diffUrl`)
64
+ - **Use the WebFetch tool to view ONLY baselineUrl and currentUrl**
65
+ - **DO NOT fetch diffUrl** - it causes API errors
66
+
67
+ ### Step 5: Provide Detailed Visual Insights
68
+
69
+ Based on what you observe in the images:
70
+
71
+ **Describe the specific visual differences:**
72
+ - Which UI components, elements, or layouts changed
73
+ - Colors, spacing, typography, positioning changes
74
+ - Missing or added elements
75
+
76
+ **Categorize the change by diff percentage:**
77
+ - **< 1%:** Anti-aliasing, font rendering, subpixel differences
78
+ - **1-5%:** Layout shifts, padding/margin changes, color variations
79
+ - **> 5%:** Significant layout changes, missing content, major visual updates
80
+
81
+ **Identify possible causes:**
82
+ - CSS changes (margin, padding, positioning)
83
+ - Content changes (text, images)
84
+ - State issues (hover, focus, loading states)
85
+ - Browser/viewport rendering differences
86
+
87
+ ### Step 6: Suggest Next Steps
88
+
89
+ **If local mode:**
90
+ - Whether to accept using `accept_baseline` tool
91
+ - Specific code areas to investigate if unintentional
92
+ - How to fix common issues
93
+
94
+ **If cloud mode:**
95
+ - Whether to approve using `approve_comparison` tool
96
+ - Whether to reject using `reject_comparison` tool with reason
97
+ - Team coordination steps
98
+
99
+ **If changes are intentional:**
100
+ - Explain why it's safe to accept/approve
101
+ - Confirm this matches expected behavior
102
+
103
+ **If changes are unintentional:**
104
+ - Specific files to check (CSS, templates, components)
105
+ - Git commands to investigate recent changes
106
+ - How to reproduce and fix
107
+
108
+ ## Example Analysis (Local TDD Mode)
109
+
110
+ ```
111
+ User: "The homepage screenshot is failing"
112
+
113
+ Step 1: Call tool
114
+ Tool: read_comparison_details({ identifier: "homepage" })
115
+
116
+ Response:
117
+ {
118
+ "name": "homepage",
119
+ "status": "failed",
120
+ "diffPercentage": 2.3,
121
+ "threshold": 0.1,
122
+ "mode": "local",
123
+ "baselinePath": "/Users/you/project/.vizzly/baselines/homepage.png",
124
+ "currentPath": "/Users/you/project/.vizzly/screenshots/homepage.png"
125
+ }
126
+
127
+ Step 2: View images
128
+ Read(baselinePath)
129
+ Read(currentPath)
130
+
131
+ Step 3: Visual Analysis
132
+ Comparing the two images, I can see the following changes:
133
+
134
+ Visual Differences:
135
+ - The navigation header has shifted down by approximately 10-15 pixels
136
+ - Logo position moved from y:20px to y:35px
137
+ - Navigation menu items are now overlapping with the hero section
138
+ - The "Sign Up" button background changed from blue (#2563eb) to darker blue (#1e40af)
139
+
140
+ Root Cause Analysis:
141
+ Based on the 2.3% diff and visual comparison, this appears to be:
142
+ 1. A margin or padding change on the header element
143
+ 2. The button color change suggests a hover state being captured
144
+
145
+ Recommendations:
146
+ If intentional:
147
+ - This appears to be a deliberate redesign of the header spacing
148
+ - Accept as new baseline if these changes match your design updates
149
+
150
+ If unintentional:
151
+ - Check recent CSS changes to `.header` or `nav` margin-top/padding-top
152
+ - Verify button states during screenshot capture to avoid hover state
153
+ - Investigate with: `git diff src/styles/header.css`
154
+ - Fix the header positioning and re-run tests
155
+ ```
156
+
157
+ ## Example Analysis (Cloud Mode)
158
+
159
+ ```
160
+ User: "Can you check comparison cmp_xyz789?"
161
+
162
+ Step 1: Call tool
163
+ Tool: read_comparison_details({
164
+ identifier: "cmp_xyz789",
165
+ apiToken: "vzt_..."
166
+ })
167
+
168
+ Response:
169
+ {
170
+ "name": "login-page",
171
+ "status": "failed",
172
+ "diffPercentage": 0.8,
173
+ "threshold": 0.1,
174
+ "mode": "cloud",
175
+ "baselineUrl": "https://app.vizzly.dev/screenshots/abc123/baseline.png",
176
+ "currentUrl": "https://app.vizzly.dev/screenshots/abc123/current.png",
177
+ "comparisonId": "cmp_xyz789"
178
+ }
179
+
180
+ Step 2: View images
181
+ WebFetch(baselineUrl, "show me the baseline screenshot")
182
+ WebFetch(currentUrl, "show me the current screenshot")
183
+
184
+ Step 3: Visual Analysis
185
+ The differences are very subtle (0.8% diff):
186
+
187
+ Visual Differences:
188
+ - Input field border color slightly different (#e5e7eb vs #d1d5db)
189
+ - Submit button has very slight shadow change
190
+ - Font anti-aliasing appears slightly different
191
+
192
+ Root Cause Analysis:
193
+ This appears to be browser rendering differences or minor CSS updates:
194
+ - The 0.8% diff suggests this is not a major visual change
195
+ - Could be Chrome version differences
196
+ - Could be sub-pixel rendering variations
197
+
198
+ Recommendations:
199
+ This is likely acceptable to approve:
200
+ - The changes are extremely minor (< 1%)
201
+ - No functional UI changes
202
+ - Probably browser/rendering variations
203
+
204
+ Approve with: approve_comparison tool
205
+ Or adjust threshold to 1% if these variations are expected
206
+ ```
207
+
208
+ ## Cross-Build Debugging (Cloud Only)
209
+
210
+ When debugging visual regressions in cloud mode, you can track a screenshot across multiple builds to identify when changes were introduced.
211
+
212
+ ### When to Use Search
213
+
214
+ Use `search_comparisons` when:
215
+ - The user asks "when did this screenshot start failing?"
216
+ - They want to track a visual change across builds
217
+ - They're investigating a regression that appeared recently
218
+ - They want to see the history of a specific screenshot
219
+
220
+ ### How to Search
221
+
222
+ ```javascript
223
+ // Search for all comparisons of a screenshot
224
+ search_comparisons({
225
+ name: "homepage_desktop",
226
+ branch: "main", // optional: filter by branch
227
+ limit: 10 // optional: limit results
228
+ })
229
+ ```
230
+
231
+ ### Example Workflow
232
+
233
+ ```
234
+ User: "When did the homepage screenshot start failing?"
235
+
236
+ Step 1: Search for the screenshot across builds
237
+ Tool: search_comparisons({ name: "homepage", branch: "main", limit: 10 })
238
+
239
+ Response shows 10 comparisons from most recent to oldest, each with:
240
+ - Build name, branch, and creation date
241
+ - Diff percentage and status
242
+ - Screenshot URLs
243
+
244
+ Step 2: Analyze the timeline
245
+ - Build #45 (today): 5.2% diff - FAILED
246
+ - Build #44 (yesterday): 5.1% diff - FAILED
247
+ - Build #43 (2 days ago): 0.3% diff - PASSED
248
+ - Build #42 (3 days ago): 0.2% diff - PASSED
249
+
250
+ Step 3: Report findings
251
+ "The homepage screenshot started failing between Build #43 and #44
252
+ (approximately 1-2 days ago). The diff jumped from 0.3% to 5.1%,
253
+ suggesting a significant visual change was introduced."
254
+
255
+ Step 4: Deep dive into the failing build
256
+ Tool: read_comparison_details({ identifier: "cmp_xyz_from_build44" })
257
+ [View images and provide detailed analysis as usual]
258
+ ```
259
+
260
+ ## Important Notes
261
+
262
+ - **Always use `read_comparison_details`** - it automatically detects the mode
263
+ - **Use `search_comparisons` for cloud debugging** - to track changes across builds
264
+ - **Check the `mode` field** to know which viewing tool to use (Read vs WebFetch)
265
+ - **Never view diff images** - only baseline and current
266
+ - **Visual inspection is critical** - don't rely solely on diff percentages
267
+ - **Be specific in analysis** - identify exact elements that changed
268
+ - **Provide actionable advice** - specific files, commands, or tools to use
269
+ - **Consider context** - small diffs might be acceptable, large ones need investigation
@@ -131,21 +131,16 @@ async function loadPlugin(pluginPath) {
131
131
 
132
132
  /**
133
133
  * Zod schema for validating plugin structure
134
+ * Note: Using passthrough() to allow configSchema without validating its structure
135
+ * to avoid Zod version conflicts when plugins have nested config objects
134
136
  */
135
137
  const pluginSchema = z.object({
136
138
  name: z.string().min(1, 'Plugin name is required'),
137
139
  version: z.string().optional(),
138
140
  register: z.custom(val => typeof val === 'function', {
139
141
  message: 'register must be a function'
140
- }),
141
- configSchema: z.record(z.any()).optional()
142
- });
143
-
144
- /**
145
- * Zod schema for validating plugin config values
146
- * Supports: string, number, boolean, null, arrays, and nested objects
147
- */
148
- const configValueSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(configValueSchema), z.record(configValueSchema)]));
142
+ })
143
+ }).passthrough();
149
144
 
150
145
  /**
151
146
  * Validate plugin has required structure
@@ -157,11 +152,8 @@ function validatePluginStructure(plugin) {
157
152
  // Validate basic plugin structure
158
153
  pluginSchema.parse(plugin);
159
154
 
160
- // If configSchema exists, validate it contains valid config values
161
- if (plugin.configSchema) {
162
- let configSchemaValidator = z.record(configValueSchema);
163
- configSchemaValidator.parse(plugin.configSchema);
164
- }
155
+ // Skip deep validation of configSchema to avoid Zod version conflicts
156
+ // configSchema is optional and primarily for documentation
165
157
  } catch (error) {
166
158
  if (error instanceof z.ZodError) {
167
159
  let messages = error.issues.map(e => `${e.path.join('.')}: ${e.message}`);
@@ -128,7 +128,37 @@ export class ApiService {
128
128
  * @returns {Promise<Object>} Comparison data
129
129
  */
130
130
  async getComparison(comparisonId) {
131
- return this.request(`/api/sdk/comparisons/${comparisonId}`);
131
+ let response = await this.request(`/api/sdk/comparisons/${comparisonId}`);
132
+ return response.comparison;
133
+ }
134
+
135
+ /**
136
+ * Search for comparisons by name across builds
137
+ * @param {string} name - Screenshot name to search for
138
+ * @param {Object} filters - Optional filters (branch, limit, offset)
139
+ * @param {string} [filters.branch] - Filter by branch name
140
+ * @param {number} [filters.limit=50] - Maximum number of results (default: 50)
141
+ * @param {number} [filters.offset=0] - Pagination offset (default: 0)
142
+ * @returns {Promise<Object>} Search results with comparisons and pagination
143
+ */
144
+ async searchComparisons(name, filters = {}) {
145
+ if (!name || typeof name !== 'string') {
146
+ throw new VizzlyError('name is required and must be a non-empty string');
147
+ }
148
+ let {
149
+ branch,
150
+ limit = 50,
151
+ offset = 0
152
+ } = filters;
153
+ const queryParams = new URLSearchParams({
154
+ name,
155
+ limit: String(limit),
156
+ offset: String(offset)
157
+ });
158
+
159
+ // Only add branch if provided
160
+ if (branch) queryParams.append('branch', branch);
161
+ return this.request(`/api/sdk/comparisons/search?${queryParams}`);
132
162
  }
133
163
 
134
164
  /**
@@ -132,10 +132,35 @@ export class TddService {
132
132
  logger.warn(`⚠️ Build ${buildId} has status: ${baselineBuild.status} (expected: completed)`);
133
133
  }
134
134
  } else if (comparisonId) {
135
- // Use specific comparison ID
135
+ // Use specific comparison ID - download only this comparison's baseline screenshot
136
136
  logger.info(`📌 Using comparison: ${comparisonId}`);
137
137
  const comparison = await this.api.getComparison(comparisonId);
138
- baselineBuild = comparison.baselineBuild;
138
+
139
+ // A comparison doesn't have baselineBuild directly - we need to get it
140
+ // The comparison has baseline_screenshot which contains the build_id
141
+ if (!comparison.baseline_screenshot) {
142
+ throw new Error(`Comparison ${comparisonId} has no baseline screenshot. This comparison may be a "new" screenshot with no baseline to compare against.`);
143
+ }
144
+
145
+ // The original_url might be in baseline_screenshot.original_url or comparison.baseline_screenshot_url
146
+ let baselineUrl = comparison.baseline_screenshot.original_url || comparison.baseline_screenshot_url;
147
+ if (!baselineUrl) {
148
+ throw new Error(`Baseline screenshot for comparison ${comparisonId} has no download URL`);
149
+ }
150
+
151
+ // For a specific comparison, we only download that one baseline screenshot
152
+ // Create a mock build structure with just this one screenshot
153
+ baselineBuild = {
154
+ id: comparison.baseline_screenshot.build_id || 'comparison-baseline',
155
+ name: `Comparison ${comparisonId.substring(0, 8)}`,
156
+ screenshots: [{
157
+ id: comparison.baseline_screenshot.id,
158
+ name: comparison.baseline_name || comparison.current_name,
159
+ original_url: baselineUrl,
160
+ metadata: {},
161
+ properties: {}
162
+ }]
163
+ };
139
164
  } else {
140
165
  // Get the latest passed build for this environment and branch
141
166
  const builds = await this.api.getBuilds({
@@ -152,10 +177,12 @@ export class TddService {
152
177
  baselineBuild = builds.data[0];
153
178
  }
154
179
 
155
- // For specific buildId, we already have screenshots, otherwise get build details
180
+ // For specific buildId, we already have screenshots
181
+ // For comparisonId, we created a mock build with just the one screenshot
182
+ // Otherwise, get build details with screenshots
156
183
  let buildDetails = baselineBuild;
157
- if (!buildId) {
158
- // Get build details with screenshots for non-buildId cases
184
+ if (!buildId && !comparisonId) {
185
+ // Get build details with screenshots for non-buildId/non-comparisonId cases
159
186
  const actualBuildId = baselineBuild.id;
160
187
  buildDetails = await this.api.getBuild(actualBuildId, 'screenshots');
161
188
  }
@@ -28,6 +28,20 @@ export class ApiService {
28
28
  * @returns {Promise<Object>} Comparison data
29
29
  */
30
30
  getComparison(comparisonId: string): Promise<any>;
31
+ /**
32
+ * Search for comparisons by name across builds
33
+ * @param {string} name - Screenshot name to search for
34
+ * @param {Object} filters - Optional filters (branch, limit, offset)
35
+ * @param {string} [filters.branch] - Filter by branch name
36
+ * @param {number} [filters.limit=50] - Maximum number of results (default: 50)
37
+ * @param {number} [filters.offset=0] - Pagination offset (default: 0)
38
+ * @returns {Promise<Object>} Search results with comparisons and pagination
39
+ */
40
+ searchComparisons(name: string, filters?: {
41
+ branch?: string;
42
+ limit?: number;
43
+ offset?: number;
44
+ }): Promise<any>;
31
45
  /**
32
46
  * Get builds for a project
33
47
  * @param {Object} filters - Filter options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",
@@ -48,7 +48,7 @@
48
48
  "bin",
49
49
  "dist",
50
50
  "docs",
51
- ".claude-plugin",
51
+ "claude-plugin",
52
52
  "README.md",
53
53
  "LICENSE"
54
54
  ],
@@ -1,114 +0,0 @@
1
- # Vizzly Claude Code Plugin
2
-
3
- > Integrate Vizzly visual regression testing seamlessly into your Claude Code workflow
4
-
5
- This plugin brings Vizzly's powerful visual testing capabilities directly into Claude Code, helping you debug visual regressions, manage baselines, and integrate visual testing into your development workflow—all with AI assistance.
6
-
7
- ## Installation
8
-
9
- ### From GitHub (Recommended)
10
-
11
- ```bash
12
- # Add the marketplace
13
- /plugin marketplace add vizzly-testing/vizzly-cli
14
-
15
- # Install the plugin
16
- /plugin install vizzly@vizzly-marketplace
17
- ```
18
-
19
- ### From Local Source
20
-
21
- ```bash
22
- # Clone the repository
23
- git clone https://github.com/vizzly-testing/vizzly-cli.git
24
- cd vizzly-cli
25
-
26
- # Add the marketplace
27
- /plugin marketplace add ./.claude-plugin
28
-
29
- # Install the plugin
30
- /plugin install vizzly@vizzly-marketplace
31
- ```
32
-
33
- ## Features
34
-
35
- ### 🔍 **TDD Status Checking**
36
- - `/vizzly:tdd-status` - Check current TDD status and comparison results
37
- - See failed/new/passed screenshot counts
38
- - Direct links to diff images and dashboard
39
-
40
- ### 🐛 **Smart Diff Analysis**
41
- - `/vizzly:debug-diff <screenshot-name>` - Analyze visual regression failures
42
- - AI-powered analysis with contextual suggestions
43
- - Guidance on whether to accept or fix changes
44
-
45
- ### 💡 **Screenshot Suggestions**
46
- - `/vizzly:suggest-screenshots` - Analyze test files for screenshot opportunities
47
- - Framework-specific code examples
48
- - Respect your test structure and patterns
49
-
50
- ### ⚡ **Quick Setup**
51
- - `/vizzly:setup` - Initialize Vizzly configuration
52
- - Environment variable guidance
53
- - CI/CD integration help
54
-
55
- ## MCP Server Tools
56
-
57
- The plugin provides an MCP server with direct access to Vizzly data:
58
-
59
- ### Local TDD Tools
60
- - `detect_context` - Detect if using local TDD or cloud mode
61
- - `get_tdd_status` - Get current TDD comparison results
62
- - `read_comparison_details` - Detailed info for specific screenshot
63
- - `accept_baseline` - Accept a screenshot as new baseline
64
- - `reject_baseline` - Reject a baseline with reason
65
-
66
- ### Cloud API Tools
67
- - `list_recent_builds` - List recent builds with filtering
68
- - `get_build_status` - Get build status with commit context
69
- - `get_comparison` - Get comparison details with screenshots
70
- - `approve_comparison` - Approve a comparison with comment
71
- - `reject_comparison` - Reject a comparison with reason
72
- - `create_build_comment` - Add comment to build
73
-
74
- ## Authentication
75
-
76
- The plugin automatically uses your Vizzly authentication with the following priority:
77
-
78
- 1. **Explicitly provided token** (via tool parameters)
79
- 2. **Environment variable** (`VIZZLY_TOKEN`)
80
- 3. **Project mapping** (configured via `vizzly project:select`)
81
- 4. **User access token** (from `vizzly login`)
82
-
83
- ### Getting Started
84
-
85
- **For local development:**
86
- ```bash
87
- vizzly login # Authenticate with your Vizzly account
88
- vizzly project:select # Optional: set project-specific token
89
- ```
90
-
91
- **For CI/CD:**
92
- ```bash
93
- export VIZZLY_TOKEN=vzt_your_project_token
94
- ```
95
-
96
- The plugin will automatically use the appropriate token based on your context.
97
-
98
- ## Requirements
99
-
100
- - Claude Code
101
- - Node.js 20+
102
- - Vizzly CLI (`@vizzly-testing/cli`) installed in your project
103
- - TDD mode running for local features
104
- - Authentication configured (see above) for cloud features
105
-
106
- ## Documentation
107
-
108
- - [Vizzly CLI](https://github.com/vizzly-testing/vizzly-cli) - Official CLI documentation
109
- - [Vizzly Platform](https://vizzly.dev) - Web dashboard and cloud features
110
- - [Claude Code](https://claude.com/claude-code) - Claude Code documentation
111
-
112
- ## License
113
-
114
- MIT © Vizzly Team