openspec-playwright 0.1.28 → 0.1.30
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 -25
- package/dist/commands/init.js +8 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcpSync.d.ts +27 -0
- package/dist/commands/mcpSync.js +124 -0
- package/dist/commands/mcpSync.js.map +1 -0
- package/dist/commands/update.js +5 -4
- package/dist/commands/update.js.map +1 -1
- package/openspec-playwright-0.1.30.tgz +0 -0
- package/package.json +1 -1
- package/release-notes.md +2 -2
- package/src/commands/init.ts +9 -3
- package/src/commands/mcpSync.ts +133 -0
- package/src/commands/update.ts +6 -4
- package/openspec-playwright-0.1.28.tgz +0 -0
|
@@ -7,7 +7,7 @@ compatibility: Requires openspec CLI, Playwright (with browsers installed), and
|
|
|
7
7
|
**Architecture**: Uses CLI + SKILLs (not `init-agents`). This follows Playwright's recommended approach for coding agents — CLI is more token-efficient than loading MCP tool schemas into context. MCP is used only for Healer (UI inspection on failure).
|
|
8
8
|
metadata:
|
|
9
9
|
author: openspec-playwright
|
|
10
|
-
version: "2.
|
|
10
|
+
version: "2.5"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Input
|
|
@@ -25,6 +25,12 @@ metadata:
|
|
|
25
25
|
|
|
26
26
|
## Architecture
|
|
27
27
|
|
|
28
|
+
This skill implements the Playwright Test Agents pipeline:
|
|
29
|
+
|
|
30
|
+
- **🎠Planner** (Step 4): Consumes OpenSpec specs (`specs/*.md`) and produces `test-plan.md` — combines OpenSpec's structured requirements with LLM编排.
|
|
31
|
+
- **🎠Generator** (Step 5): Transforms the Markdown test-plan into real Playwright `.spec.ts` files using LLM code generation.
|
|
32
|
+
- **🎠Healer** (Step 8): Executes the test suite and automatically repairs failing selectors via Playwright MCP tools.
|
|
33
|
+
|
|
28
34
|
Uses CLI + SKILLs (not `init-agents`). This follows Playwright's recommended approach for coding agents — CLI is more token-efficient than loading MCP tool schemas into context. MCP is used only for Healer (UI inspection on failure).
|
|
29
35
|
|
|
30
36
|
**Schema owns templates. CLI handles execution. Skill handles cognitive work.**
|
|
@@ -63,7 +69,24 @@ Detect if auth is required. Mark as **auth required** only when BOTH conditions
|
|
|
63
69
|
- Medium (proceed with note): Single explicit marker, context unclear
|
|
64
70
|
- Low (skip auth): No explicit markers found
|
|
65
71
|
|
|
66
|
-
### 3.
|
|
72
|
+
### 3. Validate environment
|
|
73
|
+
|
|
74
|
+
Before generating tests, verify the environment is ready by running the seed test:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx playwright test tests/playwright/seed.spec.ts --project=chromium
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**What this validates**:
|
|
81
|
+
- App server is reachable (BASE_URL accessible)
|
|
82
|
+
- Auth fixtures (`storageState`) are initialized if auth is required
|
|
83
|
+
- Playwright browser and config are working
|
|
84
|
+
|
|
85
|
+
**If seed test fails**: Stop and report the failure. User must fix the environment before proceeding.
|
|
86
|
+
|
|
87
|
+
**If seed test passes or no auth required**: Proceed to Step 4.
|
|
88
|
+
|
|
89
|
+
### 4. Generate test plan
|
|
67
90
|
|
|
68
91
|
Create `openspec/changes/<name>/specs/playwright/test-plan.md` by:
|
|
69
92
|
- Listing each functional requirement as a test case
|
|
@@ -73,7 +96,7 @@ Create `openspec/changes/<name>/specs/playwright/test-plan.md` by:
|
|
|
73
96
|
|
|
74
97
|
**Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate unless user explicitly asks.
|
|
75
98
|
|
|
76
|
-
###
|
|
99
|
+
### 5. Generate test file (LLM-driven)
|
|
77
100
|
|
|
78
101
|
Use your file writing capability to create `tests/playwright/<name>.spec.ts`.
|
|
79
102
|
|
|
@@ -115,7 +138,7 @@ test.describe('Feature Name', () => {
|
|
|
115
138
|
|
|
116
139
|
**Selector guidance**: If no `data-testid` exists in the app, prefer `getByRole`, `getByLabel`, `getByText` over fragile selectors like CSS paths.
|
|
117
140
|
|
|
118
|
-
###
|
|
141
|
+
### 6. Configure auth (if required)
|
|
119
142
|
|
|
120
143
|
If auth is required:
|
|
121
144
|
|
|
@@ -137,7 +160,7 @@ Auth required. To set up credentials:
|
|
|
137
160
|
|
|
138
161
|
**Idempotency**: If `auth.setup.ts` already exists → verify format, update only if stale.
|
|
139
162
|
|
|
140
|
-
###
|
|
163
|
+
### 7. Configure playwright.config.ts (non-destructive)
|
|
141
164
|
|
|
142
165
|
If `playwright.config.ts` does not exist → generate it from the schema template at `openspec/schemas/playwright-e2e/templates/playwright.config.ts`. The template auto-detects:
|
|
143
166
|
- **BASE_URL**: from `process.env.BASE_URL`, falling back to `tests/playwright/seed.spec.ts` → `BASE_URL` value, then `http://localhost:3000`
|
|
@@ -145,7 +168,7 @@ If `playwright.config.ts` does not exist → generate it from the schema templat
|
|
|
145
168
|
|
|
146
169
|
If `playwright.config.ts` exists → READ it first. Extract existing `webServer`, `use.baseURL`, and `projects`. Preserve ALL existing fields. Add `webServer` block if missing. Do NOT replace existing config values.
|
|
147
170
|
|
|
148
|
-
###
|
|
171
|
+
### 8. Execute tests via CLI
|
|
149
172
|
|
|
150
173
|
```bash
|
|
151
174
|
openspec-pw run <name> --project=<role>
|
|
@@ -160,28 +183,36 @@ The CLI handles:
|
|
|
160
183
|
If tests fail → analyze failures, use **Playwright MCP tools** to inspect UI state, fix selectors in the test file, and re-run.
|
|
161
184
|
|
|
162
185
|
**Healer MCP tools** (in order of use):
|
|
186
|
+
<!-- MCP_VERSION: 0.0.68 -->
|
|
163
187
|
|
|
164
188
|
| Tool | Purpose |
|
|
165
189
|
|------|---------|
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
190
|
+
| `browser_navigate` | Go to the failing test's page |
|
|
191
|
+
| `browser_snapshot` | Get page structure to find equivalent selectors |
|
|
192
|
+
| `browser_console_messages` | Diagnose JS errors that may cause failures |
|
|
193
|
+
| `browser_take_screenshot` | Visually compare before/after fixes |
|
|
194
|
+
| `browser_run_code` | Execute custom fix logic (optional) |
|
|
171
195
|
|
|
172
196
|
**Healer workflow**:
|
|
173
|
-
1. Read the failing test
|
|
174
|
-
2.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
1. Read the failing test → identify failure type by checking error message and console output
|
|
198
|
+
2. Classify the failure:
|
|
199
|
+
|
|
200
|
+
| Failure type | Signal | Action |
|
|
201
|
+
|-------------|--------|--------|
|
|
202
|
+
| **Network/backend** | `fetch failed`, `net::ERR`, 5xx, timeout | `browser_console_messages` → add `test.skip()` + report "backend error" |
|
|
203
|
+
| **Selector changed** | Element not found, page loaded | `browser_snapshot` → fix selector → re-run |
|
|
204
|
+
| **Assertion mismatch** | Element exists, wrong content/value | `browser_snapshot` → compare → fix assertion → re-run |
|
|
205
|
+
| **Timing issue** | `waitFor` timeout, race condition | Adjust wait strategy → re-run |
|
|
206
|
+
|
|
207
|
+
3. **Attempt heal** (up to 3 times):
|
|
208
|
+
- Apply fix using `browser_snapshot` (prefer `getByRole`, `getByLabel`, `getByText`)
|
|
209
|
+
- Re-run: `openspec-pw run <name> --project=<role>`
|
|
210
|
+
4. **After 3 failed attempts**:
|
|
211
|
+
- Use `browser_console_messages` + `browser_snapshot` to confirm root cause
|
|
212
|
+
- If app is clearly broken (e.g., API returns 500, element missing from flow) → add `test.skip()` + report "app bug — needs fix"
|
|
213
|
+
- If unclear → report with recommendation ("likely selector change, verify manually")
|
|
214
|
+
|
|
215
|
+
### 9. Report results
|
|
185
216
|
|
|
186
217
|
Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
|
|
187
218
|
|
|
@@ -238,12 +269,15 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
|
|
|
238
269
|
| Scenario | Behavior |
|
|
239
270
|
|----------|----------|
|
|
240
271
|
| No specs found | Stop with info message — E2E requires specs |
|
|
272
|
+
| Seed test fails | Stop with failure — fix environment before proceeding |
|
|
241
273
|
| No auth required | Skip auth setup entirely |
|
|
242
274
|
| test-plan.md exists | Read and use it — never regenerate |
|
|
243
275
|
| auth.setup.ts exists | Verify format — update only if stale |
|
|
244
276
|
| playwright.config.ts exists | Read and preserve all fields — add only missing |
|
|
245
|
-
| Test fails (
|
|
246
|
-
| Test fails (
|
|
277
|
+
| Test fails (network/backend) | Skip with `test.skip()` — backend/app error, not test fragility |
|
|
278
|
+
| Test fails (selector) | Healer: snapshot → fix selector → re-run (≤3 attempts) |
|
|
279
|
+
| Test fails (assertion) | Healer: snapshot → fix assertion → re-run (≤3 attempts) |
|
|
280
|
+
| 3 heal attempts failed | Confirm root cause → if app bug: `test.skip()` + report; if unclear: report with recommendation |
|
|
247
281
|
|
|
248
282
|
## Verification Heuristics
|
|
249
283
|
|
package/dist/commands/init.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, } from 'fs';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { readFile } from 'fs/promises';
|
|
6
|
+
import { syncMcpTools } from './mcpSync.js';
|
|
6
7
|
const TEMPLATE_DIR = new URL('../../templates', import.meta.url).pathname;
|
|
7
8
|
const SCHEMA_DIR = new URL('../../schemas', import.meta.url).pathname;
|
|
8
9
|
const SKILL_SRC = new URL('../../.claude/skills/openspec-e2e', import.meta.url).pathname;
|
|
@@ -61,15 +62,19 @@ export async function init(options) {
|
|
|
61
62
|
// 4. Copy skill files
|
|
62
63
|
console.log(chalk.blue('\n─── Installing Claude Code Skill ───'));
|
|
63
64
|
await installSkill(projectRoot);
|
|
64
|
-
// 5.
|
|
65
|
+
// 5. Sync Healer tools with latest @playwright/mcp
|
|
66
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
67
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
68
|
+
await syncMcpTools(skillDest, true);
|
|
69
|
+
// 6. Install OpenSpec schema
|
|
65
70
|
console.log(chalk.blue('\n─── Installing OpenSpec Schema ───'));
|
|
66
71
|
await installSchema(projectRoot);
|
|
67
|
-
//
|
|
72
|
+
// 7. Generate seed test
|
|
68
73
|
if (options.seed !== false) {
|
|
69
74
|
console.log(chalk.blue('\n─── Generating Seed Test ───'));
|
|
70
75
|
await generateSeedTest(projectRoot);
|
|
71
76
|
}
|
|
72
|
-
//
|
|
77
|
+
// 8. Summary
|
|
73
78
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
74
79
|
console.log(chalk.green(' ✓ Setup complete!\n'));
|
|
75
80
|
console.log(chalk.bold('Next steps:'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const MCP_VERSION_MARKER = "<!-- MCP_VERSION:";
|
|
2
|
+
export declare const DEFAULT_HEALER_TOOLS: {
|
|
3
|
+
name: string;
|
|
4
|
+
purpose: string;
|
|
5
|
+
}[];
|
|
6
|
+
/** Extract MCP version from SKILL.md marker */
|
|
7
|
+
export declare function getStoredMcpVersion(skillContent: string): string | null;
|
|
8
|
+
/** Replace the Healer tools table in SKILL.md */
|
|
9
|
+
export declare function updateHealerTable(skillContent: string, version: string, tools: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
purpose: string;
|
|
12
|
+
}>): string;
|
|
13
|
+
/** Fetch latest @playwright/mcp version from npm registry */
|
|
14
|
+
export declare function getLatestMcpVersion(): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch @playwright/mcp tools from npm package.
|
|
17
|
+
* Downloads the tarball, extracts README, parses tool names.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fetchMcpTools(version: string): Promise<Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
purpose: string;
|
|
22
|
+
}>>;
|
|
23
|
+
/**
|
|
24
|
+
* Sync Healer tools table in SKILL.md with latest @playwright/mcp.
|
|
25
|
+
* Returns true if updated, false if already current or failed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function syncMcpTools(skillDest: string, verbose?: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
export const MCP_VERSION_MARKER = '<!-- MCP_VERSION:';
|
|
5
|
+
export const DEFAULT_HEALER_TOOLS = [
|
|
6
|
+
{ name: 'browser_navigate', purpose: "Go to the failing test's page" },
|
|
7
|
+
{ name: 'browser_snapshot', purpose: 'Get page structure to find equivalent selectors' },
|
|
8
|
+
{ name: 'browser_console_messages', purpose: 'Diagnose JS errors that may cause failures' },
|
|
9
|
+
{ name: 'browser_take_screenshot', purpose: 'Visually compare before/after fixes' },
|
|
10
|
+
{ name: 'browser_run_code', purpose: 'Execute custom fix logic (optional)' },
|
|
11
|
+
];
|
|
12
|
+
/** Extract MCP version from SKILL.md marker */
|
|
13
|
+
export function getStoredMcpVersion(skillContent) {
|
|
14
|
+
const idx = skillContent.indexOf(MCP_VERSION_MARKER);
|
|
15
|
+
if (idx === -1)
|
|
16
|
+
return null;
|
|
17
|
+
const end = skillContent.indexOf(' -->', idx);
|
|
18
|
+
return skillContent.slice(idx + MCP_VERSION_MARKER.length, end).trim();
|
|
19
|
+
}
|
|
20
|
+
/** Build the Healer tools table markdown */
|
|
21
|
+
function buildHealerTable(version, tools) {
|
|
22
|
+
const rows = tools.map(t => `| \`${t.name}\` | ${t.purpose} |`).join('\n');
|
|
23
|
+
return `${MCP_VERSION_MARKER} ${version} -->\n\n| Tool | Purpose |\n|------|---------|\n${rows}`;
|
|
24
|
+
}
|
|
25
|
+
/** Replace the Healer tools table in SKILL.md */
|
|
26
|
+
export function updateHealerTable(skillContent, version, tools) {
|
|
27
|
+
const start = skillContent.indexOf('| Tool | Purpose |');
|
|
28
|
+
if (start === -1)
|
|
29
|
+
return skillContent;
|
|
30
|
+
let end = skillContent.indexOf('\n\n', start);
|
|
31
|
+
if (end === -1)
|
|
32
|
+
end = skillContent.length;
|
|
33
|
+
const before = skillContent.slice(0, start);
|
|
34
|
+
const after = skillContent.slice(end);
|
|
35
|
+
return before + buildHealerTable(version, tools) + after;
|
|
36
|
+
}
|
|
37
|
+
/** Fetch latest @playwright/mcp version from npm registry */
|
|
38
|
+
export function getLatestMcpVersion() {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
exec('npm show @playwright/mcp version --json', { timeout: 15000 }, (err, stdout) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
resolve(null);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
resolve(JSON.parse(stdout.trim()));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
resolve(null);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Parse README markdown to extract browser_* tool entries */
|
|
55
|
+
function parseMcpReadme(content) {
|
|
56
|
+
const tools = [];
|
|
57
|
+
const re = /-\s+\*\*`?([^`*\n]+)`?\*\*\s*-\s*Title:\s*([^\n]+)/g;
|
|
58
|
+
let m;
|
|
59
|
+
while ((m = re.exec(content)) !== null) {
|
|
60
|
+
const name = m[1].trim();
|
|
61
|
+
if (name.startsWith('browser_')) {
|
|
62
|
+
const purpose = m[2].trim().replace(/\.$/, '');
|
|
63
|
+
tools.push({ name, purpose });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return tools;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Fetch @playwright/mcp tools from npm package.
|
|
70
|
+
* Downloads the tarball, extracts README, parses tool names.
|
|
71
|
+
*/
|
|
72
|
+
export function fetchMcpTools(version) {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const tmpDir = `/tmp/openspec-pw-mcp-${version}`;
|
|
75
|
+
exec(`rm -rf ${tmpDir} && mkdir -p ${tmpDir} && npm pack @playwright/mcp@${version} --pack-destination ${tmpDir} 2>/dev/null && tar -xzf ${tmpDir}/playwright-mcp-${version}.tgz -C ${tmpDir} --strip-components=1 && cat ${tmpDir}/package/README.md`, { timeout: 30000 }, (err, stdout) => {
|
|
76
|
+
if (err) {
|
|
77
|
+
resolve([]);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const tools = parseMcpReadme(stdout);
|
|
81
|
+
resolve(tools);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Sync Healer tools table in SKILL.md with latest @playwright/mcp.
|
|
87
|
+
* Returns true if updated, false if already current or failed.
|
|
88
|
+
*/
|
|
89
|
+
export async function syncMcpTools(skillDest, verbose = false) {
|
|
90
|
+
const latestVersion = await getLatestMcpVersion();
|
|
91
|
+
if (!latestVersion) {
|
|
92
|
+
if (verbose)
|
|
93
|
+
console.log(chalk.yellow(' âš Could not fetch latest @playwright/mcp version'));
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (!existsSync(skillDest)) {
|
|
97
|
+
if (verbose)
|
|
98
|
+
console.log(chalk.gray(' - SKILL.md not found, skipping MCP sync'));
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const skillContent = readFileSync(skillDest, 'utf-8');
|
|
102
|
+
const storedVersion = getStoredMcpVersion(skillContent);
|
|
103
|
+
if (storedVersion === latestVersion) {
|
|
104
|
+
if (verbose)
|
|
105
|
+
console.log(chalk.gray(` - Healer tools current (${latestVersion})`));
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (verbose)
|
|
109
|
+
console.log(chalk.blue(` - Updating from ${storedVersion ?? 'unknown'} → ${latestVersion}`));
|
|
110
|
+
const tools = await fetchMcpTools(latestVersion);
|
|
111
|
+
const toolSet = tools.length > 0 ? tools : DEFAULT_HEALER_TOOLS;
|
|
112
|
+
const updated = updateHealerTable(skillContent, latestVersion, toolSet);
|
|
113
|
+
writeFileSync(skillDest, updated);
|
|
114
|
+
if (verbose) {
|
|
115
|
+
if (tools.length > 0) {
|
|
116
|
+
console.log(chalk.green(` ✓ Healer tools synced to ${latestVersion} (${tools.length} tools)`));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(chalk.green(` ✓ Healer tools synced to ${latestVersion} (default set)`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=mcpSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpSync.js","sourceRoot":"","sources":["../../src/commands/mcpSync.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/update.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
import { syncMcpTools } from './mcpSync.js';
|
|
5
6
|
const SKILL_SRC = new URL('../../.claude/skills/openspec-e2e', import.meta.url).pathname;
|
|
6
7
|
const CMD_SRC = new URL('../../.claude/commands/opsx', import.meta.url).pathname;
|
|
7
8
|
const SCHEMA_DIR = new URL('../../schemas', import.meta.url).pathname;
|
|
@@ -24,12 +25,10 @@ export async function update(options) {
|
|
|
24
25
|
if (options.skill !== false) {
|
|
25
26
|
console.log(chalk.blue('\n─── Updating Skill & Command ───'));
|
|
26
27
|
try {
|
|
27
|
-
// Download npm tarball and extract
|
|
28
28
|
const tmpDir = '/tmp/openspec-e2e-update';
|
|
29
29
|
execSync(`rm -rf ${tmpDir} && mkdir -p ${tmpDir}`, { stdio: 'pipe', cwd: projectRoot });
|
|
30
30
|
execSync(`npm pack openspec-playwright --pack-destination ${tmpDir}`, { stdio: 'pipe', cwd: projectRoot });
|
|
31
31
|
const tarball = execSync(`ls -t ${tmpDir}/openspec-playwright-*.tgz | head -1`, { encoding: 'utf-8', cwd: projectRoot }).trim();
|
|
32
|
-
// Move tarball out before extracting to avoid "overwrite archive" error
|
|
33
32
|
const tmpTarball = `${tmpDir}/package.tgz`;
|
|
34
33
|
execSync(`mv "${tarball}" "${tmpTarball}"`, { stdio: 'pipe', cwd: projectRoot });
|
|
35
34
|
execSync(`tar -xzf "${tmpTarball}" -C ${tmpDir} --strip-components=1`, { stdio: 'pipe', cwd: projectRoot });
|
|
@@ -45,6 +44,10 @@ export async function update(options) {
|
|
|
45
44
|
installSkill(projectRoot);
|
|
46
45
|
}
|
|
47
46
|
}
|
|
47
|
+
// 3. Sync Healer tools with latest @playwright/mcp
|
|
48
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
49
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
50
|
+
await syncMcpTools(skillDest, true);
|
|
48
51
|
// Summary
|
|
49
52
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
50
53
|
console.log(chalk.green(' ✓ Update complete!\n'));
|
|
@@ -70,12 +73,10 @@ function installSkillFrom(skillSrc, cmdSrc, schemaSrc, projectRoot) {
|
|
|
70
73
|
function installSchemaFrom(schemaSrc, projectRoot) {
|
|
71
74
|
const schemaDest = join(projectRoot, 'openspec', 'schemas', 'playwright-e2e');
|
|
72
75
|
mkdirSync(schemaDest, { recursive: true });
|
|
73
|
-
// Copy schema.yaml
|
|
74
76
|
const schemaYamlSrc = join(schemaSrc, 'schema.yaml');
|
|
75
77
|
if (existsSync(schemaYamlSrc)) {
|
|
76
78
|
writeFileSync(join(schemaDest, 'schema.yaml'), readFileSync(schemaYamlSrc));
|
|
77
79
|
}
|
|
78
|
-
// Copy templates
|
|
79
80
|
const templatesSrc = join(schemaSrc, 'templates');
|
|
80
81
|
const templatesDest = join(schemaDest, 'templates');
|
|
81
82
|
if (existsSync(templatesSrc)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
CHANGED
package/release-notes.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## What's Changed
|
|
2
2
|
|
|
3
|
-
- feat(skill):
|
|
3
|
+
- feat(skill): add explicit Healer failure classification (v2.5)
|
|
4
4
|
|
|
5
|
-
**Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.
|
|
5
|
+
**Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.30
|
package/src/commands/init.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { readFile } from 'fs/promises';
|
|
11
|
+
import { syncMcpTools } from './mcpSync.js';
|
|
11
12
|
|
|
12
13
|
const TEMPLATE_DIR = new URL('../../templates', import.meta.url).pathname;
|
|
13
14
|
const SCHEMA_DIR = new URL('../../schemas', import.meta.url).pathname;
|
|
@@ -82,17 +83,22 @@ export async function init(options: InitOptions) {
|
|
|
82
83
|
console.log(chalk.blue('\n─── Installing Claude Code Skill ───'));
|
|
83
84
|
await installSkill(projectRoot);
|
|
84
85
|
|
|
85
|
-
// 5.
|
|
86
|
+
// 5. Sync Healer tools with latest @playwright/mcp
|
|
87
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
88
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
89
|
+
await syncMcpTools(skillDest, true);
|
|
90
|
+
|
|
91
|
+
// 6. Install OpenSpec schema
|
|
86
92
|
console.log(chalk.blue('\n─── Installing OpenSpec Schema ───'));
|
|
87
93
|
await installSchema(projectRoot);
|
|
88
94
|
|
|
89
|
-
//
|
|
95
|
+
// 7. Generate seed test
|
|
90
96
|
if (options.seed !== false) {
|
|
91
97
|
console.log(chalk.blue('\n─── Generating Seed Test ───'));
|
|
92
98
|
await generateSeedTest(projectRoot);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
//
|
|
101
|
+
// 8. Summary
|
|
96
102
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
97
103
|
console.log(chalk.green(' ✓ Setup complete!\n'));
|
|
98
104
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export const MCP_VERSION_MARKER = '<!-- MCP_VERSION:';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_HEALER_TOOLS = [
|
|
9
|
+
{ name: 'browser_navigate', purpose: "Go to the failing test's page" },
|
|
10
|
+
{ name: 'browser_snapshot', purpose: 'Get page structure to find equivalent selectors' },
|
|
11
|
+
{ name: 'browser_console_messages', purpose: 'Diagnose JS errors that may cause failures' },
|
|
12
|
+
{ name: 'browser_take_screenshot', purpose: 'Visually compare before/after fixes' },
|
|
13
|
+
{ name: 'browser_run_code', purpose: 'Execute custom fix logic (optional)' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/** Extract MCP version from SKILL.md marker */
|
|
17
|
+
export function getStoredMcpVersion(skillContent: string): string | null {
|
|
18
|
+
const idx = skillContent.indexOf(MCP_VERSION_MARKER);
|
|
19
|
+
if (idx === -1) return null;
|
|
20
|
+
const end = skillContent.indexOf(' -->', idx);
|
|
21
|
+
return skillContent.slice(idx + MCP_VERSION_MARKER.length, end).trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Build the Healer tools table markdown */
|
|
25
|
+
function buildHealerTable(version: string, tools: Array<{ name: string; purpose: string }>): string {
|
|
26
|
+
const rows = tools.map(t => `| \`${t.name}\` | ${t.purpose} |`).join('\n');
|
|
27
|
+
return `${MCP_VERSION_MARKER} ${version} -->\n\n| Tool | Purpose |\n|------|---------|\n${rows}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Replace the Healer tools table in SKILL.md */
|
|
31
|
+
export function updateHealerTable(
|
|
32
|
+
skillContent: string,
|
|
33
|
+
version: string,
|
|
34
|
+
tools: Array<{ name: string; purpose: string }>
|
|
35
|
+
): string {
|
|
36
|
+
const start = skillContent.indexOf('| Tool | Purpose |');
|
|
37
|
+
if (start === -1) return skillContent;
|
|
38
|
+
let end = skillContent.indexOf('\n\n', start);
|
|
39
|
+
if (end === -1) end = skillContent.length;
|
|
40
|
+
|
|
41
|
+
const before = skillContent.slice(0, start);
|
|
42
|
+
const after = skillContent.slice(end);
|
|
43
|
+
return before + buildHealerTable(version, tools) + after;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fetch latest @playwright/mcp version from npm registry */
|
|
47
|
+
export function getLatestMcpVersion(): Promise<string | null> {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
exec('npm show @playwright/mcp version --json', { timeout: 15000 }, (err, stdout) => {
|
|
50
|
+
if (err) { resolve(null); return; }
|
|
51
|
+
try { resolve(JSON.parse(stdout.trim())); } catch { resolve(null); }
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Parse README markdown to extract browser_* tool entries */
|
|
57
|
+
function parseMcpReadme(content: string): Array<{ name: string; purpose: string }> {
|
|
58
|
+
const tools: Array<{ name: string; purpose: string }> = [];
|
|
59
|
+
const re = /-\s+\*\*`?([^`*\n]+)`?\*\*\s*-\s*Title:\s*([^\n]+)/g;
|
|
60
|
+
let m;
|
|
61
|
+
while ((m = re.exec(content)) !== null) {
|
|
62
|
+
const name = m[1].trim();
|
|
63
|
+
if (name.startsWith('browser_')) {
|
|
64
|
+
const purpose = m[2].trim().replace(/\.$/, '');
|
|
65
|
+
tools.push({ name, purpose });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return tools;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetch @playwright/mcp tools from npm package.
|
|
73
|
+
* Downloads the tarball, extracts README, parses tool names.
|
|
74
|
+
*/
|
|
75
|
+
export function fetchMcpTools(version: string): Promise<Array<{ name: string; purpose: string }>> {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const tmpDir = `/tmp/openspec-pw-mcp-${version}`;
|
|
78
|
+
exec(
|
|
79
|
+
`rm -rf ${tmpDir} && mkdir -p ${tmpDir} && npm pack @playwright/mcp@${version} --pack-destination ${tmpDir} 2>/dev/null && tar -xzf ${tmpDir}/playwright-mcp-${version}.tgz -C ${tmpDir} --strip-components=1 && cat ${tmpDir}/package/README.md`,
|
|
80
|
+
{ timeout: 30000 },
|
|
81
|
+
(err, stdout) => {
|
|
82
|
+
if (err) { resolve([]); return; }
|
|
83
|
+
const tools = parseMcpReadme(stdout);
|
|
84
|
+
resolve(tools);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sync Healer tools table in SKILL.md with latest @playwright/mcp.
|
|
92
|
+
* Returns true if updated, false if already current or failed.
|
|
93
|
+
*/
|
|
94
|
+
export async function syncMcpTools(
|
|
95
|
+
skillDest: string,
|
|
96
|
+
verbose = false
|
|
97
|
+
): Promise<boolean> {
|
|
98
|
+
const latestVersion = await getLatestMcpVersion();
|
|
99
|
+
if (!latestVersion) {
|
|
100
|
+
if (verbose) console.log(chalk.yellow(' âš Could not fetch latest @playwright/mcp version'));
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!existsSync(skillDest)) {
|
|
105
|
+
if (verbose) console.log(chalk.gray(' - SKILL.md not found, skipping MCP sync'));
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const skillContent = readFileSync(skillDest, 'utf-8');
|
|
110
|
+
const storedVersion = getStoredMcpVersion(skillContent);
|
|
111
|
+
|
|
112
|
+
if (storedVersion === latestVersion) {
|
|
113
|
+
if (verbose) console.log(chalk.gray(` - Healer tools current (${latestVersion})`));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (verbose) console.log(chalk.blue(` - Updating from ${storedVersion ?? 'unknown'} → ${latestVersion}`));
|
|
118
|
+
|
|
119
|
+
const tools = await fetchMcpTools(latestVersion);
|
|
120
|
+
const toolSet = tools.length > 0 ? tools : DEFAULT_HEALER_TOOLS;
|
|
121
|
+
|
|
122
|
+
const updated = updateHealerTable(skillContent, latestVersion, toolSet);
|
|
123
|
+
writeFileSync(skillDest, updated);
|
|
124
|
+
|
|
125
|
+
if (verbose) {
|
|
126
|
+
if (tools.length > 0) {
|
|
127
|
+
console.log(chalk.green(` ✓ Healer tools synced to ${latestVersion} (${tools.length} tools)`));
|
|
128
|
+
} else {
|
|
129
|
+
console.log(chalk.green(` ✓ Healer tools synced to ${latestVersion} (default set)`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
package/src/commands/update.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { syncMcpTools } from './mcpSync.js';
|
|
10
11
|
|
|
11
12
|
const SKILL_SRC = new URL('../../.claude/skills/openspec-e2e', import.meta.url).pathname;
|
|
12
13
|
const CMD_SRC = new URL('../../.claude/commands/opsx', import.meta.url).pathname;
|
|
@@ -43,7 +44,6 @@ export async function update(options: UpdateOptions) {
|
|
|
43
44
|
if (options.skill !== false) {
|
|
44
45
|
console.log(chalk.blue('\n─── Updating Skill & Command ───'));
|
|
45
46
|
try {
|
|
46
|
-
// Download npm tarball and extract
|
|
47
47
|
const tmpDir = '/tmp/openspec-e2e-update';
|
|
48
48
|
execSync(`rm -rf ${tmpDir} && mkdir -p ${tmpDir}`, { stdio: 'pipe', cwd: projectRoot });
|
|
49
49
|
execSync(
|
|
@@ -54,7 +54,6 @@ export async function update(options: UpdateOptions) {
|
|
|
54
54
|
`ls -t ${tmpDir}/openspec-playwright-*.tgz | head -1`,
|
|
55
55
|
{ encoding: 'utf-8', cwd: projectRoot }
|
|
56
56
|
).trim();
|
|
57
|
-
// Move tarball out before extracting to avoid "overwrite archive" error
|
|
58
57
|
const tmpTarball = `${tmpDir}/package.tgz`;
|
|
59
58
|
execSync(`mv "${tarball}" "${tmpTarball}"`, { stdio: 'pipe', cwd: projectRoot });
|
|
60
59
|
execSync(`tar -xzf "${tmpTarball}" -C ${tmpDir} --strip-components=1`, { stdio: 'pipe', cwd: projectRoot });
|
|
@@ -72,6 +71,11 @@ export async function update(options: UpdateOptions) {
|
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
// 3. Sync Healer tools with latest @playwright/mcp
|
|
75
|
+
console.log(chalk.blue('\n─── Syncing Healer Tools ───'));
|
|
76
|
+
const skillDest = join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md');
|
|
77
|
+
await syncMcpTools(skillDest, true);
|
|
78
|
+
|
|
75
79
|
// Summary
|
|
76
80
|
console.log(chalk.blue('\n─── Summary ───'));
|
|
77
81
|
console.log(chalk.green(' ✓ Update complete!\n'));
|
|
@@ -110,13 +114,11 @@ function installSchemaFrom(schemaSrc: string, projectRoot: string) {
|
|
|
110
114
|
const schemaDest = join(projectRoot, 'openspec', 'schemas', 'playwright-e2e');
|
|
111
115
|
|
|
112
116
|
mkdirSync(schemaDest, { recursive: true });
|
|
113
|
-
// Copy schema.yaml
|
|
114
117
|
const schemaYamlSrc = join(schemaSrc, 'schema.yaml');
|
|
115
118
|
if (existsSync(schemaYamlSrc)) {
|
|
116
119
|
writeFileSync(join(schemaDest, 'schema.yaml'), readFileSync(schemaYamlSrc));
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
// Copy templates
|
|
120
122
|
const templatesSrc = join(schemaSrc, 'templates');
|
|
121
123
|
const templatesDest = join(schemaDest, 'templates');
|
|
122
124
|
if (existsSync(templatesSrc)) {
|
|
Binary file
|