openspec-playwright 0.1.59 → 0.1.60
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 +3 -4
- package/dist/commands/mcpSync.js +13 -5
- package/dist/commands/mcpSync.js.map +1 -1
- package/dist/utils/mcp-tools.d.ts +23 -0
- package/dist/utils/mcp-tools.js +130 -0
- package/dist/utils/mcp-tools.js.map +1 -0
- package/openspec/schemas/playwright-e2e/schema.yaml +56 -0
- package/openspec/schemas/playwright-e2e/templates/e2e-test.ts +55 -0
- package/openspec/schemas/playwright-e2e/templates/playwright.config.ts +52 -0
- package/openspec/schemas/playwright-e2e/templates/report.md +27 -0
- package/openspec/schemas/playwright-e2e/templates/test-plan.md +24 -0
- package/openspec-playwright-0.1.60.tgz +0 -0
- package/package.json +3 -2
- package/release-notes.md +2 -2
- package/schemas/playwright-e2e/templates/app-knowledge.md +11 -1
- package/src/commands/mcpSync.ts +14 -5
- package/templates/seed.spec.ts +22 -0
- package/openspec-playwright-0.1.59.tgz +0 -0
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: Requires openspec CLI, Playwright (with browsers installed), and @playwright/mcp (globally installed via `claude mcp add playwright npx @playwright/mcp@latest`).
|
|
6
6
|
metadata:
|
|
7
7
|
author: openspec-playwright
|
|
8
|
-
version: "2.
|
|
8
|
+
version: "2.10"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Input
|
|
@@ -203,6 +203,7 @@ After writing `app-exploration.md`, extract **project-level shared knowledge** a
|
|
|
203
203
|
| Common Selector Patterns | New patterns discovered that apply across routes |
|
|
204
204
|
| SPA Routing | SPA framework, routing behavior |
|
|
205
205
|
| Project Conventions | BASE_URL, auth method, multi-user roles |
|
|
206
|
+
| Selector Fixes | Healed selectors (see Step 9) — route, old selector, new selector, reason |
|
|
206
207
|
|
|
207
208
|
Append only new/changed items — preserve existing content.
|
|
208
209
|
|
|
@@ -357,8 +358,6 @@ The CLI handles: server lifecycle, port mismatch, report generation.
|
|
|
357
358
|
If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
358
359
|
|
|
359
360
|
**Healer MCP tools** (in order of use):
|
|
360
|
-
<!-- MCP_VERSION: 0.0.68 -->
|
|
361
|
-
|
|
362
361
|
<!-- MCP_VERSION: 0.0.70 -->
|
|
363
362
|
|
|
364
363
|
| Tool | Purpose |
|
|
@@ -381,7 +380,7 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
|
381
380
|
| **Timing issue** | `waitFor`/`page.evaluate` timeout | Switch to `request` API or add `waitFor` → re-run |
|
|
382
381
|
| **page.evaluate with fetch** | `fetch` in browser context, CORS errors | Switch to `page.request` API → re-run |
|
|
383
382
|
|
|
384
|
-
3. **Heal** (≤3 attempts): snapshot → fix → re-run
|
|
383
|
+
3. **Heal** (≤3 attempts): snapshot → fix → re-run. If healed successfully → append to `app-knowledge.md` → **Selector Fixes** table: route, old selector → new selector, reason.
|
|
385
384
|
4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
|
|
386
385
|
|
|
387
386
|
### 10. False Pass Detection
|
package/dist/commands/mcpSync.js
CHANGED
|
@@ -21,6 +21,13 @@ export function getStoredMcpVersion(skillContent) {
|
|
|
21
21
|
const end = skillContent.indexOf(' -->', idx);
|
|
22
22
|
return skillContent.slice(idx + MCP_VERSION_MARKER.length, end).trim();
|
|
23
23
|
}
|
|
24
|
+
/** Remove all existing MCP_VERSION comment lines from content */
|
|
25
|
+
function removeMcpVersionMarkers(content) {
|
|
26
|
+
return content
|
|
27
|
+
.split('\n')
|
|
28
|
+
.filter(line => !line.trim().startsWith(MCP_VERSION_MARKER))
|
|
29
|
+
.join('\n');
|
|
30
|
+
}
|
|
24
31
|
/** Build the Healer tools table markdown */
|
|
25
32
|
function buildHealerTable(version, tools) {
|
|
26
33
|
const rows = tools.map(t => `| \`${t.name}\` | ${t.purpose} |`).join('\n');
|
|
@@ -28,14 +35,15 @@ function buildHealerTable(version, tools) {
|
|
|
28
35
|
}
|
|
29
36
|
/** Replace the Healer tools table in SKILL.md */
|
|
30
37
|
export function updateHealerTable(skillContent, version, tools) {
|
|
31
|
-
const
|
|
38
|
+
const noMarkers = removeMcpVersionMarkers(skillContent);
|
|
39
|
+
const start = noMarkers.indexOf('| Tool | Purpose |');
|
|
32
40
|
if (start === -1)
|
|
33
41
|
return skillContent;
|
|
34
|
-
let end =
|
|
42
|
+
let end = noMarkers.indexOf('\n\n', start);
|
|
35
43
|
if (end === -1)
|
|
36
|
-
end =
|
|
37
|
-
const before =
|
|
38
|
-
const after =
|
|
44
|
+
end = noMarkers.length;
|
|
45
|
+
const before = noMarkers.slice(0, start);
|
|
46
|
+
const after = noMarkers.slice(end);
|
|
39
47
|
return before + buildHealerTable(version, tools) + after;
|
|
40
48
|
}
|
|
41
49
|
/** Fetch latest @playwright/mcp version from npm registry */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpSync.js","sourceRoot":"","sources":["../../src/commands/mcpSync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAE3B,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAEtD,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,+BAA+B,EAAE;IACtE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,iDAAiD,EAAE;IACxF,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,4CAA4C,EAAE;IAC3F,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,qCAAqC,EAAE;IACnF,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,qCAAqC,EAAE;CAC7E,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrD,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,YAAY,CAAC,KAAK,CAAC,GAAG,GAAG,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzE,CAAC;AAED,4CAA4C;AAC5C,SAAS,gBAAgB,CAAC,OAAe,EAAE,KAA+C;IACxF,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,GAAG,kBAAkB,IAAI,OAAO,mDAAmD,IAAI,EAAE,CAAC;AACnG,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAC/B,YAAoB,EACpB,OAAe,EACf,KAA+C;IAE/C,MAAM,
|
|
1
|
+
{"version":3,"file":"mcpSync.js","sourceRoot":"","sources":["../../src/commands/mcpSync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Record of tool name -> purpose description */
|
|
2
|
+
export interface McpTool {
|
|
3
|
+
name: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
purpose: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Download and parse the @playwright/mcp README to extract all browser_* tools.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fetchMcpTools(): McpTool[];
|
|
12
|
+
/**
|
|
13
|
+
* Update the Healer MCP tools table in SKILL.md.
|
|
14
|
+
* Only syncs the 5 core Healer tools; all other MCP tools are
|
|
15
|
+
* general-purpose automation not specific to healing.
|
|
16
|
+
* Deduplicates by tool name.
|
|
17
|
+
*/
|
|
18
|
+
export declare function updateSkillHealerTable(skillPath: string, tools: McpTool[]): void;
|
|
19
|
+
/**
|
|
20
|
+
* Main entry point: fetch tools and update SKILL.md.
|
|
21
|
+
* Call this after installing the skill in init.ts and update.ts.
|
|
22
|
+
*/
|
|
23
|
+
export declare function syncMcpTools(skillPath: string): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Playwright MCP tool synchronization.
|
|
3
|
+
* Parses @playwright/mcp README to extract browser_* tool names and
|
|
4
|
+
* updates the Healer MCP tools table in SKILL.md.
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
/** Cached directory for npm temp files */
|
|
11
|
+
const TMP_DIR = '/tmp/openspec-pw-mcp';
|
|
12
|
+
/**
|
|
13
|
+
* Get the latest @playwright/mcp version from npm registry.
|
|
14
|
+
* Falls back to cached README if network fails.
|
|
15
|
+
*/
|
|
16
|
+
function getLatestVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const json = execSync('npm show @playwright/mcp version --json', { stdio: 'pipe', encoding: 'utf-8' }).trim();
|
|
19
|
+
return JSON.parse(json);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '0.0.68'; // fallback to known version
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Download and parse the @playwright/mcp README to extract all browser_* tools.
|
|
27
|
+
*/
|
|
28
|
+
export function fetchMcpTools() {
|
|
29
|
+
const version = getLatestVersion();
|
|
30
|
+
const tmpPkg = join(TMP_DIR, 'package');
|
|
31
|
+
// Clean and prepare temp dir
|
|
32
|
+
execSync(`rm -rf ${TMP_DIR} && mkdir -p ${TMP_DIR}`, { stdio: 'pipe' });
|
|
33
|
+
// Download tarball
|
|
34
|
+
execSync(`cd ${TMP_DIR} && npm pack @playwright/mcp@${version} --pack-destination ${TMP_DIR}`, { stdio: 'pipe' });
|
|
35
|
+
const tarball = execSync(`ls -t ${TMP_DIR}/playwright-mcp-*.tgz | head -1`, { encoding: 'utf-8' }).trim();
|
|
36
|
+
execSync(`tar -xzf "${tarball}" -C ${TMP_DIR} --strip-components=1`, { stdio: 'pipe' });
|
|
37
|
+
const readme = readFileSync(join(TMP_DIR, 'README.md'), 'utf-8');
|
|
38
|
+
return parseToolsFromReadme(readme);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse browser_* tools from the MCP README markdown.
|
|
42
|
+
* Format:
|
|
43
|
+
* - **browser_navigate**
|
|
44
|
+
* - Title: Navigate to a URL
|
|
45
|
+
* - Description: Navigate to a URL
|
|
46
|
+
*/
|
|
47
|
+
function parseToolsFromReadme(readme) {
|
|
48
|
+
const tools = [];
|
|
49
|
+
// Match tool blocks: **browser_name** followed by Title and Description
|
|
50
|
+
const blockRegex = /- \*\*([a-z_]+)\*\*[\s\S]*?- Title: ([^\n]+)\n[\s\S]*?- Description: ([^\n]+)/g;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = blockRegex.exec(readme)) !== null) {
|
|
53
|
+
const name = match[1];
|
|
54
|
+
const title = match[2].trim();
|
|
55
|
+
const description = match[3].trim();
|
|
56
|
+
// Map tool name to Healer purpose
|
|
57
|
+
const purpose = mapToolToPurpose(name, title, description);
|
|
58
|
+
tools.push({ name, title, description, purpose });
|
|
59
|
+
}
|
|
60
|
+
return tools;
|
|
61
|
+
}
|
|
62
|
+
/** Map a browser_* tool to its Healer purpose in the SKILL */
|
|
63
|
+
function mapToolToPurpose(name, title, description) {
|
|
64
|
+
const map = {
|
|
65
|
+
browser_navigate: 'Go to the failing test\'s page',
|
|
66
|
+
browser_snapshot: 'Get page structure to find equivalent selectors',
|
|
67
|
+
browser_console_messages: 'Diagnose JS errors that may cause failures',
|
|
68
|
+
browser_take_screenshot: 'Visually compare before/after fixes',
|
|
69
|
+
browser_run_code: 'Execute custom fix logic (optional)',
|
|
70
|
+
};
|
|
71
|
+
return map[name] || title;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Update the Healer MCP tools table in SKILL.md.
|
|
75
|
+
* Only syncs the 5 core Healer tools; all other MCP tools are
|
|
76
|
+
* general-purpose automation not specific to healing.
|
|
77
|
+
* Deduplicates by tool name.
|
|
78
|
+
*/
|
|
79
|
+
export function updateSkillHealerTable(skillPath, tools) {
|
|
80
|
+
if (!existsSync(skillPath))
|
|
81
|
+
return;
|
|
82
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
83
|
+
// Only the 5 core Healer tools
|
|
84
|
+
const healerToolNames = [
|
|
85
|
+
'browser_navigate',
|
|
86
|
+
'browser_snapshot',
|
|
87
|
+
'browser_console_messages',
|
|
88
|
+
'browser_take_screenshot',
|
|
89
|
+
'browser_run_code',
|
|
90
|
+
];
|
|
91
|
+
// Filter to healer tools, deduplicate by name
|
|
92
|
+
const healerTools = tools.filter(t => healerToolNames.includes(t.name));
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
const unique = healerTools.filter(t => {
|
|
95
|
+
if (seen.has(t.name))
|
|
96
|
+
return false;
|
|
97
|
+
seen.add(t.name);
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
// Build new table rows
|
|
101
|
+
const rows = unique.map(t => `| \`${t.name}\` | ${t.purpose} |`).join('\n');
|
|
102
|
+
// Replace the existing table
|
|
103
|
+
const tableHeader = '| Tool | Purpose |';
|
|
104
|
+
const tableSep = '|------|---------|';
|
|
105
|
+
const start = content.indexOf(tableHeader);
|
|
106
|
+
const end = content.indexOf(tableSep, start + 1);
|
|
107
|
+
if (start === -1 || end === -1)
|
|
108
|
+
return;
|
|
109
|
+
const newSection = tableHeader + '\n' + tableSep + '\n' + rows;
|
|
110
|
+
const newContent = content.substring(0, start) +
|
|
111
|
+
newSection +
|
|
112
|
+
content.substring(end + tableSep.length);
|
|
113
|
+
writeFileSync(skillPath, newContent, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Main entry point: fetch tools and update SKILL.md.
|
|
117
|
+
* Call this after installing the skill in init.ts and update.ts.
|
|
118
|
+
*/
|
|
119
|
+
export function syncMcpTools(skillPath) {
|
|
120
|
+
try {
|
|
121
|
+
const tools = fetchMcpTools();
|
|
122
|
+
updateSkillHealerTable(skillPath, tools);
|
|
123
|
+
console.log(chalk.green(` ✓ Healer tools synced from @playwright/mcp`));
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.log(chalk.yellow(` ⚠ Failed to sync MCP tools: ${err}`));
|
|
127
|
+
console.log(chalk.gray(' SKILL.md MCP tools table may be stale'));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=mcp-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/utils/mcp-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: playwright-e2e
|
|
2
|
+
version: 1
|
|
3
|
+
description: Playwright E2E verification for an OpenSpec change
|
|
4
|
+
|
|
5
|
+
artifacts:
|
|
6
|
+
- id: test-plan
|
|
7
|
+
generates: specs/playwright/test-plan.md
|
|
8
|
+
description: Test plan derived from change specs
|
|
9
|
+
template: test-plan.md
|
|
10
|
+
instruction: |
|
|
11
|
+
Generate a test plan from the change specs. Each functional requirement becomes
|
|
12
|
+
one or more test cases.
|
|
13
|
+
|
|
14
|
+
For each test case, specify:
|
|
15
|
+
- **Test name** (kebab-case): what it verifies
|
|
16
|
+
- **Route/Page**: which URL or UI component it targets
|
|
17
|
+
- **Prerequisites**: auth role needed (user/admin/guest/none)
|
|
18
|
+
- **Happy path**: main user flow to verify
|
|
19
|
+
- **Error paths**: key error conditions to verify
|
|
20
|
+
|
|
21
|
+
Mark tests with role tags: `@role(user)`, `@role(admin)`, `@role(guest)`, `@role(none)`.
|
|
22
|
+
Mark auth requirement: `@auth(required)` or `@auth(none)`.
|
|
23
|
+
|
|
24
|
+
- id: e2e-test
|
|
25
|
+
generates: tests/playwright/<change>.spec.ts
|
|
26
|
+
description: Playwright E2E test suite
|
|
27
|
+
template: e2e-test.ts
|
|
28
|
+
instruction: |
|
|
29
|
+
Generate Playwright tests from the test-plan.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
- Follow the page object pattern from seed.spec.ts
|
|
33
|
+
- Prefer `data-testid` selectors, fall back to semantic selectors
|
|
34
|
+
- Each test maps to one test case from the test-plan
|
|
35
|
+
- Use `@project(user)` / `@project(admin)` for role-specific tests
|
|
36
|
+
- Cover happy path AND key error paths
|
|
37
|
+
- Name tests descriptively: `test('shows error on invalid input')`
|
|
38
|
+
|
|
39
|
+
- id: playwright-config
|
|
40
|
+
generates: playwright.config.ts
|
|
41
|
+
description: Playwright config with webServer and auth projects
|
|
42
|
+
template: playwright.config.ts
|
|
43
|
+
instruction: |
|
|
44
|
+
Configure Playwright for E2E testing.
|
|
45
|
+
|
|
46
|
+
- Set webServer command and url from BASE_URL
|
|
47
|
+
- Set baseURL for all tests
|
|
48
|
+
- Add auth setup project if credentials are configured
|
|
49
|
+
- Preserve any existing config fields (browsers, retries, etc.)
|
|
50
|
+
- Do NOT overwrite existing projects unless they conflict
|
|
51
|
+
|
|
52
|
+
apply:
|
|
53
|
+
requires: [test-plan, e2e-test]
|
|
54
|
+
instruction: |
|
|
55
|
+
Run tests via: openspec-pw run <change-name>
|
|
56
|
+
Analyze results and generate report at openspec/reports/playwright-e2e-<change>-<timestamp>.md
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { test, expect, Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
// ──────────────────────────────────────────────
|
|
4
|
+
// Test plan: <change-name>
|
|
5
|
+
// Generated from: openspec/changes/<change-name>/specs/playwright/test-plan.md
|
|
6
|
+
// ──────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Page Object Pattern - add selectors for your app's pages
|
|
12
|
+
*/
|
|
13
|
+
class AppPage {
|
|
14
|
+
constructor(private page: Page) {}
|
|
15
|
+
|
|
16
|
+
async goto(path: string = '/') {
|
|
17
|
+
await this.page.goto(`${BASE_URL}${path}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async getByTestId(id: string) {
|
|
21
|
+
return this.page.locator(`[data-testid="${id}"]`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async waitForToast(message?: string) {
|
|
25
|
+
if (message) {
|
|
26
|
+
await this.page.getByText(message, { state: 'visible' }).waitFor();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createPage(page: Page): AppPage {
|
|
32
|
+
return new AppPage(page);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ──────────────────────────────────────────────
|
|
36
|
+
// Tests - generated from test-plan.md
|
|
37
|
+
// Customize selectors and assertions to match your app
|
|
38
|
+
// ──────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
test.describe('<change-name>: E2E verification', () => {
|
|
41
|
+
|
|
42
|
+
test.beforeEach(async ({ page }) => {
|
|
43
|
+
const app = createPage(page);
|
|
44
|
+
await app.goto('/');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// TODO: Add test cases from specs/playwright/test-plan.md
|
|
48
|
+
// Example:
|
|
49
|
+
// test('shows expected content on page load', async ({ page }) => {
|
|
50
|
+
// const app = createPage(page);
|
|
51
|
+
// await app.goto('/');
|
|
52
|
+
// await expect(app.getByTestId('main-heading')).toBeVisible();
|
|
53
|
+
// });
|
|
54
|
+
|
|
55
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
// ─── BASE_URL: prefer env, then seed.spec.ts, then default ───
|
|
6
|
+
const seedSpec = join(__dirname, '../tests/playwright/seed.spec.ts');
|
|
7
|
+
let baseUrl = process.env.BASE_URL || 'http://localhost:3000';
|
|
8
|
+
if (existsSync(seedSpec)) {
|
|
9
|
+
const content = readFileSync(seedSpec, 'utf-8');
|
|
10
|
+
const m = content.match(/BASE_URL\s*=\s*process\.env\.BASE_URL\s*\|\|\s*['"]([^'"]+)['"]/);
|
|
11
|
+
if (m) baseUrl = m[1];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ─── Dev command: detect from package.json scripts ───
|
|
15
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
16
|
+
let devCmd = 'npm run dev';
|
|
17
|
+
if (existsSync(pkgPath)) {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
19
|
+
const scripts = pkg.scripts ?? {};
|
|
20
|
+
// Prefer in order: dev, start, serve, preview
|
|
21
|
+
devCmd = scripts.dev ?? scripts.start ?? scripts.serve ?? scripts.preview ?? devCmd;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
testDir: '../tests/playwright',
|
|
26
|
+
// Keep test artifacts inside tests/playwright/ instead of project root
|
|
27
|
+
outputDir: '../tests/playwright/test-results',
|
|
28
|
+
fullyParallel: true,
|
|
29
|
+
forbidOnly: !!process.env.CI,
|
|
30
|
+
retries: process.env.CI ? 2 : 0,
|
|
31
|
+
workers: process.env.CI ? 1 : undefined,
|
|
32
|
+
reporter: 'list',
|
|
33
|
+
|
|
34
|
+
use: {
|
|
35
|
+
baseURL: baseUrl,
|
|
36
|
+
trace: 'on-first-retry',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Dev server lifecycle - Playwright starts/stops automatically
|
|
40
|
+
webServer: {
|
|
41
|
+
command: devCmd,
|
|
42
|
+
url: baseUrl,
|
|
43
|
+
timeout: 120000,
|
|
44
|
+
reuseExistingServer: true,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Setup project for authentication (configured by openspec-pw run)
|
|
48
|
+
projects: [
|
|
49
|
+
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
|
|
50
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# E2E Verify Report: <change-name>
|
|
2
|
+
|
|
3
|
+
**Change**: `<change-name>`
|
|
4
|
+
**Generated**: `<timestamp>`
|
|
5
|
+
**Auth**: `<required|none>`
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
| Check | Status |
|
|
10
|
+
|-------|--------|
|
|
11
|
+
| Tests Run | <N> |
|
|
12
|
+
| Passed | <X> |
|
|
13
|
+
| Failed | <Y> |
|
|
14
|
+
| Auto-heals | <Z> |
|
|
15
|
+
| Final Status | <PASS|FAIL> |
|
|
16
|
+
|
|
17
|
+
## Test Results
|
|
18
|
+
|
|
19
|
+
<!-- List each test with pass/fail status -->
|
|
20
|
+
|
|
21
|
+
## Auto-Heal Log
|
|
22
|
+
|
|
23
|
+
<!-- If heals were performed, document each attempt -->
|
|
24
|
+
|
|
25
|
+
## Recommendations
|
|
26
|
+
|
|
27
|
+
<!-- For failed tests, specific file:line references and fixes -->
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Test Plan: <change-name>
|
|
2
|
+
|
|
3
|
+
Generated from: `openspec/changes/<change-name>/specs/`
|
|
4
|
+
|
|
5
|
+
## Auth Requirements
|
|
6
|
+
|
|
7
|
+
<!-- Mark auth requirements based on specs analysis: -->
|
|
8
|
+
- Auth required: **yes / no**
|
|
9
|
+
- Roles needed: none / user / admin / user+admin
|
|
10
|
+
|
|
11
|
+
## Test Cases
|
|
12
|
+
|
|
13
|
+
### <test-name>
|
|
14
|
+
|
|
15
|
+
- **Route**: `/<page>`
|
|
16
|
+
- **Role**: `@role(<role>)`
|
|
17
|
+
- **Auth**: `@auth(required|none)`
|
|
18
|
+
|
|
19
|
+
**Happy path:**
|
|
20
|
+
- Step 1: ...
|
|
21
|
+
- Step 2: ...
|
|
22
|
+
|
|
23
|
+
**Error paths:**
|
|
24
|
+
- ...
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspec-playwright",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
"release": "npm version patch && npm run build && git push && git push --tags && npm publish"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@types/tar": "^6.1.13",
|
|
17
18
|
"chalk": "^5.3.0",
|
|
18
19
|
"commander": "^12.1.0",
|
|
19
|
-
"tar": "^7.
|
|
20
|
+
"tar": "^7.5.13"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@types/node": "^22.0.0",
|
package/release-notes.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## What's Changed
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- feat: fix MCP_VERSION dup, expand seed, add selector memory (v2.10)
|
|
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.60
|
|
@@ -70,6 +70,16 @@ Priority: `[data-testid]` > `getByRole` > `getByLabel` > `getByText` > CSS
|
|
|
70
70
|
| auth method | API / UI | See auth.setup.ts |
|
|
71
71
|
| multi-user roles | `<roles>` | e.g. admin, user, guest |
|
|
72
72
|
|
|
73
|
+
## Selector Fixes (Healer memory)
|
|
74
|
+
|
|
75
|
+
Persists selector repairs across sessions. Prevents the same selector from being healed repeatedly.
|
|
76
|
+
|
|
77
|
+
| Date | Route | Old Selector | New Selector | Reason |
|
|
78
|
+
|------|-------|-------------|-------------|--------|
|
|
79
|
+
| | | | | |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
73
83
|
## Changelog
|
|
74
84
|
|
|
75
85
|
| Date | Change | By |
|
|
@@ -78,4 +88,4 @@ Priority: `[data-testid]` > `getByRole` > `getByLabel` > `getByText` > CSS
|
|
|
78
88
|
|
|
79
89
|
---
|
|
80
90
|
|
|
81
|
-
> **Updating this file**: After each E2E exploration (Step 4), extract new shared patterns and update this file. Generator (Step 6) reads this before writing tests.
|
|
91
|
+
> **Updating this file**: After each E2E exploration (Step 4), extract new shared patterns and update this file. Generator (Step 6) reads this before writing tests. After Healer repairs (Step 9), append the fix here.
|
package/src/commands/mcpSync.ts
CHANGED
|
@@ -24,6 +24,14 @@ export function getStoredMcpVersion(skillContent: string): string | null {
|
|
|
24
24
|
return skillContent.slice(idx + MCP_VERSION_MARKER.length, end).trim();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Remove all existing MCP_VERSION comment lines from content */
|
|
28
|
+
function removeMcpVersionMarkers(content: string): string {
|
|
29
|
+
return content
|
|
30
|
+
.split('\n')
|
|
31
|
+
.filter(line => !line.trim().startsWith(MCP_VERSION_MARKER))
|
|
32
|
+
.join('\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
/** Build the Healer tools table markdown */
|
|
28
36
|
function buildHealerTable(version: string, tools: Array<{ name: string; purpose: string }>): string {
|
|
29
37
|
const rows = tools.map(t => `| \`${t.name}\` | ${t.purpose} |`).join('\n');
|
|
@@ -36,13 +44,14 @@ export function updateHealerTable(
|
|
|
36
44
|
version: string,
|
|
37
45
|
tools: Array<{ name: string; purpose: string }>
|
|
38
46
|
): string {
|
|
39
|
-
const
|
|
47
|
+
const noMarkers = removeMcpVersionMarkers(skillContent);
|
|
48
|
+
const start = noMarkers.indexOf('| Tool | Purpose |');
|
|
40
49
|
if (start === -1) return skillContent;
|
|
41
|
-
let end =
|
|
42
|
-
if (end === -1) end =
|
|
50
|
+
let end = noMarkers.indexOf('\n\n', start);
|
|
51
|
+
if (end === -1) end = noMarkers.length;
|
|
43
52
|
|
|
44
|
-
const before =
|
|
45
|
-
const after =
|
|
53
|
+
const before = noMarkers.slice(0, start);
|
|
54
|
+
const after = noMarkers.slice(end);
|
|
46
55
|
return before + buildHealerTable(version, tools) + after;
|
|
47
56
|
}
|
|
48
57
|
|
package/templates/seed.spec.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Customize the page object and base URL for your application
|
|
4
4
|
|
|
5
5
|
import { test, expect, Page } from '@playwright/test';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
6
7
|
|
|
7
8
|
// Customize these for your application
|
|
8
9
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
@@ -63,6 +64,27 @@ test.describe('Application smoke tests', () => {
|
|
|
63
64
|
});
|
|
64
65
|
});
|
|
65
66
|
|
|
67
|
+
// ──────────────────────────────────────────────
|
|
68
|
+
// Environment validation (runs in Step 3)
|
|
69
|
+
// ──────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
test.describe('Environment validation', () => {
|
|
72
|
+
test('BASE_URL responds 200', async ({ page }) => {
|
|
73
|
+
const res = await page.request.get(`${BASE_URL}/`);
|
|
74
|
+
expect(res.status(), `BASE_URL ${BASE_URL} returned ${res.status()}`).toBeLessThan(500);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('auth storageState exists if credentials provided', async () => {
|
|
78
|
+
if (!process.env.E2E_USERNAME) return; // skip if no credentials configured
|
|
79
|
+
const authPath = 'playwright/.auth/user.json';
|
|
80
|
+
if (!existsSync(authPath)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Auth not configured. Run:\n npx playwright test --project=setup\nThen re-run /opsx:e2e.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
66
88
|
// ──────────────────────────────────────────────
|
|
67
89
|
// Example: Role-based tests with @tag
|
|
68
90
|
// Use tags (@admin, @user) for permission filtering instead of
|
|
Binary file
|