openspec-playwright 0.3.7 → 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/CLAUDE.md +14 -2
- package/.claude/skills/openspec-e2e/SKILL.md +60 -1
- 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/employee-standards.md +7 -5
- package/package.json +3 -1
- package/templates/CLAUDE.md +16 -1
- package/templates/app-exploration.md +12 -0
- package/templates/app-knowledge.md +16 -0
- package/templates/pages/CLAUDE.md +0 -13
|
@@ -3,9 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
5
|
|
|
6
|
-
###
|
|
6
|
+
### Mar 27, 2026
|
|
7
7
|
|
|
8
8
|
| ID | Time | T | Title | Read |
|
|
9
9
|
|----|------|---|-------|------|
|
|
10
|
-
| #
|
|
10
|
+
| #5635 | 3:00 PM | ✅ | Simplified skill input instructions | ~201 |
|
|
11
|
+
| #5634 | " | 🟣 | Improved change selection logic in openspec-e2e skill | ~200 |
|
|
12
|
+
| #5633 | " | 🔵 | openspec-e2e SKILL.md implements three-agent Playwright pipeline | ~252 |
|
|
13
|
+
| #5511 | 11:22 AM | ✅ | Extended guardrails to protect auth files from overwriting | ~181 |
|
|
14
|
+
| #5510 | 11:21 AM | 🟣 | Enhanced SKILL.md with Playwright auth project configuration | ~228 |
|
|
15
|
+
| #5427 | 10:05 AM | ✅ | Committed style fixes aligning SKILL.md with OpenSpec template standard | ~281 |
|
|
16
|
+
|
|
17
|
+
### Apr 16, 2026
|
|
18
|
+
|
|
19
|
+
| ID | Time | T | Title | Read |
|
|
20
|
+
|----|------|---|-------|------|
|
|
21
|
+
| #5997 | 9:59 AM | 🔵 | openspec-e2e skill uses openspec-pw CLI for parallel exploration | ~279 |
|
|
22
|
+
| #5996 | " | 🔵 | openspec-e2e skill handles missing auth gracefully | ~172 |
|
|
11
23
|
</claude-mem-context>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openspec-e2e
|
|
3
|
-
description: Run Playwright E2E verification
|
|
3
|
+
description: "Run Playwright E2E verification. ONLY invoke when user explicitly says '/opsx:e2e' or 'run E2E tests'. Do NOT auto-trigger from explore/propose/apply/verify/continue stages — E2E is a separate workflow requiring explicit user request."
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires openspec CLI, Playwright (with browsers installed), /browse (gstack, for exploration), and @playwright/mcp (globally installed via `claude mcp add playwright npx @playwright/mcp@latest`, for test execution + Healer).
|
|
6
6
|
metadata:
|
|
@@ -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":"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"}
|
|
@@ -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":"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;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAC1D,CAAC;AAEF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CACV,yEAAyE,CAC1E;KACA,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,EAAE,SAAS,CAAC;KAC/D,MAAM,CAAC,UAAU,EAAE,mCAAmC,CAAC;KACvD,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;KACvE,MAAM,CAAC,WAAW,EAAE,uCAAuC,CAAC;KAC5D,MAAM,CAAC,IAAI,CAAC,CAAC;AAEhB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,QAAQ,EAAE,wBAAwB,CAAC;KAC1C,MAAM,CAAC,MAAM,CAAC,CAAC;AAElB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC;KACrC,MAAM,CAAC,YAAY,EAAE,2BAA2B,CAAC;KACjD,MAAM,CAAC,MAAM,CAAC,CAAC;AAElB,OAAO;KACJ,OAAO,CAAC,mBAAmB,CAAC;KAC5B,WAAW,CAAC,iDAAiD,CAAC;KAC9D,MAAM,CACL,sBAAsB,EACtB,+CAA+C,CAChD;KACA,MAAM,CAAC,yBAAyB,EAAE,yBAAyB,EAAE,KAAK,CAAC;KACnE,MAAM,CAAC,QAAQ,EAAE,wBAAwB,CAAC;KAC1C,MAAM,CAAC,sBAAsB,EAAE,iCAAiC,CAAC;KACjE,MAAM,CAAC,SAAS,EAAE,sCAAsC,CAAC;KACzD,MAAM,CAAC,mBAAmB,EAAE,4BAA4B,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC;KAC5F,MAAM,CAAC,gBAAgB,EAAE,oCAAoC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC;KACjG,MAAM,CAAC,cAAc,EAAE,sCAAsC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC;KACjG,MAAM,CAAC,YAAY,EAAE,0BAA0B,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC;KACnF,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC;KAC3F,MAAM,CAAC,UAAU,EAAE,kDAAkD,CAAC;KACtE,MAAM,CAAC,oBAAoB,EAAE,kDAAkD,CAAC;KAChF,MAAM,CAAC,GAAG,CAAC,CAAC;AAEf,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CACV,2FAA2F,CAC5F;KACA,MAAM,CACL,eAAe,EACf,kDAAkD,CACnD;KACA,MAAM,CAAC,aAAa,EAAE,8CAA8C,CAAC;KACrE,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CACV,uEAAuE,CACxE;KACA,MAAM,CAAC,SAAS,CAAC,CAAC;AAErB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,mEAAmE,CAAC;KAChF,MAAM,CAAC,KAAK,CAAC,CAAC;AAEjB,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CACL,oBAAoB,EACpB,4BAA4B,EAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EACtB,CAAC,CACF;KACA,MAAM,CACL,eAAe,EACf,6CAA6C,CAC9C;KACA,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,2DAA2D,CAAC;KACxE,cAAc,CACb,6BAA6B,EAC7B,0DAA0D,CAC3D;KACA,MAAM,CACL,qBAAqB,EACrB,8CAA8C,CAC/C;KACA,MAAM,CACL,oBAAoB,EACpB,uCAAuC,EACvC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EACtB,CAAC,CACF;KACA,MAAM,CACL,qBAAqB,EACrB,4BAA4B,CAC7B;KACA,MAAM,CACL,qBAAqB,EACrB,4CAA4C,CAC7C;KACA,MAAM,CACL,eAAe,EACf,oCAAoC,CACrC;KACA,MAAM,CACL,QAAQ,EACR,wBAAwB,CACzB;KACA,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -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/employee-standards.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
本规范适用于 OpenSpec + openspec-playwright 项目(Claude Code 作为开发工具)。
|
|
10
10
|
|
|
11
|
+
**项目规范**:动手前读 `openspec/config.yaml`(技术栈、结构、约定、约束等),无内容则忽略。
|
|
12
|
+
|
|
11
13
|
E2E 工作流前提(由用户确保,非 AI 操作):
|
|
12
14
|
- gstack:`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`(提供 `/browse` 探索 + `/qa` 浏览器验证)
|
|
13
15
|
- OpenSpec CLI:`npm install -g @fission-ai/openspec && openspec init`(提供变更管理能力)
|
|
@@ -28,7 +30,7 @@ E2E 工作流前提(由用户确保,非 AI 操作):
|
|
|
28
30
|
## 3. 上下文管理
|
|
29
31
|
**文件读取完整**:超过 500 行的文件,不要假设单次读取覆盖完整内容——根据需要分次读取或编辑前重新读取完整文件。超过 10 条消息后,编辑任何文件前强制重新读取。
|
|
30
32
|
|
|
31
|
-
**OpenSpec
|
|
33
|
+
**OpenSpec 阶段隔离**:所有阶段均由用户手动触发,不自动进入下一阶段。`/opsx:explore`、`/opsx:propose`、`/opsx:apply`、`/opsx:verify`、`/opsx:e2e` 均需用户明确调用。禁止在同一阶段内触发其他阶段(如 explore 阶段不能调用 apply,verify 阶段不能调用 e2e)。
|
|
32
34
|
|
|
33
35
|
**重构前清死代码**:未使用的 import/export/prop/console.log 先删掉,单独提交,再做重构。
|
|
34
36
|
|
|
@@ -51,11 +53,11 @@ E2E 工作流前提(由用户确保,非 AI 操作):
|
|
|
51
53
|
```
|
|
52
54
|
1. 探索与提案
|
|
53
55
|
2. 产品与架构评审(按需触发)
|
|
54
|
-
3. 设计审查 → /plan-design-review
|
|
56
|
+
3. 设计审查 → /plan-design-review
|
|
55
57
|
4. 实现 → /opsx:apply
|
|
56
58
|
5. 自审 → /opsx:verify
|
|
57
59
|
6. E2E 测试 → /opsx:e2e <change-name> → /browse 探索 + /qa 验证
|
|
58
|
-
7. 发布 → /ship 或 /land-and-deploy
|
|
60
|
+
7. 发布 → 用户手动 /ship 或 /land-and-deploy
|
|
59
61
|
8. 迭代回顾 → /retro
|
|
60
62
|
```
|
|
61
63
|
|
|
@@ -74,8 +76,8 @@ E2E 工作流前提(由用户确保,非 AI 操作):
|
|
|
74
76
|
|
|
75
77
|
**5. 自审**:`/opsx:verify` 自审实现代码,确保质量。
|
|
76
78
|
|
|
77
|
-
**6. E2E 测试**:`/opsx:e2e <change-name>` 生成 Playwright 测试 → `/browse` 探索真实 DOM → Healer 自动修复 → `/qa` 真实浏览器验证。E2E
|
|
79
|
+
**6. E2E 测试**:`/opsx:e2e <change-name>` 生成 Playwright 测试 → `/browse` 探索真实 DOM → Healer 自动修复 → `/qa` 真实浏览器验证。E2E 通过后,由用户决定发布时机。
|
|
78
80
|
|
|
79
|
-
**7.
|
|
81
|
+
**7. 发布**:由用户手动触发 `/ship`、`/land-and-deploy` 或 `/canary`。内部项目可能直接部署,无需走 PR 机制。
|
|
80
82
|
|
|
81
83
|
**8. 迭代回顾**:`/retro`
|
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
|
},
|
package/templates/CLAUDE.md
CHANGED
|
@@ -3,5 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
### Mar 27, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #5522 | 11:25 AM | 🔵 | Playwright auth setup template supports API and UI login | ~261 |
|
|
11
|
+
| #5514 | 11:22 AM | 🟣 | Created credentials.yaml template for E2E test authentication | ~227 |
|
|
12
|
+
| #5513 | " | 🟣 | Created auth.setup.ts template for Playwright Test Agents | ~276 |
|
|
13
|
+
| #5390 | 9:48 AM | 🔄 | Refactored openspec-playwright from CLI to setup tool + Claude Code skill | ~209 |
|
|
14
|
+
| #5375 | 9:44 AM | 🟣 | Created Playwright seed test template | ~224 |
|
|
15
|
+
|
|
16
|
+
### Apr 16, 2026
|
|
17
|
+
|
|
18
|
+
| ID | Time | T | Title | Read |
|
|
19
|
+
|----|------|---|-------|------|
|
|
20
|
+
| #6002 | 10:06 AM | 🔵 | openspec-e2e provides seed test template with anti-patterns documented | ~299 |
|
|
21
|
+
| #5998 | 9:59 AM | 🔵 | OpenSpec Playwright auth.setup.ts uses session reuse pattern | ~202 |
|
|
7
22
|
</claude-mem-context>
|
|
@@ -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>
|