openspec-playwright 0.3.47 → 0.3.48

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.
@@ -1,619 +0,0 @@
1
- /**
2
- * Ollama API utilities for vision model integration.
3
- *
4
- * Configuration: reads from `.env` in `tests/playwright/` (highest priority),
5
- * then falls back to environment variables. If no URL or model is found,
6
- * vision check is disabled.
7
- */
8
- import { config as loadEnv } from "dotenv";
9
- import { createRequire } from "module";
10
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
11
- import { join, dirname, basename } from "path";
12
- import { createHash } from "crypto";
13
- const _require = createRequire(import.meta.url);
14
- // ── Config loading ────────────────────────────────────────────────────────────
15
- /**
16
- * Load Ollama config from `.env` in `tests/playwright/` and environment variables.
17
- * If no URL or model is configured, vision check is disabled.
18
- */
19
- export function loadOllamaConfig(projectRoot = process.cwd()) {
20
- // Lazy load .env from tests/playwright/ (falls back gracefully if missing)
21
- // Avoid path duplication if projectRoot is already inside tests/playwright/
22
- const normRoot = projectRoot.replace(/[\/\\]+$/, "");
23
- const envPath = normRoot.match(/[\/\\]tests[\/\\]playwright$/)
24
- ? join(normRoot, ".env") // already inside tests/playwright/
25
- : join(normRoot, "tests", "playwright", ".env");
26
- if (existsSync(envPath)) {
27
- loadEnv({ path: envPath });
28
- }
29
- const url = process.env.OLLAMA_URL;
30
- const model = process.env.OLLAMA_VISION_MODEL;
31
- // If no url or model configured, disable vision check
32
- if (!url || !model) {
33
- return { url: "", model: "", enabled: false };
34
- }
35
- // Enabled: default true unless explicitly disabled
36
- const enabled = process.env.OLLAMA_VISION_ENABLED === undefined ||
37
- process.env.OLLAMA_VISION_ENABLED === "true";
38
- return { url, model, enabled };
39
- }
40
- // ── Health check ──────────────────────────────────────────────────────────────
41
- /**
42
- * Check if Ollama is running and the vision model is available.
43
- * Returns { ok, message } for doctor output.
44
- */
45
- export async function checkOllamaHealth(config) {
46
- if (!config.enabled) {
47
- return { ok: false, message: "disabled in config" };
48
- }
49
- try {
50
- // Check Ollama server
51
- const controller = new AbortController();
52
- const timeout = setTimeout(() => controller.abort(), 5000);
53
- const response = await fetch(`${config.url}/api/tags`, {
54
- signal: controller.signal,
55
- });
56
- clearTimeout(timeout);
57
- if (!response.ok) {
58
- return { ok: false, message: `server error: ${response.status}` };
59
- }
60
- const data = (await response.json());
61
- const models = data.models || [];
62
- const modelNames = models.map((m) => m.name);
63
- // Check if vision model exists (exact match or prefix match)
64
- const hasModel = modelNames.some((name) => name === config.model ||
65
- name.startsWith(`${config.model}:`) ||
66
- name === `${config.model}:latest`);
67
- if (!hasModel) {
68
- return {
69
- ok: false,
70
- message: `model "${config.model}" not found (available: ${modelNames.slice(0, 3).join(", ")}...)`,
71
- };
72
- }
73
- return { ok: true, message: `${config.url} (${config.model})` };
74
- }
75
- catch (err) {
76
- const error = err;
77
- if (error.name === "AbortError") {
78
- return { ok: false, message: "connection timeout" };
79
- }
80
- return { ok: false, message: `not reachable: ${error.message}` };
81
- }
82
- }
83
- // ── Vision analysis ───────────────────────────────────────────────────────────
84
- const VISION_PROMPT_ZH = [
85
- "你是专业的UI测试工程师,以普通用户视角审视这张截图。",
86
- "",
87
- "【检测目标】用户可感知的功能性UI问题,不包括:",
88
- "- 装饰性图标、logo的细微偏差",
89
- "- CSS hover/transition动画状态",
90
- "- 浏览器默认样式(滚动条、右键菜单)",
91
- "- 动态广告/推荐内容(如能正常交互则不报错)",
92
- "",
93
- "【问题类型】",
94
- "1. obscured — 可交互元素被遮挡/覆盖,无法点击",
95
- "2. crowded — 元素间距过窄、文字重叠、难以区分",
96
- "3. overflowed — 内容被裁剪,关键信息不可见",
97
- "4. missing — 明显应该存在的元素完全消失",
98
- "5. incorrect — 文本内容明显错误、样式完全错误",
99
- "",
100
- "【严重程度】",
101
- "- blocking:核心功能不可用(如提交按钮被遮挡)",
102
- "- warning:影响使用体验(如重要文字被截断)",
103
- "- minor:细微视觉问题(图标轻微偏移)",
104
- "",
105
- "【定位】用屏幕区域:左上/上/右上/左/中/右/左下/下/右下",
106
- "",
107
- '返回JSON格式,不要包含任何其他文字:',
108
- '{"anomalies":[]}',
109
- "",
110
- '如果一切正常,返回:{"anomalies":[]}',
111
- ].join("\n");
112
- const VISION_PROMPT_EN = [
113
- "You are a professional UI test engineer reviewing this screenshot from a user's perspective.",
114
- "",
115
- "Detect user-facing functional UI issues. Exclude:",
116
- "- Decorative icon/logo minor offsets",
117
- "- CSS hover/transition animation states",
118
- "- Browser default styles (scrollbars, context menus)",
119
- "- Dynamic ads/recommendations (if interactive, not an issue)",
120
- "",
121
- "Issue types:",
122
- "1. obscured — Interactive element covered/overlapped, cannot click",
123
- "2. crowded — Elements too close, text overlap, hard to distinguish",
124
- "3. overflowed — Content clipped, key info not visible",
125
- "4. missing — Element that should clearly exist is gone",
126
- "5. incorrect — Clearly wrong text content or styling",
127
- "",
128
- "Severity:",
129
- "- blocking — Core function unusable (e.g. submit button covered)",
130
- "- warning — Affects usability (e.g. important text truncated)",
131
- "- minor — Minor visual issue (icon slightly offset)",
132
- "",
133
- "Position: use screen region: top-left/top/top-right/left/center/right/bottom-left/bottom/bottom-right",
134
- "",
135
- 'Return JSON only, no other text:',
136
- '{"anomalies":[]}',
137
- "",
138
- 'If everything looks fine: {"anomalies":[]}',
139
- ].join("\n");
140
- /** Check if model name indicates a Chinese-language model */
141
- function isZhModel(model) {
142
- const zhModels = ["qwen", "yi", "chatglm", "deepseek", "internvl"];
143
- return zhModels.some((m) => model.toLowerCase().includes(m));
144
- }
145
- /** Select VISION_PROMPT based on model name (Chinese models → Chinese prompt) */
146
- function selectVisionPrompt(model) {
147
- return isZhModel(model) ? VISION_PROMPT_ZH : VISION_PROMPT_EN;
148
- }
149
- /** Select DIFF_PROMPT based on model name */
150
- function selectDiffPrompt(model) {
151
- return isZhModel(model) ? DIFF_PROMPT_ZH : DIFF_PROMPT_EN;
152
- }
153
- // ── Retry helper ──────────────────────────────────────────────────────────────
154
- async function fetchWithRetry(url, options, maxRetries = 3) {
155
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
156
- try {
157
- const controller = new AbortController();
158
- const timeout = setTimeout(() => controller.abort(), 60000);
159
- const response = await fetch(url, { ...options, signal: controller.signal });
160
- clearTimeout(timeout);
161
- if (response.ok || response.status < 500)
162
- return response;
163
- // 5xx → retry
164
- }
165
- catch (err) {
166
- if (attempt === maxRetries)
167
- throw err;
168
- }
169
- // Exponential backoff: 1s, 2s, 4s
170
- await new Promise((r) => setTimeout(r, 1000 * 2 ** (attempt - 1)));
171
- }
172
- throw new Error(`Ollama API: ${maxRetries} retries exhausted`);
173
- }
174
- // ── Result cache ──────────────────────────────────────────────────────────────
175
- const VISION_CACHE_DIR = ".openspec-pw/vision-cache";
176
- function screenshotHash(filePath) {
177
- const buf = readFileSync(filePath);
178
- return createHash("sha256").update(buf).digest("hex").slice(0, 16);
179
- }
180
- function getCachedResult(fileHash, model, promptHash) {
181
- const cachePath = join(process.cwd(), VISION_CACHE_DIR, `${fileHash}_${model.replace(/[:/]/g, "_")}_${promptHash}.json`);
182
- if (existsSync(cachePath)) {
183
- try {
184
- return JSON.parse(readFileSync(cachePath, "utf-8"));
185
- }
186
- catch {
187
- return null;
188
- }
189
- }
190
- return null;
191
- }
192
- function setCachedResult(fileHash, model, promptHash, anomalies) {
193
- const cacheDir = join(process.cwd(), VISION_CACHE_DIR);
194
- mkdirSync(cacheDir, { recursive: true });
195
- const cachePath = join(cacheDir, `${fileHash}_${model.replace(/[:/]/g, "_")}_${promptHash}.json`);
196
- writeFileSync(cachePath, JSON.stringify(anomalies), "utf-8");
197
- }
198
- /**
199
- * Analyze a single screenshot with the vision model.
200
- */
201
- export async function analyzeScreenshot(config, screenshotPath, route, prompt, noCache) {
202
- if (!existsSync(screenshotPath)) {
203
- throw new Error(`Screenshot not found: ${screenshotPath}`);
204
- }
205
- const effectivePrompt = prompt || selectVisionPrompt(config.model);
206
- const promptHash = createHash("sha256").update(effectivePrompt).digest("hex").slice(0, 8);
207
- const fileHash = screenshotHash(screenshotPath);
208
- // Check cache
209
- if (!noCache) {
210
- const cached = getCachedResult(fileHash, config.model, promptHash);
211
- if (cached) {
212
- return cached.map((a) => ({ ...a, route, screenshot: screenshotPath }));
213
- }
214
- }
215
- const imageBase64 = readFileSync(screenshotPath).toString("base64");
216
- const response = await fetchWithRetry(`${config.url}/api/generate`, {
217
- method: "POST",
218
- headers: { "Content-Type": "application/json" },
219
- body: JSON.stringify({
220
- model: config.model,
221
- prompt: effectivePrompt,
222
- images: [imageBase64],
223
- stream: false,
224
- }),
225
- });
226
- if (!response.ok) {
227
- throw new Error(`Ollama API error: ${response.status}`);
228
- }
229
- const data = (await response.json());
230
- const responseText = data.response || "";
231
- // Parse JSON from response (may have markdown code block)
232
- let jsonStr = responseText;
233
- const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
234
- if (jsonMatch) {
235
- jsonStr = jsonMatch[1];
236
- }
237
- try {
238
- const parsed = JSON.parse(jsonStr.trim());
239
- const anomalies = parsed.anomalies || [];
240
- // Cache result (without route/screenshot to keep cache portable)
241
- if (!noCache) {
242
- setCachedResult(fileHash, config.model, promptHash, anomalies);
243
- }
244
- // Enrich with route and screenshot path
245
- return anomalies.map((a) => ({
246
- ...a,
247
- route,
248
- screenshot: screenshotPath,
249
- }));
250
- }
251
- catch {
252
- // If JSON parse fails, return empty (graceful degradation)
253
- console.error(`Warning: Could not parse vision response for ${route}`);
254
- return [];
255
- }
256
- }
257
- /**
258
- * Batch analyze multiple screenshots with concurrency control.
259
- */
260
- export async function batchAnalyzeScreenshots(config, screenshots, concurrency = 4, noCache) {
261
- const result = {
262
- processed: 0,
263
- anomalies: [],
264
- skipped: [],
265
- ollamaUrl: config.url,
266
- model: config.model,
267
- };
268
- // Process in batches
269
- for (let i = 0; i < screenshots.length; i += concurrency) {
270
- const batch = screenshots.slice(i, i + concurrency);
271
- const promises = batch.map(async ({ path, route }) => {
272
- try {
273
- const anomalies = await analyzeScreenshot(config, path, route, undefined, noCache);
274
- result.processed++;
275
- result.anomalies.push(...anomalies);
276
- }
277
- catch (err) {
278
- const error = err;
279
- console.error(`Warning: Skipped ${route}: ${error.message}`);
280
- result.skipped.push(path);
281
- }
282
- });
283
- await Promise.allSettled(promises);
284
- }
285
- return result;
286
- }
287
- // ── Pixel diff ───────────────────────────────────────────────────────────────
288
- const DIFF_PROMPT_ZH = [
289
- "你是专业的UI回归测试工程师。这张图是像素差异图,红色高亮区域表示与基准版不同。",
290
- "",
291
- "请分析红色高亮区域,检测:",
292
- "1. 哪些变化是真实的UI bug(如元素被遮挡、布局错乱、内容丢失)",
293
- "2. 哪些变化是正常的(如动态内容更新、动画状态变化)",
294
- "",
295
- "【忽略】动态广告/推荐内容、时间戳、数值变化、hover/focus状态、第三方嵌入内容的变化。",
296
- "",
297
- "【问题类型】",
298
- "- obscured:可交互元素被遮挡,无法点击",
299
- "- crowded:元素间距错乱、文字重叠",
300
- "- overflowed:内容被裁剪,关键信息不可见",
301
- "- missing:原有元素消失",
302
- "- incorrect:样式/内容明显错误",
303
- "",
304
- "【严重程度】",
305
- "- blocking:核心功能不可用",
306
- "- warning:影响使用体验",
307
- "- minor:细微视觉回归",
308
- "",
309
- '返回JSON(不要包含任何其他文字):',
310
- '{"anomalies":[]}',
311
- ].join("\n");
312
- const DIFF_PROMPT_EN = [
313
- "You are a professional UI regression test engineer. This image is a pixel diff map where red highlighted areas differ from the baseline.",
314
- "",
315
- "Analyze the red highlighted areas to determine:",
316
- "1. Which changes are real UI bugs (e.g. elements obscured, layout broken, content lost)",
317
- "2. Which changes are normal (e.g. dynamic content updates, animation state changes)",
318
- "",
319
- "Ignore: Dynamic ads/recommendations, timestamps, numerical changes, hover/focus states, third-party embedded content changes.",
320
- "",
321
- "Issue types:",
322
- "- obscured: Interactive element covered, cannot click",
323
- "- crowded: Element spacing broken, text overlap",
324
- "- overflowed: Content clipped, key info not visible",
325
- "- missing: Previously existing element disappeared",
326
- "- incorrect: Clearly wrong styling/content",
327
- "",
328
- "Severity:",
329
- "- blocking: Core function unusable",
330
- "- warning: Affects usability",
331
- "- minor: Minor visual regression",
332
- "",
333
- 'Return JSON only, no other text:',
334
- '{"anomalies":[]}',
335
- ].join("\n");
336
- /**
337
- * Compare two screenshots with pixel diff.
338
- * Returns changed regions and optionally saves a diff image.
339
- */
340
- export async function compareScreenshotDiff(config, baselinePath, currentPath, diffPath, route, threshold = 0.1) {
341
- const pixelmatch = _require("pixelmatch");
342
- const { PNG } = _require("pngjs");
343
- if (!existsSync(baselinePath)) {
344
- throw new Error("Baseline screenshot not found: " + baselinePath);
345
- }
346
- if (!existsSync(currentPath)) {
347
- throw new Error("Current screenshot not found: " + currentPath);
348
- }
349
- const img1 = PNG.sync.read(readFileSync(baselinePath));
350
- const img2 = PNG.sync.read(readFileSync(currentPath));
351
- if (img1.width !== img2.width || img1.height !== img2.height) {
352
- console.warn(`Warning: Dimension mismatch (${img1.width}x${img1.height} vs ${img2.width}x${img2.height}) — skipping pixel diff for ${route}`);
353
- return { changed: true, anomalies: [] };
354
- }
355
- const diff = new PNG(img1.width, img1.height);
356
- const changedPixels = pixelmatch(img1.data, img2.data, diff.data, img1.width, img1.height, { threshold });
357
- const changedRatio = changedPixels / (img1.width * img1.height);
358
- // Save diff image + keep buffer for VLM
359
- const diffBuf = PNG.sync.write(diff);
360
- writeFileSync(diffPath, diffBuf);
361
- // If no meaningful changes, skip VLM analysis
362
- if (changedRatio < 0.001) {
363
- return { changed: false, anomalies: [] };
364
- }
365
- // Analyze diff image with VLM (reuse buffer, no re-read from disk)
366
- const diffB64 = diffBuf.toString("base64");
367
- const response = await fetchWithRetry(`${config.url}/api/generate`, {
368
- method: "POST",
369
- headers: { "Content-Type": "application/json" },
370
- body: JSON.stringify({
371
- model: config.model,
372
- prompt: selectDiffPrompt(config.model),
373
- images: [diffB64],
374
- stream: false,
375
- }),
376
- });
377
- if (!response.ok) {
378
- throw new Error(`Ollama API error: ${response.status}`);
379
- }
380
- const data = (await response.json());
381
- const responseText = data.response || "";
382
- let jsonStr = responseText;
383
- const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
384
- if (jsonMatch) {
385
- jsonStr = jsonMatch[1];
386
- }
387
- try {
388
- const parsed = JSON.parse(jsonStr.trim());
389
- const anomalies = (parsed.anomalies || []).map((a) => ({
390
- ...a,
391
- route,
392
- screenshot: currentPath,
393
- changed: true,
394
- }));
395
- return { changed: true, anomalies };
396
- }
397
- catch {
398
- console.error(`Warning: Could not parse diff response for ${route}`);
399
- return { changed: true, anomalies: [] };
400
- }
401
- }
402
- // ── HTML Report ─────────────────────────────────────────────────────────────
403
- /**
404
- * Generate a self-contained HTML report with embedded screenshots and results.
405
- * If diffDir is provided, diff images are shown alongside current screenshots.
406
- */
407
- function escapeHtml(str) {
408
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
409
- }
410
- export function generateHtmlReport(result, screenshots, outputPath, diffDir) {
411
- const timestamp = new Date().toISOString();
412
- // Group anomalies by severity
413
- const bySeverity = {
414
- blocking: [],
415
- warning: [],
416
- minor: [],
417
- };
418
- for (const a of result.anomalies) {
419
- const key = a.severity || "minor";
420
- if (bySeverity[key])
421
- bySeverity[key].push(a);
422
- }
423
- // Build screenshot cards — use relative file paths to keep report size manageable
424
- const outputDir = dirname(outputPath);
425
- const usedNames = new Set();
426
- const screenshotCards = screenshots
427
- .map((s) => {
428
- // Copy screenshots next to the report for portability
429
- const originalBasename = basename(s.path, ".png");
430
- let imgName = basename(s.path);
431
- // Avoid overwriting same-named files from different directories
432
- if (usedNames.has(imgName)) {
433
- const ext = imgName.includes(".") ? `.${imgName.split(".").pop()}` : "";
434
- const base = ext ? imgName.slice(0, -ext.length) : imgName;
435
- let idx = 2;
436
- while (usedNames.has(`${base}-${idx}${ext}`))
437
- idx++;
438
- imgName = `${base}-${idx}${ext}`;
439
- }
440
- usedNames.add(imgName);
441
- const imgDest = join(outputDir, imgName);
442
- if (!existsSync(imgDest)) {
443
- writeFileSync(imgDest, readFileSync(s.path));
444
- }
445
- const imgSrc = `./${imgName}`;
446
- // Check for diff image — use original basename (not deduplicated imgName)
447
- let diffSrc = "";
448
- let hasDiff = false;
449
- if (diffDir) {
450
- const diffName = `${originalBasename}-diff.png`;
451
- const diffPath = join(diffDir, diffName);
452
- if (existsSync(diffPath)) {
453
- const diffDest = join(outputDir, diffName);
454
- if (!existsSync(diffDest)) {
455
- writeFileSync(diffDest, readFileSync(diffPath));
456
- }
457
- diffSrc = `./${diffName}`;
458
- hasDiff = true;
459
- }
460
- }
461
- const anomalies = result.anomalies.filter((a) => a.screenshot === s.path);
462
- const badge = anomalies.length > 0
463
- ? `<span class="badge badge-${anomalies[0].severity}">${anomalies.length} issue${anomalies.length > 1 ? "s" : ""}</span>`
464
- : `<span class="badge badge-ok">OK</span>`;
465
- const anomalyList = anomalies.length > 0
466
- ? `<div class="anomaly-list">
467
- ${anomalies
468
- .map((a) => ` <div class="anomaly-item severity-${a.severity}">
469
- <div class="anomaly-header">
470
- <span class="type-badge">${a.type}</span>
471
- <span class="severity-badge ${a.severity}">${a.severity}</span>
472
- ${a.viewport ? `<span class="viewport-badge">${escapeHtml(a.viewport)}</span>` : ""}
473
- ${a.changed ? `<span class="diff-badge">changed</span>` : ""}
474
- </div>
475
- <div class="anomaly-body">
476
- <strong>${escapeHtml(a.element)}</strong>
477
- <p>${escapeHtml(a.description)}</p>
478
- <div class="anomaly-meta">${escapeHtml(a.position)}</div>
479
- </div>
480
- </div>`)
481
- .join("\n")}
482
- </div>`
483
- : "";
484
- const diffSection = hasDiff
485
- ? `<div class="card-diff"><div class="diff-label">Diff</div><img src="${diffSrc}" alt="diff" /></div>`
486
- : "";
487
- const currentSection = `<div class="card-img"><div class="img-label">Current</div><img src="${imgSrc}" alt="${s.route}" /></div>`;
488
- const comparisonSection = hasDiff
489
- ? `<div class="card-comparison">${currentSection}${diffSection}</div>`
490
- : currentSection;
491
- return ` <div class="screenshot-card ${anomalies.length > 0 ? "has-issues" : ""}">
492
- <div class="card-header">
493
- <span class="route">${s.route}</span>
494
- ${badge}
495
- </div>
496
- <div class="card-body">${comparisonSection}</div>
497
- ${anomalyList}
498
- </div>`;
499
- })
500
- .join("\n");
501
- const html = `<!DOCTYPE html>
502
- <html lang="en">
503
- <head>
504
- <meta charset="UTF-8">
505
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
506
- <title>Vision Check Report</title>
507
- <style>
508
- * { box-sizing: border-box; margin: 0; padding: 0; }
509
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 24px; }
510
- .header { margin-bottom: 24px; }
511
- .header h1 { font-size: 24px; font-weight: 600; color: #f8fafc; }
512
- .meta { font-size: 13px; color: #94a3b8; margin-top: 4px; }
513
- .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 32px; }
514
- .stat-card { background: #1e293b; border-radius: 12px; padding: 16px; }
515
- .stat-value { font-size: 32px; font-weight: 700; }
516
- .stat-label { font-size: 12px; color: #94a3b8; margin-top: 4px; }
517
- .stat-blocking .stat-value { color: #ef4444; }
518
- .stat-warning .stat-value { color: #f59e0b; }
519
- .stat-minor .stat-value { color: #94a3b8; }
520
- .stat-processed .stat-value { color: #22c55e; }
521
- .section { margin-bottom: 32px; }
522
- .section h2 { font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #f8fafc; border-bottom: 1px solid #334155; padding-bottom: 8px; }
523
- .screenshots-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 16px; }
524
- .screenshot-card { background: #1e293b; border-radius: 12px; overflow: hidden; border: 1px solid #334155; }
525
- .screenshot-card.has-issues { border-color: #475569; }
526
- .card-header { padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #334155; }
527
- .route { font-family: monospace; font-size: 14px; font-weight: 600; color: #f8fafc; }
528
- .badge { font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 9999px; }
529
- .badge-ok { background: #14532d; color: #4ade80; }
530
- .badge-blocking { background: #7f1d1d; color: #fca5a5; }
531
- .badge-warning { background: #78350f; color: #fcd34d; }
532
- .badge-minor { background: #1e293b; color: #94a3b8; border: 1px solid #475569; }
533
- .card-body { padding: 8px; }
534
- .card-comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
535
- .card-img { position: relative; }
536
- .img-label { position: absolute; top: 4px; left: 4px; font-size: 10px; background: rgba(0,0,0,0.6); color: #fff; padding: 2px 6px; border-radius: 4px; z-index: 1; }
537
- .card-diff { position: relative; }
538
- .diff-label { position: absolute; top: 4px; left: 4px; font-size: 10px; background: rgba(220,38,38,0.8); color: #fff; padding: 2px 6px; border-radius: 4px; z-index: 1; }
539
- .card-body img { width: 100%; border-radius: 6px; display: block; }
540
- .anomaly-list { padding: 12px 16px; border-top: 1px solid #334155; }
541
- .anomaly-item { padding: 10px 12px; border-radius: 8px; margin-bottom: 8px; background: #0f172a; }
542
- .anomaly-item:last-child { margin-bottom: 0; }
543
- .anomaly-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
544
- .type-badge { font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: 4px; background: #334155; color: #e2e8f0; font-family: monospace; }
545
- .severity-badge { font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: 4px; }
546
- .severity-badge.blocking { background: #7f1d1d; color: #fca5a5; }
547
- .severity-badge.warning { background: #78350f; color: #fcd34d; }
548
- .severity-badge.minor { background: #1e293b; color: #94a3b8; border: 1px solid #475569; }
549
- .viewport-badge { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: #312e81; color: #a5b4fc; }
550
- .diff-badge { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: #dc2626; color: #fff; font-family: monospace; }
551
- .anomaly-body { }
552
- .anomaly-body strong { font-size: 13px; color: #f8fafc; }
553
- .anomaly-body p { font-size: 12px; color: #cbd5e1; margin-top: 4px; line-height: 1.5; }
554
- .anomaly-meta { font-size: 11px; color: #64748b; margin-top: 4px; }
555
- .empty-state { text-align: center; padding: 48px; color: #64748b; }
556
- .empty-state h3 { font-size: 18px; color: #94a3b8; margin-bottom: 8px; }
557
- .diff-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; margin-right: 4px; }
558
- .diff-mode { font-size: 14px; background: #dc2626; color: #fff; padding: 2px 8px; border-radius: 6px; font-weight: normal; vertical-align: middle; }
559
- </style>
560
- </head>
561
- <body>
562
- <div class="header">
563
- <h1>Vision Check Report${diffDir ? " <span class='diff-mode'>DIFF MODE</span>" : ""}</h1>
564
- <div class="meta">Generated: ${timestamp} | Ollama: ${result.ollamaUrl} | Model: ${result.model}${diffDir ? " | Baseline: " + result.baselineDir : ""}</div>
565
- </div>
566
-
567
- <div class="summary">
568
- <div class="stat-card stat-processed">
569
- <div class="stat-value">${result.processed}</div>
570
- <div class="stat-label">Processed</div>
571
- </div>
572
- <div class="stat-card stat-blocking">
573
- <div class="stat-value">${bySeverity.blocking.length}</div>
574
- <div class="stat-label">Blocking</div>
575
- </div>
576
- <div class="stat-card stat-warning">
577
- <div class="stat-value">${bySeverity.warning.length}</div>
578
- <div class="stat-label">Warning</div>
579
- </div>
580
- <div class="stat-card stat-minor">
581
- <div class="stat-value">${bySeverity.minor.length}</div>
582
- <div class="stat-label">Minor</div>
583
- </div>
584
- </div>
585
-
586
- <div class="section">
587
- <h2>Screenshots (${screenshots.length})</h2>
588
- ${screenshotCards
589
- ? `<div class="screenshots-grid">${screenshotCards}</div>`
590
- : `<div class="empty-state"><h3>No screenshots analyzed</h3></div>`}
591
- </div>
592
-
593
- ${result.anomalies.length > 0
594
- ? `<div class="section">
595
- <h2>All Anomalies</h2>
596
- <div class="anomaly-list" style="padding:0;border:none;background:transparent">
597
- ${result.anomalies
598
- .map((a) => ` <div class="anomaly-item severity-${a.severity}">
599
- <div class="anomaly-header">
600
- <span class="type-badge">${a.type}</span>
601
- <span class="severity-badge ${a.severity}">${a.severity}</span>
602
- ${a.viewport ? `<span class="viewport-badge">${escapeHtml(a.viewport)}</span>` : ""}
603
- ${a.changed ? `<span class="diff-indicator" title="Pixel diff detected"></span>` : ""}
604
- </div>
605
- <div class="anomaly-body">
606
- <strong>${escapeHtml(a.element)}</strong>
607
- <p>${escapeHtml(a.description)}</p>
608
- <div class="anomaly-meta">${escapeHtml(a.route)} · ${escapeHtml(a.position)}</div>
609
- </div>
610
- </div>`)
611
- .join("\n")}
612
- </div>
613
- </div>`
614
- : ""}
615
- </body>
616
- </html>`;
617
- writeFileSync(outputPath, html, "utf-8");
618
- }
619
- //# sourceMappingURL=ollama.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/utils/ollama.ts"],"names":[],"mappings":"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"}