@vizzly-testing/cli 0.33.0 → 0.34.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 +14 -0
- package/dist/cli.js +2 -2
- package/dist/commands/init.js +214 -2
- package/package.json +2 -1
- package/skills/vizzly/SKILL.md +99 -0
- package/skills/vizzly/agents/openai.yaml +4 -0
- package/skills/vizzly/references/cli-context.md +42 -0
- package/skills/vizzly/references/dynamic-content.md +44 -0
- package/skills/vizzly/references/public-screenshots.md +44 -0
- package/skills/vizzly/references/sdks.md +62 -0
- package/skills/vizzly/references/setup-ci.md +42 -0
package/README.md
CHANGED
|
@@ -28,6 +28,13 @@ pnpm install -g @vizzly-testing/cli
|
|
|
28
28
|
vizzly init
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
For agent-friendly repos, install the Vizzly skill and add a short project
|
|
32
|
+
`AGENTS.md` note:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
vizzly init --agent-guidance
|
|
36
|
+
```
|
|
37
|
+
|
|
31
38
|
### Start Local TDD
|
|
32
39
|
|
|
33
40
|
Start the TDD server, run your tests, and open the dashboard at
|
|
@@ -140,6 +147,13 @@ Generate a config file:
|
|
|
140
147
|
vizzly init
|
|
141
148
|
```
|
|
142
149
|
|
|
150
|
+
To teach project agents about Vizzly screenshot memory and the local visual TDD
|
|
151
|
+
loop, add the repo-local skill and AGENTS.md guidance:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
vizzly init --agent-guidance
|
|
155
|
+
```
|
|
156
|
+
|
|
143
157
|
Or create `vizzly.config.js` manually:
|
|
144
158
|
|
|
145
159
|
```javascript
|
package/dist/cli.js
CHANGED
|
@@ -303,8 +303,8 @@ try {
|
|
|
303
303
|
} catch (error) {
|
|
304
304
|
output.debug(`Plugin loading failed: ${error.message}`);
|
|
305
305
|
}
|
|
306
|
-
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').action(async options => {
|
|
307
|
-
|
|
306
|
+
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install the repo-local Vizzly agent skill').option('--agent-guidance', 'Add Vizzly guidance to this project AGENTS.md and install the agent skill').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
|
|
307
|
+
let globalOptions = program.opts();
|
|
308
308
|
await init({
|
|
309
309
|
...globalOptions,
|
|
310
310
|
...options,
|
package/dist/commands/init.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { z } from 'zod';
|
|
5
7
|
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
6
8
|
import { loadPlugins } from '../plugin-loader.js';
|
|
7
9
|
import { loadConfig } from '../utils/config-loader.js';
|
|
8
10
|
import * as output from '../utils/output.js';
|
|
11
|
+
let commandDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
12
|
let configValueSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(configValueSchema), z.record(z.string(), configValueSchema)]));
|
|
10
13
|
let configSchemaValidator = z.record(z.string(), configValueSchema);
|
|
11
14
|
function createInitDeps(deps = {}) {
|
|
12
15
|
return {
|
|
13
16
|
access: deps.access || fs.access,
|
|
17
|
+
copy: deps.copy || fs.cp,
|
|
14
18
|
cwd: deps.cwd || (() => process.cwd()),
|
|
19
|
+
isInteractive: deps.isInteractive || (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
|
|
15
20
|
loadConfig: deps.loadConfig || loadConfig,
|
|
16
21
|
loadPlugins: deps.loadPlugins || loadPlugins,
|
|
22
|
+
mkdir: deps.mkdir || fs.mkdir,
|
|
17
23
|
output: deps.output || output,
|
|
24
|
+
promptAgentSkill: deps.promptAgentSkill || promptAgentSkill,
|
|
25
|
+
readFile: deps.readFile || fs.readFile,
|
|
26
|
+
skillSourcePath: deps.skillSourcePath || getPackagedAgentSkillPath(),
|
|
18
27
|
writeFile: deps.writeFile || fs.writeFile
|
|
19
28
|
};
|
|
20
29
|
}
|
|
@@ -28,6 +37,15 @@ function configureOutput(output, options) {
|
|
|
28
37
|
export function getInitConfigPath(cwd = process.cwd()) {
|
|
29
38
|
return path.join(cwd, 'vizzly.config.js');
|
|
30
39
|
}
|
|
40
|
+
export function getPackagedAgentSkillPath(baseDir = commandDir) {
|
|
41
|
+
return path.resolve(baseDir, '..', '..', 'skills', 'vizzly');
|
|
42
|
+
}
|
|
43
|
+
export function getProjectAgentSkillPath(cwd = process.cwd()) {
|
|
44
|
+
return path.join(cwd, '.agents', 'skills', 'vizzly');
|
|
45
|
+
}
|
|
46
|
+
export function getProjectAgentsPath(cwd = process.cwd()) {
|
|
47
|
+
return path.join(cwd, 'AGENTS.md');
|
|
48
|
+
}
|
|
31
49
|
export async function fileExists(filePath, access = fs.access) {
|
|
32
50
|
try {
|
|
33
51
|
await access(filePath);
|
|
@@ -162,6 +180,169 @@ function showNextSteps(output) {
|
|
|
162
180
|
output.labelValue('Next steps', '');
|
|
163
181
|
output.list(['Set your API token: export VIZZLY_TOKEN="your-api-key"', 'Run your tests with Vizzly: pnpm exec vizzly run "pnpm test"', 'Upload screenshots: pnpm exec vizzly upload ./screenshots']);
|
|
164
182
|
}
|
|
183
|
+
async function promptAgentSkill({
|
|
184
|
+
stdin = process.stdin,
|
|
185
|
+
stdout = process.stdout
|
|
186
|
+
} = {}) {
|
|
187
|
+
let rl = createInterface({
|
|
188
|
+
input: stdin,
|
|
189
|
+
output: stdout
|
|
190
|
+
});
|
|
191
|
+
try {
|
|
192
|
+
let answer = await rl.question('Add the Vizzly agent skill to this repo? [Y/n] ');
|
|
193
|
+
return !['n', 'no'].includes(answer.trim().toLowerCase());
|
|
194
|
+
} finally {
|
|
195
|
+
rl.close();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function shouldInstallAgentSkill(options, deps) {
|
|
199
|
+
if (options.agentGuidance) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (options.skipAgentSkill) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
if (options.agentSkill) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (options.json || !deps.isInteractive()) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return await deps.promptAgentSkill();
|
|
212
|
+
}
|
|
213
|
+
let agentGuidanceStart = '<!-- vizzly-agent-guidance -->';
|
|
214
|
+
let agentGuidanceEnd = '<!-- /vizzly-agent-guidance -->';
|
|
215
|
+
export function createAgentGuidanceContent() {
|
|
216
|
+
return `${agentGuidanceStart}
|
|
217
|
+
## Visual Testing With Vizzly
|
|
218
|
+
|
|
219
|
+
Use Vizzly for user-facing UI changes when this repo has Vizzly configured.
|
|
220
|
+
|
|
221
|
+
- Read visual history before changing UI: \`npx vizzly context build current --source local --agent\`
|
|
222
|
+
- Inspect named screenshot history when useful: \`npx vizzly context screenshot "<screenshot-name>" --source local --json\`
|
|
223
|
+
- Verify visual changes through the existing user workflow that owns the surface: \`npx vizzly tdd run "<test command>" --no-open\`
|
|
224
|
+
|
|
225
|
+
Prefer existing E2E/user journeys over narrow screenshot-only specs. Treat Vizzly diffs as review evidence; do not approve or reject changes unless asked.
|
|
226
|
+
${agentGuidanceEnd}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
function appendSection(content, section) {
|
|
230
|
+
if (!content.trim()) {
|
|
231
|
+
return section;
|
|
232
|
+
}
|
|
233
|
+
return `${content.replace(/\s*$/, '')}\n\n${section}`;
|
|
234
|
+
}
|
|
235
|
+
export async function upsertProjectAgentGuidance({
|
|
236
|
+
cwd,
|
|
237
|
+
access = fs.access,
|
|
238
|
+
readFile = fs.readFile,
|
|
239
|
+
writeFile = fs.writeFile
|
|
240
|
+
}) {
|
|
241
|
+
let agentsPath = getProjectAgentsPath(cwd);
|
|
242
|
+
let guidance = createAgentGuidanceContent();
|
|
243
|
+
if (!(await fileExists(agentsPath, access))) {
|
|
244
|
+
await writeFile(agentsPath, guidance, 'utf8');
|
|
245
|
+
return {
|
|
246
|
+
status: 'created',
|
|
247
|
+
agentsPath
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
let existingContent = await readFile(agentsPath, 'utf8');
|
|
251
|
+
if (existingContent.includes(agentGuidanceStart)) {
|
|
252
|
+
return {
|
|
253
|
+
status: 'exists',
|
|
254
|
+
agentsPath
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
await writeFile(agentsPath, appendSection(existingContent, guidance), 'utf8');
|
|
258
|
+
return {
|
|
259
|
+
status: 'updated',
|
|
260
|
+
agentsPath
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export async function installProjectAgentSkill({
|
|
264
|
+
cwd,
|
|
265
|
+
sourcePath,
|
|
266
|
+
access = fs.access,
|
|
267
|
+
copy = fs.cp,
|
|
268
|
+
mkdir = fs.mkdir
|
|
269
|
+
}) {
|
|
270
|
+
let targetPath = getProjectAgentSkillPath(cwd);
|
|
271
|
+
if (!(await fileExists(path.join(sourcePath, 'SKILL.md'), access))) {
|
|
272
|
+
return {
|
|
273
|
+
status: 'missing-source',
|
|
274
|
+
sourcePath,
|
|
275
|
+
targetPath
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (await fileExists(targetPath, access)) {
|
|
279
|
+
return {
|
|
280
|
+
status: 'exists',
|
|
281
|
+
sourcePath,
|
|
282
|
+
targetPath
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
await mkdir(path.dirname(targetPath), {
|
|
286
|
+
recursive: true
|
|
287
|
+
});
|
|
288
|
+
await copy(sourcePath, targetPath, {
|
|
289
|
+
recursive: true,
|
|
290
|
+
errorOnExist: true,
|
|
291
|
+
force: false
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
status: 'installed',
|
|
295
|
+
sourcePath,
|
|
296
|
+
targetPath
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function writeAgentSkillOutput(output, result) {
|
|
300
|
+
if (!result) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (result.status === 'installed') {
|
|
304
|
+
output.complete('Added Vizzly agent skill');
|
|
305
|
+
output.hint(`Installed at ${result.targetPath}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (result.status === 'exists') {
|
|
309
|
+
output.hint('Vizzly agent skill already exists in this repo');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (result.status === 'missing-source') {
|
|
313
|
+
output.warn('Vizzly agent skill was not found in this CLI package');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function writeAgentGuidanceOutput(output, result) {
|
|
317
|
+
if (!result) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (result.status === 'created') {
|
|
321
|
+
output.complete('Created AGENTS.md with Vizzly guidance');
|
|
322
|
+
output.hint(`Wrote ${result.agentsPath}`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (result.status === 'updated') {
|
|
326
|
+
output.complete('Added Vizzly guidance to AGENTS.md');
|
|
327
|
+
output.hint(`Updated ${result.agentsPath}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (result.status === 'exists') {
|
|
331
|
+
output.hint('Vizzly guidance already exists in AGENTS.md');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function withAgentSetupResults(payload, {
|
|
335
|
+
agentSkillResult,
|
|
336
|
+
agentGuidanceResult
|
|
337
|
+
}) {
|
|
338
|
+
if (agentSkillResult) {
|
|
339
|
+
payload.agentSkill = agentSkillResult;
|
|
340
|
+
}
|
|
341
|
+
if (agentGuidanceResult) {
|
|
342
|
+
payload.agentGuidance = agentGuidanceResult;
|
|
343
|
+
}
|
|
344
|
+
return payload;
|
|
345
|
+
}
|
|
165
346
|
async function writeConfigFile({
|
|
166
347
|
configPath,
|
|
167
348
|
plugins,
|
|
@@ -197,14 +378,37 @@ export async function init(options = {}, deps = {}) {
|
|
|
197
378
|
let plugins = await loadInitPlugins(options, resolvedDeps);
|
|
198
379
|
let configPath = getInitConfigPath(resolvedDeps.cwd());
|
|
199
380
|
let hasConfig = await fileExists(configPath, resolvedDeps.access);
|
|
381
|
+
let agentSkillResult = null;
|
|
382
|
+
let agentGuidanceResult = null;
|
|
383
|
+
if (await shouldInstallAgentSkill(options, resolvedDeps)) {
|
|
384
|
+
agentSkillResult = await installProjectAgentSkill({
|
|
385
|
+
cwd: resolvedDeps.cwd(),
|
|
386
|
+
sourcePath: resolvedDeps.skillSourcePath,
|
|
387
|
+
access: resolvedDeps.access,
|
|
388
|
+
copy: resolvedDeps.copy,
|
|
389
|
+
mkdir: resolvedDeps.mkdir
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (options.agentGuidance) {
|
|
393
|
+
agentGuidanceResult = await upsertProjectAgentGuidance({
|
|
394
|
+
cwd: resolvedDeps.cwd(),
|
|
395
|
+
access: resolvedDeps.access,
|
|
396
|
+
readFile: resolvedDeps.readFile,
|
|
397
|
+
writeFile: resolvedDeps.writeFile
|
|
398
|
+
});
|
|
399
|
+
}
|
|
200
400
|
if (hasConfig && !options.force) {
|
|
201
401
|
if (options.json) {
|
|
202
|
-
|
|
402
|
+
let payload = withAgentSetupResults({
|
|
203
403
|
status: 'skipped',
|
|
204
404
|
reason: 'config_exists',
|
|
205
405
|
configPath,
|
|
206
406
|
message: 'A vizzly.config.js file already exists. Use --force to overwrite.'
|
|
407
|
+
}, {
|
|
408
|
+
agentSkillResult,
|
|
409
|
+
agentGuidanceResult
|
|
207
410
|
});
|
|
411
|
+
resolvedDeps.output.data(payload);
|
|
208
412
|
return {
|
|
209
413
|
status: 'skipped',
|
|
210
414
|
configPath
|
|
@@ -213,6 +417,8 @@ export async function init(options = {}, deps = {}) {
|
|
|
213
417
|
resolvedDeps.output.header('init');
|
|
214
418
|
resolvedDeps.output.warn('A vizzly.config.js file already exists');
|
|
215
419
|
resolvedDeps.output.hint('Use --force to overwrite');
|
|
420
|
+
writeAgentSkillOutput(resolvedDeps.output, agentSkillResult);
|
|
421
|
+
writeAgentGuidanceOutput(resolvedDeps.output, agentGuidanceResult);
|
|
216
422
|
return {
|
|
217
423
|
status: 'skipped',
|
|
218
424
|
configPath
|
|
@@ -228,11 +434,15 @@ export async function init(options = {}, deps = {}) {
|
|
|
228
434
|
});
|
|
229
435
|
let pluginNames = getPluginConfigNames(plugins);
|
|
230
436
|
if (options.json) {
|
|
231
|
-
|
|
437
|
+
let payload = withAgentSetupResults({
|
|
232
438
|
status: 'created',
|
|
233
439
|
configPath,
|
|
234
440
|
plugins: pluginNames
|
|
441
|
+
}, {
|
|
442
|
+
agentSkillResult,
|
|
443
|
+
agentGuidanceResult
|
|
235
444
|
});
|
|
445
|
+
resolvedDeps.output.data(payload);
|
|
236
446
|
return {
|
|
237
447
|
status: 'created',
|
|
238
448
|
configPath,
|
|
@@ -240,6 +450,8 @@ export async function init(options = {}, deps = {}) {
|
|
|
240
450
|
};
|
|
241
451
|
}
|
|
242
452
|
showNextSteps(resolvedDeps.output);
|
|
453
|
+
writeAgentSkillOutput(resolvedDeps.output, agentSkillResult);
|
|
454
|
+
writeAgentGuidanceOutput(resolvedDeps.output, agentGuidanceResult);
|
|
243
455
|
resolvedDeps.output.blank();
|
|
244
456
|
resolvedDeps.output.complete('Vizzly CLI setup complete');
|
|
245
457
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Visual regression testing from your terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm-context",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"bin",
|
|
56
56
|
"dist",
|
|
57
57
|
"docs/assets",
|
|
58
|
+
"skills",
|
|
58
59
|
"README.md",
|
|
59
60
|
"LICENSE"
|
|
60
61
|
],
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vizzly
|
|
3
|
+
description: "Use when a repo has Vizzly configured and you need screenshot memory or visual history: before/after UI changes, visual regression review, screenshot history, approved baselines, diffs, dynamic regions, docs/manual images, public screenshots, or Vizzly builds. Teaches agents to use Vizzly CLI/context for local TDD, cloud builds, review state, comments, hotspots, previews, and SDK capture patterns."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Vizzly
|
|
7
|
+
|
|
8
|
+
Vizzly is the project's screenshot memory database. It stores approved baselines, current screenshots, diffs, review state, comments, hotspots, previews, and public screenshot URLs.
|
|
9
|
+
|
|
10
|
+
Use Vizzly when you need to understand what the UI looked like before, what changed, what humans already reviewed, or which screenshots already exist. Browser automation is still useful for interacting with a live app; Vizzly is usually the faster first stop for visual history.
|
|
11
|
+
|
|
12
|
+
## First Instinct
|
|
13
|
+
|
|
14
|
+
Before changing UI, check whether Vizzly already has local screenshot context:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
vizzly context build current --source local --agent
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If a task names a screen, component, or screenshot, inspect that screenshot's history before changing thresholds or re-capturing blindly:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
vizzly context screenshot "<screenshot-name>" --source local --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
When you make or verify UI changes, run the focused user workflow that owns the surface:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
vizzly tdd run "<test command>" --no-open
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then inspect what changed:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
vizzly context build current --source local --agent --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For specific evidence, drill in:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
vizzly context comparison <comparison-id> --json
|
|
42
|
+
vizzly context screenshot "<screenshot-name>" --json
|
|
43
|
+
vizzly context review-queue --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If local context is unavailable and the project uses cloud builds, use the build id from CI or CLI output:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
vizzly context build <build-id> --agent --json --include diffs,comments
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What Vizzly Knows
|
|
53
|
+
|
|
54
|
+
- **Approved baselines**: expected UI.
|
|
55
|
+
- **Current screenshots**: what the latest run rendered.
|
|
56
|
+
- **Diffs**: where pixels/layout/content changed.
|
|
57
|
+
- **Review state and comments**: human context attached to builds and screenshots.
|
|
58
|
+
- **Hotspots and confirmed regions**: known dynamic areas.
|
|
59
|
+
- **Preview links**: static or deployed UI context for a build.
|
|
60
|
+
- **Public screenshots**: stable URLs for documentation and manuals.
|
|
61
|
+
|
|
62
|
+
## Acting On Visual Context
|
|
63
|
+
|
|
64
|
+
- Treat approved baselines as visual truth.
|
|
65
|
+
- Treat diffs as evidence, not as approval instructions.
|
|
66
|
+
- Do not approve or reject visual changes unless the user explicitly asks.
|
|
67
|
+
- Prefer existing E2E or user journeys over narrow screenshot-only specs.
|
|
68
|
+
- For dynamic content, inspect screenshot context before changing thresholds.
|
|
69
|
+
- Prefer deterministic test data, per-screenshot `threshold`, per-screenshot `minClusterSize`, hotspots, or confirmed regions over global tolerance changes.
|
|
70
|
+
- Report visual findings with screenshot names, build/comparison links when available, and the command you ran.
|
|
71
|
+
|
|
72
|
+
## Capturing Screenshots
|
|
73
|
+
|
|
74
|
+
Use the existing integration when one is present. For direct JavaScript capture:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
78
|
+
|
|
79
|
+
let screenshot = await page.screenshot();
|
|
80
|
+
await vizzlyScreenshot('checkout-form', screenshot, {
|
|
81
|
+
properties: {
|
|
82
|
+
browser: 'chromium',
|
|
83
|
+
viewport: 'desktop',
|
|
84
|
+
state: 'valid-card'
|
|
85
|
+
},
|
|
86
|
+
threshold: 2,
|
|
87
|
+
minClusterSize: 4
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use `properties` to separate variants such as theme, locale, viewport, role, state, component, page, or docs/manual grouping.
|
|
92
|
+
|
|
93
|
+
## When You Need More Detail
|
|
94
|
+
|
|
95
|
+
- For SDK examples and capture patterns, read `references/sdks.md`.
|
|
96
|
+
- For CLI/context commands and JSON output, read `references/cli-context.md`.
|
|
97
|
+
- For dynamic content, thresholds, and hotspots, read `references/dynamic-content.md`.
|
|
98
|
+
- For public screenshot URLs and docs/manual images, read `references/public-screenshots.md`.
|
|
99
|
+
- For project setup and CI, read `references/setup-ci.md`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# CLI And Context
|
|
2
|
+
|
|
3
|
+
Use these commands to gather Vizzly evidence before making UI assumptions.
|
|
4
|
+
|
|
5
|
+
## Local TDD
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
vizzly tdd start
|
|
9
|
+
vizzly tdd run "<test command>" --no-open
|
|
10
|
+
vizzly context build current --source local --agent
|
|
11
|
+
vizzly context build current --source local --agent --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Local context reads `.vizzly` state and does not require cloud auth.
|
|
15
|
+
|
|
16
|
+
## Cloud Builds
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
export VIZZLY_TOKEN="project-token"
|
|
20
|
+
vizzly run "<test command>" --wait --json
|
|
21
|
+
vizzly context build <build-id> --agent --json --include diffs,comments
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`vizzly run --wait --json` returns a `contextCommand` when a cloud build is created. Prefer that command over constructing URLs by hand.
|
|
25
|
+
|
|
26
|
+
## Drilldowns
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
vizzly context comparison <comparison-id> --json
|
|
30
|
+
vizzly context screenshot "<screenshot-name>" --json
|
|
31
|
+
vizzly context similar <fingerprint-hash> --json
|
|
32
|
+
vizzly context review-queue --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use comparison context for one diff. Use screenshot context for history and recurring dynamic areas. Use review queue context when triaging unresolved visual work.
|
|
36
|
+
|
|
37
|
+
## Good Agent Behavior
|
|
38
|
+
|
|
39
|
+
- Use `--json` for automation and summaries.
|
|
40
|
+
- Use `--agent` when building prompt context or asking another agent to continue.
|
|
41
|
+
- Keep human-readable command output in final summaries only when it changes the user's next action.
|
|
42
|
+
- Do not approve/reject unless explicitly asked.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Dynamic Content
|
|
2
|
+
|
|
3
|
+
Dynamic content is common: dates, calendars, generated images, avatars, timers, randomized data, responsive text, and API-backed content.
|
|
4
|
+
|
|
5
|
+
## Order Of Operations
|
|
6
|
+
|
|
7
|
+
1. Make test data deterministic when the changing content is not what you are testing.
|
|
8
|
+
2. Inspect screenshot context before changing code:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
vizzly context screenshot "<screenshot-name>" --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. Use per-screenshot tolerances for local noise:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
await vizzlyScreenshot('plant-calendar', screenshot, {
|
|
18
|
+
properties: { surface: 'calendar' },
|
|
19
|
+
threshold: 4,
|
|
20
|
+
minClusterSize: 12
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
4. Use hotspots or confirmed regions for recurring valid dynamic regions.
|
|
25
|
+
|
|
26
|
+
## Avoid
|
|
27
|
+
|
|
28
|
+
- Raising global project thresholds for one dynamic area.
|
|
29
|
+
- Masking a whole page when a small region changes.
|
|
30
|
+
- Treating every daily diff as product breakage.
|
|
31
|
+
- Ignoring structural changes because a region is known to be dynamic.
|
|
32
|
+
|
|
33
|
+
## How To Explain Findings
|
|
34
|
+
|
|
35
|
+
Say whether the diff looks like:
|
|
36
|
+
|
|
37
|
+
- expected dynamic content
|
|
38
|
+
- deterministic fixture drift
|
|
39
|
+
- real content disappearance
|
|
40
|
+
- layout shift
|
|
41
|
+
- screenshot timing/capture instability
|
|
42
|
+
- baseline mismatch
|
|
43
|
+
|
|
44
|
+
Then name the screenshot and include the relevant context command or link.
|
|
@@ -0,0 +1,44 @@
|
|
|
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.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# SDK Capture Patterns
|
|
2
|
+
|
|
3
|
+
Vizzly works best when screenshots are attached to real user journeys and stable names.
|
|
4
|
+
|
|
5
|
+
## JavaScript Client
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
9
|
+
|
|
10
|
+
let screenshot = await page.screenshot();
|
|
11
|
+
await vizzlyScreenshot('settings-profile-edit-mode', screenshot, {
|
|
12
|
+
properties: {
|
|
13
|
+
browser: 'chromium',
|
|
14
|
+
viewport: 'desktop',
|
|
15
|
+
state: 'edit'
|
|
16
|
+
},
|
|
17
|
+
threshold: 2,
|
|
18
|
+
minClusterSize: 4
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`vizzlyScreenshot(name, image, options)` accepts a PNG buffer or file path.
|
|
23
|
+
|
|
24
|
+
Important options:
|
|
25
|
+
|
|
26
|
+
- `properties`: metadata used for baseline identity and filtering.
|
|
27
|
+
- `threshold`: per-screenshot CIEDE2000 Delta E tolerance.
|
|
28
|
+
- `minClusterSize`: minimum changed-pixel cluster size.
|
|
29
|
+
- `fullPage`: marks full-page captures.
|
|
30
|
+
|
|
31
|
+
## Vitest
|
|
32
|
+
|
|
33
|
+
Use the Vizzly Vitest plugin when present. It keeps the native `toMatchScreenshot` style:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
await expect(page.getByRole('heading')).toMatchScreenshot('hero-section.png', {
|
|
37
|
+
properties: {
|
|
38
|
+
theme: 'dark',
|
|
39
|
+
viewport: 'desktop'
|
|
40
|
+
},
|
|
41
|
+
threshold: 2,
|
|
42
|
+
fullPage: true
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Storybook And Static Sites
|
|
47
|
+
|
|
48
|
+
If the repo uses Vizzly Storybook or static-site clients, prefer those existing flows over adding custom Playwright screenshots. They already know how to crawl stories/pages, name screenshots, and attach viewport metadata.
|
|
49
|
+
|
|
50
|
+
## Swift
|
|
51
|
+
|
|
52
|
+
Swift/XCTest projects use `VizzlyXCTest` helpers such as `app.vizzlyScreenshot(name:properties:threshold:minClusterSize:)`. For iOS work, verify against the Swift package docs in the repo before changing examples.
|
|
53
|
+
|
|
54
|
+
## Naming
|
|
55
|
+
|
|
56
|
+
Use stable, descriptive screenshot names:
|
|
57
|
+
|
|
58
|
+
- Good: `checkout-payment-form-valid-card`
|
|
59
|
+
- Good: `settings-profile-edit-mode`
|
|
60
|
+
- Avoid: `screenshot1`, `test`, names with slashes
|
|
61
|
+
|
|
62
|
+
Use properties for variants instead of stuffing every variant into the name.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Setup And CI
|
|
2
|
+
|
|
3
|
+
Use Vizzly locally for fast feedback and in CI for shared review.
|
|
4
|
+
|
|
5
|
+
## Local Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
vizzly init
|
|
9
|
+
vizzly tdd start
|
|
10
|
+
vizzly tdd run "<test command>" --no-open
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The local SDK discovers `.vizzly/server.json` and sends screenshots to the TDD server when it is running.
|
|
14
|
+
|
|
15
|
+
## Cloud Setup
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export VIZZLY_TOKEN="project-token"
|
|
19
|
+
vizzly run "<test command>" --wait
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use project tokens in CI. Avoid committing tokens.
|
|
23
|
+
|
|
24
|
+
## Parallel CI
|
|
25
|
+
|
|
26
|
+
Use `--parallel-id` when jobs are sharded:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
vizzly run "npm test -- --shard=1/4" --parallel-id shard-1 --wait
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Keep fast PR checks and broad scheduled checks separate:
|
|
33
|
+
|
|
34
|
+
- PR builds: critical flows and changed surfaces.
|
|
35
|
+
- Nightly builds: full-page sweeps, generated image suites, docs/manual captures.
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
- Run `vizzly doctor` for local configuration checks.
|
|
40
|
+
- Run `vizzly status <build-id>` for cloud build status.
|
|
41
|
+
- Run `vizzly context build <build-id> --agent --json` for review evidence.
|
|
42
|
+
- If no screenshots appear, verify the SDK/import is present and the TDD server or `vizzly run` wrapper is active.
|