openspec-playwright 0.3.8 → 0.3.10
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/.claude/skills/openspec-e2e/SKILL.md +59 -0
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/commands/doctor.js +18 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/visionCheck.d.ts +25 -0
- package/dist/commands/visionCheck.js +224 -0
- package/dist/commands/visionCheck.js.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/ollama.d.ts +63 -0
- package/dist/utils/ollama.js +208 -0
- package/dist/utils/ollama.js.map +1 -0
- package/package.json +3 -1
- package/templates/app-exploration.md +12 -0
- package/templates/app-knowledge.md +16 -0
- package/templates/pages/CLAUDE.md +0 -13
|
@@ -449,6 +449,65 @@ After writing `app-exploration.md`, extract **project-level shared knowledge** a
|
|
|
449
449
|
|
|
450
450
|
Append only new/changed items — preserve existing content.
|
|
451
451
|
|
|
452
|
+
### 4.5. Vision Check (Optional, VLM-powered)
|
|
453
|
+
|
|
454
|
+
If Ollama with a vision model is available, analyze screenshots for layout anomalies before proceeding to test generation. This catches UI issues early — before tests fail.
|
|
455
|
+
|
|
456
|
+
**Prerequisite**: Run `openspec-pw doctor` to check Vision Check availability:
|
|
457
|
+
```
|
|
458
|
+
─── Vision Check ───
|
|
459
|
+
✓ ollama: http://localhost:11434 (qwen2.5-vl)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
If Vision Check shows `⚠ disabled` or `⚠ not reachable` → skip this step entirely. Vision Check is optional and should not block exploration.
|
|
463
|
+
|
|
464
|
+
**Configuration** (in order of priority):
|
|
465
|
+
1. Environment variables: `OLLAMA_URL`, `OLLAMA_VISION_MODEL`
|
|
466
|
+
2. `tests/playwright/app-knowledge.md` → **Vision Check Config** section
|
|
467
|
+
|
|
468
|
+
If no configuration is found, vision check is disabled.
|
|
469
|
+
|
|
470
|
+
**Run vision check** after exploration is complete:
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
openspec-pw vision-check --screenshots "__screenshots__/*.png"
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Flags**:
|
|
477
|
+
- `--screenshots <glob>` — Screenshot paths (required)
|
|
478
|
+
- `--parallel <n>` — Concurrent Ollama requests (default: 4)
|
|
479
|
+
- `--severity <levels>` — Filter by severity: `blocking,warning,minor`
|
|
480
|
+
- `--output <path>` — Write JSON results to file
|
|
481
|
+
- `--dry-run` — List screenshots without analyzing
|
|
482
|
+
- `--json` — Output JSON format
|
|
483
|
+
|
|
484
|
+
**Exit codes**:
|
|
485
|
+
- `0` = Check completed (with or without anomalies)
|
|
486
|
+
- `1` = Ollama not available → skip vision check, continue workflow
|
|
487
|
+
- `2` = Configuration disabled → skip vision check, continue workflow
|
|
488
|
+
|
|
489
|
+
**Anomaly types detected**:
|
|
490
|
+
|
|
491
|
+
| Type | Description | Example |
|
|
492
|
+
| --- | --- | --- |
|
|
493
|
+
| `obscured` | Interactive element covered by another element | Submit button hidden behind modal |
|
|
494
|
+
| `crowded` | Elements too close together, hard to distinguish | Form fields with <8px spacing |
|
|
495
|
+
| `overflowed` | Content clipped by container | Truncated text without ellipsis |
|
|
496
|
+
|
|
497
|
+
**Output handling**:
|
|
498
|
+
|
|
499
|
+
1. **JSON output** (with `--json`): Parse anomalies array for programmatic use
|
|
500
|
+
2. **Auto-append**: Without `--json`, anomalies are appended to `app-exploration.md` → **Visual Anomalies** table
|
|
501
|
+
3. **Decision table**:
|
|
502
|
+
|
|
503
|
+
| Anomaly Severity | Action |
|
|
504
|
+
| --- | --- |
|
|
505
|
+
| `blocking` | Flag route with `⚠️ Layout`, consider skipping test generation until fixed |
|
|
506
|
+
| `warning` | Log to Visual Anomalies table, continue test generation |
|
|
507
|
+
| `minor` | Log for reference, no action required |
|
|
508
|
+
|
|
509
|
+
**Graceful degradation**: If Ollama is unavailable or times out → log warning, skip vision check, continue to Step 5. Vision check should never block the E2E workflow.
|
|
510
|
+
|
|
452
511
|
#### 4.7. After exploration
|
|
453
512
|
|
|
454
513
|
Pass `app-exploration.md` to:
|
package/README.md
CHANGED
|
@@ -35,9 +35,11 @@ Claude Code — E2E workflow is driven by SKILL.md using /browse (exploration) +
|
|
|
35
35
|
```bash
|
|
36
36
|
openspec-pw init # Initialize integration (one-time setup)
|
|
37
37
|
openspec-pw update # Update CLI and commands to latest version
|
|
38
|
-
openspec-pw doctor # Check prerequisites
|
|
38
|
+
openspec-pw doctor # Check prerequisites (including Vision Check)
|
|
39
39
|
openspec-pw audit # Audit tests for orphaned specs and issues
|
|
40
40
|
openspec-pw migrate # Migrate old test files to new structure
|
|
41
|
+
openspec-pw explore # Explore routes in parallel with Playwright
|
|
42
|
+
openspec-pw vision-check # Analyze screenshots for layout anomalies (Ollama VLM)
|
|
41
43
|
openspec-pw uninstall # Remove integration from the project
|
|
42
44
|
```
|
|
43
45
|
|
package/README.zh-CN.md
CHANGED
|
@@ -45,9 +45,11 @@ Claude Code — E2E 工作流由 SKILL.md 驱动,使用 /browse(探索)+ P
|
|
|
45
45
|
```bash
|
|
46
46
|
openspec-pw init # 初始化集成(一次性设置)
|
|
47
47
|
openspec-pw update # 更新 CLI 和命令到最新版本
|
|
48
|
-
openspec-pw doctor #
|
|
48
|
+
openspec-pw doctor # 检查前置条件(含 Vision Check)
|
|
49
49
|
openspec-pw audit # 检查测试文件是否有孤儿文件和配置问题
|
|
50
50
|
openspec-pw migrate # 迁移旧测试文件到新目录结构
|
|
51
|
+
openspec-pw explore # 并行探索路由
|
|
52
|
+
openspec-pw vision-check # 使用 Ollama VLM 检测布局问题
|
|
51
53
|
openspec-pw uninstall # 移除项目中的集成
|
|
52
54
|
```
|
|
53
55
|
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { loadOllamaConfig, checkOllamaHealth } from "../utils/ollama.js";
|
|
5
6
|
export async function doctor(options = {}) {
|
|
6
7
|
const checks = [];
|
|
7
8
|
const projectRoot = process.cwd();
|
|
@@ -105,7 +106,22 @@ export async function doctor(options = {}) {
|
|
|
105
106
|
ok: hasSeed,
|
|
106
107
|
message: hasSeed ? "found" : "not found (optional)",
|
|
107
108
|
});
|
|
108
|
-
|
|
109
|
+
// Vision Check (Ollama)
|
|
110
|
+
const ollamaConfig = loadOllamaConfig(projectRoot);
|
|
111
|
+
let visionOk = false;
|
|
112
|
+
let visionMessage = "disabled";
|
|
113
|
+
if (ollamaConfig.enabled) {
|
|
114
|
+
const health = await checkOllamaHealth(ollamaConfig);
|
|
115
|
+
visionOk = health.ok;
|
|
116
|
+
visionMessage = health.message;
|
|
117
|
+
}
|
|
118
|
+
checks.push({
|
|
119
|
+
category: "Vision Check",
|
|
120
|
+
name: "ollama",
|
|
121
|
+
ok: visionOk,
|
|
122
|
+
message: visionMessage,
|
|
123
|
+
});
|
|
124
|
+
const allOk = checks.filter((c) => !c.ok && c.category !== "Seed Test" && c.category !== "Vision Check").length === 0;
|
|
109
125
|
if (options.json) {
|
|
110
126
|
console.log(JSON.stringify({ ok: allOk, checks }, null, 2));
|
|
111
127
|
if (!allOk)
|
|
@@ -123,7 +139,7 @@ export async function doctor(options = {}) {
|
|
|
123
139
|
if (check.ok) {
|
|
124
140
|
console.log(chalk.green(` ✓ ${check.name}: ${check.message}`));
|
|
125
141
|
}
|
|
126
|
-
else if (check.category === "Seed Test") {
|
|
142
|
+
else if (check.category === "Seed Test" || check.category === "Vision Check") {
|
|
127
143
|
console.log(chalk.yellow(` ⚠ ${check.name}: ${check.message}`));
|
|
128
144
|
}
|
|
129
145
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAMzE,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,UAAyB,EAAE;IACtD,MAAM,MAAM,GAKP,EAAE,CAAC;IAER,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAElC,UAAU;IACV,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,MAAM;YACZ,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,MAAM;YACZ,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;IACL,CAAC;IAED,MAAM;IACN,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,KAAK;YACX,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,GAAG;SACb,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,KAAK;YACX,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;IACL,CAAC;IAED,WAAW;IACX,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,UAAU;QAChB,EAAE,EAAE,WAAW;QACf,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB;KACzD,CAAC,CAAC;IAEH,sBAAsB;IACtB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,0BAA0B,EAAE;YAC9C,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,qBAAqB;YAC/B,IAAI,EAAE,YAAY;YAClB,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,EAAE;SACZ,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,qBAAqB;YAC/B,IAAI,EAAE,YAAY;YAClB,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC;IACL,CAAC;IAED,mFAAmF;IACnF,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,iBAAiB,EAAE;YACzC,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YAClC,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,gBAAgB;QAC1B,IAAI,EAAE,gBAAgB;QACtB,EAAE,EAAE,YAAY;QAChB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB;KACvD,CAAC,CAAC;IAEH,QAAQ;IACR,MAAM,QAAQ,GAAG,UAAU,CACzB,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,CAAC,CACnE,CAAC;IACF,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,mBAAmB;QAC7B,IAAI,EAAE,OAAO;QACb,EAAE,EAAE,QAAQ;QACZ,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe;KAClD,CAAC,CAAC;IAEH,YAAY;IACZ,MAAM,OAAO,GAAG,UAAU,CACxB,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,CAAC,CACzD,CAAC;IACF,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,OAAO;QACX,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;KACpD,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,YAAY,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,aAAa,GAAG,UAAU,CAAC;IAE/B,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACrD,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;QACrB,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC;IACjC,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,cAAc;QACxB,IAAI,EAAE,QAAQ;QACd,EAAE,EAAE,QAAQ;QACZ,OAAO,EAAE,aAAa;KACvB,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ,KAAK,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAEtH,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAC/C,CAAC;QACF,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,cAAc;IACd,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CACnE,CAAC;IAEF,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC;YACrD,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC;QAChC,CAAC;QACD,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,WAAW,IAAI,KAAK,CAAC,QAAQ,KAAK,cAAc,EAAE,CAAC;YAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC7C,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision check command: Analyze screenshots for layout anomalies using Ollama VLM.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* openspec-pw vision-check --screenshots "__screenshots__/*.png"
|
|
6
|
+
* openspec-pw vision-check --screenshots "shot1.png,shot2.png" --parallel 2
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 = Check completed (with or without anomalies)
|
|
10
|
+
* 1 = Ollama not available (skipped)
|
|
11
|
+
* 2 = Configuration missing or invalid (skipped)
|
|
12
|
+
*/
|
|
13
|
+
export interface VisionCheckOptions {
|
|
14
|
+
screenshots: string;
|
|
15
|
+
config?: string;
|
|
16
|
+
parallel?: number;
|
|
17
|
+
output?: string;
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
severity?: string;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Main vision check command.
|
|
24
|
+
*/
|
|
25
|
+
export declare function visionCheck(options: VisionCheckOptions): Promise<void>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision check command: Analyze screenshots for layout anomalies using Ollama VLM.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* openspec-pw vision-check --screenshots "__screenshots__/*.png"
|
|
6
|
+
* openspec-pw vision-check --screenshots "shot1.png,shot2.png" --parallel 2
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 = Check completed (with or without anomalies)
|
|
10
|
+
* 1 = Ollama not available (skipped)
|
|
11
|
+
* 2 = Configuration missing or invalid (skipped)
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
+
import { join, basename } from "path";
|
|
15
|
+
import { glob } from "glob";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { loadOllamaConfig, checkOllamaHealth, batchAnalyzeScreenshots, } from "../utils/ollama.js";
|
|
18
|
+
/**
|
|
19
|
+
* Extract route name from screenshot path.
|
|
20
|
+
* e.g., "__screenshots__/dashboard.png" → "/dashboard"
|
|
21
|
+
* "__screenshots__/settings-profile.png" → "/settings/profile"
|
|
22
|
+
*/
|
|
23
|
+
function extractRouteFromPath(screenshotPath) {
|
|
24
|
+
const name = basename(screenshotPath, ".png");
|
|
25
|
+
// Convert dashes back to slashes, handle root
|
|
26
|
+
if (name === "index" || name === "home" || name === "root") {
|
|
27
|
+
return "/";
|
|
28
|
+
}
|
|
29
|
+
return "/" + name.replace(/-/g, "/");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve screenshot paths from glob or comma-separated list.
|
|
33
|
+
*/
|
|
34
|
+
async function resolveScreenshots(pattern) {
|
|
35
|
+
// Check if comma-separated list
|
|
36
|
+
if (pattern.includes(",") && !pattern.includes("*")) {
|
|
37
|
+
return pattern.split(",").map((p) => ({
|
|
38
|
+
path: p.trim(),
|
|
39
|
+
route: extractRouteFromPath(p.trim()),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
// Glob pattern
|
|
43
|
+
const paths = await glob(pattern, { absolute: true });
|
|
44
|
+
return paths.map((p) => ({
|
|
45
|
+
path: p,
|
|
46
|
+
route: extractRouteFromPath(p),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Filter anomalies by severity level.
|
|
51
|
+
*/
|
|
52
|
+
function filterBySeverity(anomalies, severityFilter) {
|
|
53
|
+
const levels = severityFilter.toLowerCase().split(",").map((s) => s.trim());
|
|
54
|
+
return anomalies.filter((a) => levels.includes(a.severity));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Append anomalies to app-exploration.md Visual Anomalies section.
|
|
58
|
+
*/
|
|
59
|
+
function appendToExploration(projectRoot, anomalies) {
|
|
60
|
+
const explorationPath = join(projectRoot, "tests", "playwright", "app-exploration.md");
|
|
61
|
+
if (!existsSync(explorationPath)) {
|
|
62
|
+
console.error(chalk.yellow(`Warning: ${explorationPath} not found, skipping write`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let content = readFileSync(explorationPath, "utf-8");
|
|
66
|
+
// Find or create Visual Anomalies section
|
|
67
|
+
const sectionHeader = "## Visual Anomalies";
|
|
68
|
+
const tableHeader = `| Route | Element | Type | Position | Severity | Description |
|
|
69
|
+
| --- | --- | --- | --- | --- | --- |`;
|
|
70
|
+
if (!content.includes(sectionHeader)) {
|
|
71
|
+
// Add section before "## Next Steps" or at the end
|
|
72
|
+
const nextStepsIndex = content.indexOf("## Next Steps");
|
|
73
|
+
if (nextStepsIndex !== -1) {
|
|
74
|
+
content =
|
|
75
|
+
content.slice(0, nextStepsIndex) +
|
|
76
|
+
`${sectionHeader}\n\n${tableHeader}\n\n` +
|
|
77
|
+
content.slice(nextStepsIndex);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
content += `\n\n${sectionHeader}\n\n${tableHeader}\n`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Append new anomalies (de-duplicate by route + element + type)
|
|
84
|
+
const existingRows = content.match(/\| [^|]+ \| [^|]+ \| [^|]+ \| [^|]+ \| [^|]+ \| [^|]+ \|/g) || [];
|
|
85
|
+
const existingKeys = new Set(existingRows.map((row) => {
|
|
86
|
+
const cells = row.split("|").map((c) => c.trim()).filter(Boolean);
|
|
87
|
+
return `${cells[0]}:${cells[1]}:${cells[2]}`;
|
|
88
|
+
}));
|
|
89
|
+
const newRows = [];
|
|
90
|
+
for (const a of anomalies) {
|
|
91
|
+
const key = `${a.route}:${a.element}:${a.type}`;
|
|
92
|
+
if (!existingKeys.has(key)) {
|
|
93
|
+
newRows.push(`| ${a.route} | ${a.element} | ${a.type} | ${a.position} | ${a.severity} | ${a.description} |`);
|
|
94
|
+
existingKeys.add(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (newRows.length > 0) {
|
|
98
|
+
// Insert after table header
|
|
99
|
+
const headerIndex = content.indexOf(tableHeader);
|
|
100
|
+
if (headerIndex !== -1) {
|
|
101
|
+
const insertPoint = headerIndex + tableHeader.length;
|
|
102
|
+
content =
|
|
103
|
+
content.slice(0, insertPoint) +
|
|
104
|
+
"\n" +
|
|
105
|
+
newRows.join("\n") +
|
|
106
|
+
content.slice(insertPoint);
|
|
107
|
+
}
|
|
108
|
+
writeFileSync(explorationPath, content, "utf-8");
|
|
109
|
+
console.log(chalk.green(` ✓ Appended ${newRows.length} anomalies to app-exploration.md`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Main vision check command.
|
|
114
|
+
*/
|
|
115
|
+
export async function visionCheck(options) {
|
|
116
|
+
const projectRoot = process.cwd();
|
|
117
|
+
// 1. Load configuration
|
|
118
|
+
const config = loadOllamaConfig(projectRoot);
|
|
119
|
+
if (!config.enabled) {
|
|
120
|
+
const result = {
|
|
121
|
+
processed: 0,
|
|
122
|
+
anomalies: [],
|
|
123
|
+
skipped: [],
|
|
124
|
+
skipReason: "disabled",
|
|
125
|
+
ollamaUrl: config.url,
|
|
126
|
+
model: config.model,
|
|
127
|
+
};
|
|
128
|
+
if (options.json) {
|
|
129
|
+
console.log(JSON.stringify(result, null, 2));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(chalk.yellow("Vision check disabled in config"));
|
|
133
|
+
}
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
// 2. Dry run: list files only
|
|
137
|
+
if (options.dryRun) {
|
|
138
|
+
const screenshots = await resolveScreenshots(options.screenshots);
|
|
139
|
+
console.log(chalk.blue("\n📷 Screenshots to analyze (dry run):\n"));
|
|
140
|
+
for (const s of screenshots) {
|
|
141
|
+
console.log(` ${s.route} → ${s.path}`);
|
|
142
|
+
}
|
|
143
|
+
console.log(chalk.gray(`\nTotal: ${screenshots.length} files`));
|
|
144
|
+
console.log(chalk.gray(`Ollama: ${config.url} (${config.model})`));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// 3. Health check
|
|
148
|
+
const health = await checkOllamaHealth(config);
|
|
149
|
+
if (!health.ok) {
|
|
150
|
+
const result = {
|
|
151
|
+
processed: 0,
|
|
152
|
+
anomalies: [],
|
|
153
|
+
skipped: [],
|
|
154
|
+
skipReason: health.message,
|
|
155
|
+
ollamaUrl: config.url,
|
|
156
|
+
model: config.model,
|
|
157
|
+
};
|
|
158
|
+
if (options.json) {
|
|
159
|
+
console.log(JSON.stringify(result, null, 2));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log(chalk.yellow(`Vision check skipped: ${health.message}`));
|
|
163
|
+
}
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
// 4. Resolve screenshots
|
|
167
|
+
const screenshots = await resolveScreenshots(options.screenshots);
|
|
168
|
+
if (screenshots.length === 0) {
|
|
169
|
+
console.log(chalk.yellow("No screenshots found matching pattern"));
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
if (!options.json) {
|
|
173
|
+
console.log(chalk.blue(`\n🔍 Analyzing ${screenshots.length} screenshots...\n`));
|
|
174
|
+
}
|
|
175
|
+
// 5. Batch analyze
|
|
176
|
+
const concurrency = options.parallel || 4;
|
|
177
|
+
const result = await batchAnalyzeScreenshots(config, screenshots, concurrency);
|
|
178
|
+
// 6. Filter by severity if specified
|
|
179
|
+
if (options.severity) {
|
|
180
|
+
result.anomalies = filterBySeverity(result.anomalies, options.severity);
|
|
181
|
+
}
|
|
182
|
+
// 7. Output results
|
|
183
|
+
if (options.json) {
|
|
184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// Text output
|
|
188
|
+
console.log(chalk.blue("─── Vision Check Results ───\n"));
|
|
189
|
+
console.log(` Processed: ${result.processed}/${screenshots.length}`);
|
|
190
|
+
console.log(` Anomalies: ${result.anomalies.length}`);
|
|
191
|
+
if (result.skipped.length > 0) {
|
|
192
|
+
console.log(chalk.yellow(` Skipped: ${result.skipped.length}`));
|
|
193
|
+
}
|
|
194
|
+
if (result.anomalies.length > 0) {
|
|
195
|
+
console.log(chalk.blue("\n─── Anomalies ───\n"));
|
|
196
|
+
for (const a of result.anomalies) {
|
|
197
|
+
const severityColor = a.severity === "blocking"
|
|
198
|
+
? chalk.red
|
|
199
|
+
: a.severity === "warning"
|
|
200
|
+
? chalk.yellow
|
|
201
|
+
: chalk.gray;
|
|
202
|
+
console.log(` ${severityColor(`[${a.severity}]`)} ${a.route}`);
|
|
203
|
+
console.log(` Element: ${a.element}`);
|
|
204
|
+
console.log(` Type: ${a.type} (${a.position})`);
|
|
205
|
+
console.log(` ${a.description}\n`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
console.log(chalk.green("\n ✓ No layout anomalies detected\n"));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 8. Append to app-exploration.md if anomalies found
|
|
213
|
+
if (result.anomalies.length > 0 && !options.json) {
|
|
214
|
+
appendToExploration(projectRoot, result.anomalies);
|
|
215
|
+
}
|
|
216
|
+
// 9. Write to output file if specified
|
|
217
|
+
if (options.output) {
|
|
218
|
+
writeFileSync(options.output, JSON.stringify(result, null, 2), "utf-8");
|
|
219
|
+
if (!options.json) {
|
|
220
|
+
console.log(chalk.green(` ✓ Results written to ${options.output}`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=visionCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visionCheck.js","sourceRoot":"","sources":["../../src/commands/visionCheck.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { migrate } from "./commands/migrate.js";
|
|
|
10
10
|
import { uninstall } from "./commands/uninstall.js";
|
|
11
11
|
import { audit } from "./commands/audit.js";
|
|
12
12
|
import { explore } from "./commands/explore.js";
|
|
13
|
+
import { visionCheck } from "./commands/visionCheck.js";
|
|
13
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
15
16
|
const program = new Command();
|
|
@@ -74,5 +75,18 @@ program
|
|
|
74
75
|
.action(async (opts) => {
|
|
75
76
|
await explore(opts);
|
|
76
77
|
});
|
|
78
|
+
program
|
|
79
|
+
.command("vision-check")
|
|
80
|
+
.description("Analyze screenshots for layout anomalies using Ollama VLM")
|
|
81
|
+
.requiredOption("-s, --screenshots <pattern>", "Screenshot path(s): glob pattern or comma-separated list")
|
|
82
|
+
.option("-c, --config <path>", "Config file path (default: app-knowledge.md)")
|
|
83
|
+
.option("-p, --parallel <n>", "Number of parallel requests to Ollama", (v) => parseInt(v, 10), 4)
|
|
84
|
+
.option("-o, --output <path>", "Write JSON results to file")
|
|
85
|
+
.option("--severity <levels>", "Filter by severity: blocking,warning,minor")
|
|
86
|
+
.option("-n, --dry-run", "List screenshots without analyzing")
|
|
87
|
+
.option("--json", "Output results as JSON")
|
|
88
|
+
.action(async (opts) => {
|
|
89
|
+
await visionCheck(opts);
|
|
90
|
+
});
|
|
77
91
|
program.parse();
|
|
78
92
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama API utilities for vision model integration.
|
|
3
|
+
*
|
|
4
|
+
* Configuration priority (highest to lowest):
|
|
5
|
+
* 1. Function parameters
|
|
6
|
+
* 2. Environment variables (OLLAMA_URL, OLLAMA_VISION_MODEL)
|
|
7
|
+
* 3. app-knowledge.md Vision Check Config section
|
|
8
|
+
*
|
|
9
|
+
* If no configuration is found, vision check is disabled.
|
|
10
|
+
*/
|
|
11
|
+
export interface OllamaConfig {
|
|
12
|
+
url: string;
|
|
13
|
+
model: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface VisionAnomaly {
|
|
17
|
+
route: string;
|
|
18
|
+
screenshot: string;
|
|
19
|
+
element: string;
|
|
20
|
+
type: "obscured" | "crowded" | "overflowed";
|
|
21
|
+
position: string;
|
|
22
|
+
severity: "blocking" | "warning" | "minor";
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
export interface VisionCheckResult {
|
|
26
|
+
processed: number;
|
|
27
|
+
anomalies: VisionAnomaly[];
|
|
28
|
+
skipped: string[];
|
|
29
|
+
skipReason?: string;
|
|
30
|
+
ollamaUrl: string;
|
|
31
|
+
model: string;
|
|
32
|
+
}
|
|
33
|
+
export interface OllamaGenerateResponse {
|
|
34
|
+
response: string;
|
|
35
|
+
done: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load Ollama config with priority:
|
|
39
|
+
* 1. Environment variables (OLLAMA_URL, OLLAMA_VISION_MODEL)
|
|
40
|
+
* 2. app-knowledge.md Vision Check Config section
|
|
41
|
+
*
|
|
42
|
+
* If no configuration is found, returns enabled=false.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadOllamaConfig(projectRoot?: string): OllamaConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Check if Ollama is running and the vision model is available.
|
|
47
|
+
* Returns { ok, message } for doctor output.
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkOllamaHealth(config: OllamaConfig): Promise<{
|
|
50
|
+
ok: boolean;
|
|
51
|
+
message: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Analyze a single screenshot with the vision model.
|
|
55
|
+
*/
|
|
56
|
+
export declare function analyzeScreenshot(config: OllamaConfig, screenshotPath: string, route: string): Promise<VisionAnomaly[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Batch analyze multiple screenshots with concurrency control.
|
|
59
|
+
*/
|
|
60
|
+
export declare function batchAnalyzeScreenshots(config: OllamaConfig, screenshots: Array<{
|
|
61
|
+
path: string;
|
|
62
|
+
route: string;
|
|
63
|
+
}>, concurrency?: number): Promise<VisionCheckResult>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama API utilities for vision model integration.
|
|
3
|
+
*
|
|
4
|
+
* Configuration priority (highest to lowest):
|
|
5
|
+
* 1. Function parameters
|
|
6
|
+
* 2. Environment variables (OLLAMA_URL, OLLAMA_VISION_MODEL)
|
|
7
|
+
* 3. app-knowledge.md Vision Check Config section
|
|
8
|
+
*
|
|
9
|
+
* If no configuration is found, vision check is disabled.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
// ── Config loading ────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Load Ollama config from app-knowledge.md.
|
|
16
|
+
* Parses the "## Vision Check Config" section.
|
|
17
|
+
*/
|
|
18
|
+
function loadFromAppKnowledge(projectRoot) {
|
|
19
|
+
const appKnowledgePath = join(projectRoot, "tests", "playwright", "app-knowledge.md");
|
|
20
|
+
if (!existsSync(appKnowledgePath))
|
|
21
|
+
return {};
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(appKnowledgePath, "utf-8");
|
|
24
|
+
const match = content.match(/## Vision Check Config\s*\n([\s\S]*?)(?=\n##|\n---|\z)/);
|
|
25
|
+
if (!match)
|
|
26
|
+
return {};
|
|
27
|
+
const section = match[1];
|
|
28
|
+
const config = {};
|
|
29
|
+
const urlMatch = section.match(/ollama_url:\s*(.+)/);
|
|
30
|
+
if (urlMatch)
|
|
31
|
+
config.url = urlMatch[1].trim();
|
|
32
|
+
const modelMatch = section.match(/vision_model:\s*(.+)/);
|
|
33
|
+
if (modelMatch)
|
|
34
|
+
config.model = modelMatch[1].trim();
|
|
35
|
+
const enabledMatch = section.match(/vision_enabled:\s*(.+)/);
|
|
36
|
+
if (enabledMatch)
|
|
37
|
+
config.enabled = enabledMatch[1].trim() === "true";
|
|
38
|
+
return config;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Load Ollama config with priority:
|
|
46
|
+
* 1. Environment variables (OLLAMA_URL, OLLAMA_VISION_MODEL)
|
|
47
|
+
* 2. app-knowledge.md Vision Check Config section
|
|
48
|
+
*
|
|
49
|
+
* If no configuration is found, returns enabled=false.
|
|
50
|
+
*/
|
|
51
|
+
export function loadOllamaConfig(projectRoot = process.cwd()) {
|
|
52
|
+
const appKnowledge = loadFromAppKnowledge(projectRoot);
|
|
53
|
+
// Environment variables override app-knowledge.md
|
|
54
|
+
const url = process.env.OLLAMA_URL || appKnowledge.url;
|
|
55
|
+
const model = process.env.OLLAMA_VISION_MODEL || appKnowledge.model;
|
|
56
|
+
// If no url or model configured, disable vision check
|
|
57
|
+
if (!url || !model) {
|
|
58
|
+
return { url: "", model: "", enabled: false };
|
|
59
|
+
}
|
|
60
|
+
// Enabled: default true unless explicitly disabled
|
|
61
|
+
let enabled = true;
|
|
62
|
+
if (process.env.OLLAMA_VISION_ENABLED !== undefined) {
|
|
63
|
+
enabled = process.env.OLLAMA_VISION_ENABLED === "true";
|
|
64
|
+
}
|
|
65
|
+
else if (appKnowledge.enabled !== undefined) {
|
|
66
|
+
enabled = appKnowledge.enabled;
|
|
67
|
+
}
|
|
68
|
+
return { url, model, enabled };
|
|
69
|
+
}
|
|
70
|
+
// ── Health check ──────────────────────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Check if Ollama is running and the vision model is available.
|
|
73
|
+
* Returns { ok, message } for doctor output.
|
|
74
|
+
*/
|
|
75
|
+
export async function checkOllamaHealth(config) {
|
|
76
|
+
if (!config.enabled) {
|
|
77
|
+
return { ok: false, message: "disabled in config" };
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
// Check Ollama server
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
83
|
+
const response = await fetch(`${config.url}/api/tags`, {
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
});
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
return { ok: false, message: `server error: ${response.status}` };
|
|
89
|
+
}
|
|
90
|
+
const data = (await response.json());
|
|
91
|
+
const models = data.models || [];
|
|
92
|
+
const modelNames = models.map((m) => m.name);
|
|
93
|
+
// Check if vision model exists (exact match or prefix match)
|
|
94
|
+
const hasModel = modelNames.some((name) => name === config.model ||
|
|
95
|
+
name.startsWith(`${config.model}:`) ||
|
|
96
|
+
name === `${config.model}:latest`);
|
|
97
|
+
if (!hasModel) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
message: `model "${config.model}" not found (available: ${modelNames.slice(0, 3).join(", ")}...)`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, message: `${config.url} (${config.model})` };
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const error = err;
|
|
107
|
+
if (error.name === "AbortError") {
|
|
108
|
+
return { ok: false, message: "connection timeout" };
|
|
109
|
+
}
|
|
110
|
+
return { ok: false, message: `not reachable: ${error.message}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ── Vision analysis ───────────────────────────────────────────────────────────
|
|
114
|
+
const VISION_PROMPT = `你是一个专业的网页UI测试员。请分析这张截图,检测以下问题:
|
|
115
|
+
|
|
116
|
+
1. 遮挡:可交互元素(按钮、表单、链接)是否被其他元素遮挡、覆盖或压住,导致无法正常点击?
|
|
117
|
+
2. 拥挤:多个元素是否间距过窄、文字重叠、边界模糊,导致难以区分或误操作?
|
|
118
|
+
3. 溢出:内容是否被容器裁剪,导致关键信息不可见?
|
|
119
|
+
|
|
120
|
+
对于每个问题,请返回:
|
|
121
|
+
- element: 元素名称/描述
|
|
122
|
+
- type: 问题类型(obscured / crowded / overflowed)
|
|
123
|
+
- position: 位置(用屏幕区域描述:左上/右上/中/左下/右下)
|
|
124
|
+
- severity: 严重程度(blocking / warning / minor)
|
|
125
|
+
- description: 问题描述
|
|
126
|
+
|
|
127
|
+
如果一切正常,返回空的 anomalies 数组。
|
|
128
|
+
|
|
129
|
+
请严格以JSON格式回答,不要包含任何其他文字:
|
|
130
|
+
{
|
|
131
|
+
"anomalies": []
|
|
132
|
+
}`;
|
|
133
|
+
/**
|
|
134
|
+
* Analyze a single screenshot with the vision model.
|
|
135
|
+
*/
|
|
136
|
+
export async function analyzeScreenshot(config, screenshotPath, route) {
|
|
137
|
+
if (!existsSync(screenshotPath)) {
|
|
138
|
+
throw new Error(`Screenshot not found: ${screenshotPath}`);
|
|
139
|
+
}
|
|
140
|
+
const imageBase64 = readFileSync(screenshotPath).toString("base64");
|
|
141
|
+
const response = await fetch(`${config.url}/api/generate`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
model: config.model,
|
|
146
|
+
prompt: VISION_PROMPT,
|
|
147
|
+
images: [imageBase64],
|
|
148
|
+
stream: false,
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`Ollama API error: ${response.status}`);
|
|
153
|
+
}
|
|
154
|
+
const data = (await response.json());
|
|
155
|
+
const responseText = data.response || "";
|
|
156
|
+
// Parse JSON from response (may have markdown code block)
|
|
157
|
+
let jsonStr = responseText;
|
|
158
|
+
const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
159
|
+
if (jsonMatch) {
|
|
160
|
+
jsonStr = jsonMatch[1];
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(jsonStr.trim());
|
|
164
|
+
const anomalies = parsed.anomalies || [];
|
|
165
|
+
// Enrich with route and screenshot path
|
|
166
|
+
return anomalies.map((a) => ({
|
|
167
|
+
...a,
|
|
168
|
+
route,
|
|
169
|
+
screenshot: screenshotPath,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// If JSON parse fails, return empty (graceful degradation)
|
|
174
|
+
console.error(`Warning: Could not parse vision response for ${route}`);
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Batch analyze multiple screenshots with concurrency control.
|
|
180
|
+
*/
|
|
181
|
+
export async function batchAnalyzeScreenshots(config, screenshots, concurrency = 4) {
|
|
182
|
+
const result = {
|
|
183
|
+
processed: 0,
|
|
184
|
+
anomalies: [],
|
|
185
|
+
skipped: [],
|
|
186
|
+
ollamaUrl: config.url,
|
|
187
|
+
model: config.model,
|
|
188
|
+
};
|
|
189
|
+
// Process in batches
|
|
190
|
+
for (let i = 0; i < screenshots.length; i += concurrency) {
|
|
191
|
+
const batch = screenshots.slice(i, i + concurrency);
|
|
192
|
+
const promises = batch.map(async ({ path, route }) => {
|
|
193
|
+
try {
|
|
194
|
+
const anomalies = await analyzeScreenshot(config, path, route);
|
|
195
|
+
result.processed++;
|
|
196
|
+
result.anomalies.push(...anomalies);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const error = err;
|
|
200
|
+
console.error(`Warning: Skipped ${route}: ${error.message}`);
|
|
201
|
+
result.skipped.push(path);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
await Promise.allSettled(promises);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/utils/ollama.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAkC5B,iFAAiF;AAEjF;;;GAGG;AACH,SAAS,oBAAoB,CAAC,WAAmB;IAC/C,MAAM,gBAAgB,GAAG,IAAI,CAC3B,WAAW,EACX,OAAO,EACP,YAAY,EACZ,kBAAkB,CACnB,CAAC;IACF,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,EAAE,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CACzB,wDAAwD,CACzD,CAAC;QACF,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;QAEtB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,MAAM,GAA0B,EAAE,CAAC;QAEzC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACrD,IAAI,QAAQ;YAAE,MAAM,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAE9C,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzD,IAAI,UAAU;YAAE,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEpD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC7D,IAAI,YAAY;YAAE,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;QAErE,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,cAAsB,OAAO,CAAC,GAAG,EAAE;IAClE,MAAM,YAAY,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAEvD,kDAAkD;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,YAAY,CAAC,GAAG,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,YAAY,CAAC,KAAK,CAAC;IAEpE,sDAAsD;IACtD,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,mDAAmD;IACnD,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,SAAS,EAAE,CAAC;QACpD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,MAAM,CAAC;IACzD,CAAC;SAAM,IAAI,YAAY,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9C,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAoB;IAEpB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IACtD,CAAC;IAED,IAAI,CAAC;QACH,sBAAsB;QACtB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAE3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,WAAW,EAAE;YACrD,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,CAAC;QAEtB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACpE,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyC,CAAC;QAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAE7C,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,KAAK,MAAM,CAAC,KAAK;YACrB,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC;YACnC,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,SAAS,CACpC,CAAC;QAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,UAAU,MAAM,CAAC,KAAK,2BAA2B,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;aAClG,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAAY,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;QACtD,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;IACnE,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;EAkBpB,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAoB,EACpB,cAAsB,EACtB,KAAa;IAEb,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,WAAW,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEpE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,eAAe,EAAE;QACzD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,CAAC,WAAW,CAAC;YACrB,MAAM,EAAE,KAAK;SACd,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IAEzC,0DAA0D;IAC1D,IAAI,OAAO,GAAG,YAAY,CAAC;IAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACrE,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAoC,CAAC;QAC7E,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;QAEzC,wCAAwC;QACxC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,GAAG,CAAC;YACJ,KAAK;YACL,UAAU,EAAE,cAAc;SAC3B,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;QAC3D,OAAO,CAAC,KAAK,CAAC,gDAAgD,KAAK,EAAE,CAAC,CAAC;QACvE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,MAAoB,EACpB,WAAmD,EACnD,cAAsB,CAAC;IAEvB,MAAM,MAAM,GAAsB;QAChC,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,MAAM,CAAC,GAAG;QACrB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;IAEF,qBAAqB;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;YACnD,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBAC/D,MAAM,CAAC,SAAS,EAAE,CAAC;gBACnB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,GAAY,CAAC;gBAC3B,OAAO,CAAC,KAAK,CAAC,oBAAoB,KAAK,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC7D,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspec-playwright",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
"release": "npm version patch && node scripts/bump-docs.js && npm run build && git add docs/index.html && git push && git push --tags && npm publish"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@types/glob": "^8.1.0",
|
|
22
23
|
"@types/tar": "^6.1.13",
|
|
23
24
|
"chalk": "^5.3.0",
|
|
24
25
|
"commander": "^12.1.0",
|
|
26
|
+
"glob": "^13.0.6",
|
|
25
27
|
"playwright": "^1.50.0",
|
|
26
28
|
"tar": "^7.5.13"
|
|
27
29
|
},
|
|
@@ -58,6 +58,18 @@ BASE_URL: <from env or seed.spec.ts>
|
|
|
58
58
|
|-------|-------|-------|
|
|
59
59
|
| | | |
|
|
60
60
|
|
|
61
|
+
## Visual Anomalies
|
|
62
|
+
|
|
63
|
+
Auto-populated by `openspec-pw vision-check`. Layout issues detected via Ollama VLM.
|
|
64
|
+
|
|
65
|
+
| Route | Element | Type | Position | Severity | Description |
|
|
66
|
+
| --- | --- | --- | --- | --- | --- |
|
|
67
|
+
| | | | | | |
|
|
68
|
+
|
|
69
|
+
> **Type legend**: `obscured` (element covered) | `crowded` (spacing too narrow) | `overflowed` (content clipped)
|
|
70
|
+
>
|
|
71
|
+
> **Severity**: `blocking` (cannot interact) | `warning` (usability issue) | `minor` (cosmetic)
|
|
72
|
+
|
|
61
73
|
## Next Steps
|
|
62
74
|
|
|
63
75
|
After exploration, pass this file to Step 5 (Planner) and Step 6 (Generator).
|
|
@@ -101,6 +101,22 @@ Persists assertion repairs (typos, spec drift) across sessions.
|
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
+
## Vision Check Config
|
|
105
|
+
|
|
106
|
+
Ollama VLM configuration for layout anomaly detection. See `openspec-pw doctor` for status.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
ollama_url:
|
|
110
|
+
vision_model:
|
|
111
|
+
vision_enabled: false
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
> **Note**: Vision check is disabled by default. Configure `ollama_url` and `vision_model` to enable.
|
|
115
|
+
> Environment variables (`OLLAMA_URL`, `OLLAMA_VISION_MODEL`) override these values.
|
|
116
|
+
> Run `openspec-pw vision-check --dry-run` to verify configuration.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
104
120
|
## Changelog
|
|
105
121
|
|
|
106
122
|
| Date | Change | By |
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<claude-mem-context>
|
|
2
|
-
# Recent Activity
|
|
3
|
-
|
|
4
|
-
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
-
|
|
6
|
-
### Apr 7, 2026
|
|
7
|
-
|
|
8
|
-
| ID | Time | T | Title | Read |
|
|
9
|
-
|----|------|---|-------|------|
|
|
10
|
-
| #5756 | 11:10 AM | ✅ | BasePage Input Methods Enhanced with Framework Sync | ~226 |
|
|
11
|
-
| #5747 | 10:58 AM | ⚖️ | Input Reactivity Fix Strategy | ~265 |
|
|
12
|
-
| #5744 | 10:57 AM | 🔵 | BasePage Pattern for Playwright Testing | ~253 |
|
|
13
|
-
</claude-mem-context>
|