openspec-playwright 0.1.58 → 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.
@@ -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.9"
8
+ version: "2.10"
9
9
  ---
10
10
 
11
11
  ## Input
@@ -32,6 +32,28 @@ Two modes, same pipeline:
32
32
 
33
33
  Both modes update `app-knowledge.md` and `app-exploration.md`. All `.spec.ts` files run together as regression suite.
34
34
 
35
+ ## Testing principles
36
+
37
+ **UI first** — Test every user flow through the browser UI. E2E validates that users can accomplish tasks in the real interface, not just that the backend responds correctly.
38
+
39
+ ```
40
+ 用户操作 → 浏览器 UI → 后端 → 数据库 → UI 反馈
41
+ ```
42
+
43
+ **API only as fallback** — Use `page.request` only when UI genuinely cannot cover the scenario:
44
+ - Triggering HTTP 5xx/4xx error responses (hard to reach via UI)
45
+ - Edge cases requiring pre-condition data that UI cannot set up
46
+ - Cases where Step 4 exploration confirmed no UI element exists
47
+
48
+ **Decision rule**:
49
+ ```
50
+ Can this be tested through the UI?
51
+ → Yes → page.getByRole/ByLabel/ByText + click/fill/type + assert UI
52
+ → No → record reason → use page.request
53
+ ```
54
+
55
+ **Never use API calls to replace routine UI flows.** If a test completes in < 200ms, it is almost certainly using `page.request` instead of real UI interactions.
56
+
35
57
  ## Steps
36
58
 
37
59
  ### 1. Select the change or mode
@@ -181,6 +203,7 @@ After writing `app-exploration.md`, extract **project-level shared knowledge** a
181
203
  | Common Selector Patterns | New patterns discovered that apply across routes |
182
204
  | SPA Routing | SPA framework, routing behavior |
183
205
  | Project Conventions | BASE_URL, auth method, multi-user roles |
206
+ | Selector Fixes | Healed selectors (see Step 9) — route, old selector, new selector, reason |
184
207
 
185
208
  Append only new/changed items — preserve existing content.
186
209
 
@@ -230,44 +253,58 @@ Template: `openspec/schemas/playwright-e2e/templates/test-plan.md`
230
253
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
231
254
  - Prefer `data-testid` selectors (see 4.3 table)
232
255
 
233
- **Code examples — same for both modes:**
256
+ **Code examples — UI first:**
234
257
 
235
258
  ```typescript
236
- // 🚫 False Passnever silently skip when element is missing
259
+ // UI 测试用户在界面上的真实操作
260
+ await page.goto(`${BASE_URL}/orders`);
261
+ await page.getByRole('button', { name: '新建订单' }).click();
262
+ await page.getByLabel('订单名称').fill('Test Order');
263
+ await page.getByRole('button', { name: '提交' }).click();
264
+ await expect(page.getByText('订单创建成功')).toBeVisible();
265
+
266
+ // ✅ Error path — 通过 UI 触发错误
267
+ await page.goto(`${BASE_URL}/orders`);
268
+ await page.getByRole('button', { name: '新建订单' }).click();
269
+ await page.getByRole('button', { name: '提交' }).click();
270
+ await expect(page.getByRole('alert')).toContainText('名称不能为空');
271
+
272
+ // ✅ API fallback — 仅在 UI 无法触发时使用
273
+ const res = await page.request.get(`${BASE_URL}/api/orders/99999`);
274
+ expect(res.status()).toBe(404);
275
+ ```
276
+
277
+ ```typescript
278
+ // 🚫 False Pass — 元素不存在时静默跳过
237
279
  if (await btn.isVisible().catch(() => false)) { ... }
280
+
238
281
  // ✅ CORRECT
239
282
  await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
240
283
 
241
- // 🚫 Auth guard test with FRESH browser context
242
- test('redirects unauthenticated user to login', async ({ browser }) => {
243
- const freshPage = await browser.newContext().newPage();
244
- await freshPage.goto(`${BASE_URL}/dashboard`);
245
- await expect(freshPage).toHaveURL(/login|auth/);
246
- });
284
+ // 🚫 API 替代 UI 失去了端到端的意义
285
+ const res = await page.request.post(`${BASE_URL}/api/login`, { data: credentials });
247
286
 
248
- // ✅ API calls use page.request directly
249
- const res = await page.request.get(`${BASE_URL}/api/data`);
250
- expect(res.status()).toBe(200);
287
+ // ✅ CORRECT通过 UI 登录
288
+ await page.goto(`${BASE_URL}/login`);
289
+ await page.getByLabel('邮箱').fill(process.env.E2E_USERNAME);
290
+ await page.getByLabel('密码').fill(process.env.E2E_PASSWORD);
291
+ await page.getByRole('button', { name: '登录' }).click();
292
+ await expect(page).toHaveURL(/dashboard/);
251
293
  ```
252
294
 
253
295
  ```typescript
254
- // ✅ BrowserContext close when done
255
- const context = await browser.newContext();
256
- await context.close();
257
-
258
- // ✅ APIRequestContext — page.request is already one, no cleanup needed
259
- const res = await page.request.get(`${BASE_URL}/api/data`);
260
- ```
296
+ // ✅ Fresh browser context for auth guard
297
+ test('unauthenticated user redirected to login', async ({ browser }) => {
298
+ const freshPage = await browser.newContext().newPage();
299
+ await freshPage.goto(`${BASE_URL}/dashboard`);
300
+ await expect(freshPage).toHaveURL(/login|auth/);
301
+ });
261
302
 
262
- ```typescript
263
- // ✅ File uploads — use setInputFiles()
303
+ // ✅ File uploads — UI 操作
264
304
  await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');
265
-
266
- // ✅ Form submissions — prefer Playwright locators
267
- await page.getByRole('button', { name: 'Submit' }).click();
268
305
  ```
269
306
 
270
- Always include error path tests: API 500, 404, network timeout, invalid input.
307
+ Always include error path tests: UI validation messages, network failure, invalid input. Use `page.request` only for scenarios confirmed unreachable via UI.
271
308
 
272
309
  If the file exists → diff against test-plan, add only missing test cases.
273
310
 
@@ -321,8 +358,6 @@ The CLI handles: server lifecycle, port mismatch, report generation.
321
358
  If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
322
359
 
323
360
  **Healer MCP tools** (in order of use):
324
- <!-- MCP_VERSION: 0.0.68 -->
325
-
326
361
  <!-- MCP_VERSION: 0.0.70 -->
327
362
 
328
363
  | Tool | Purpose |
@@ -345,7 +380,7 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
345
380
  | **Timing issue** | `waitFor`/`page.evaluate` timeout | Switch to `request` API or add `waitFor` → re-run |
346
381
  | **page.evaluate with fetch** | `fetch` in browser context, CORS errors | Switch to `page.request` API → re-run |
347
382
 
348
- 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.
349
384
  4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
350
385
 
351
386
  ### 10. False Pass Detection
@@ -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 start = skillContent.indexOf('| Tool | Purpose |');
38
+ const noMarkers = removeMcpVersionMarkers(skillContent);
39
+ const start = noMarkers.indexOf('| Tool | Purpose |');
32
40
  if (start === -1)
33
41
  return skillContent;
34
- let end = skillContent.indexOf('\n\n', start);
42
+ let end = noMarkers.indexOf('\n\n', start);
35
43
  if (end === -1)
36
- end = skillContent.length;
37
- const before = skillContent.slice(0, start);
38
- const after = skillContent.slice(end);
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":"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"}
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.58",
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.0.0"
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
- - feat(SKILL): unify "all" and "change" modes same pipeline, different scope
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.58
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.
@@ -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 start = skillContent.indexOf('| Tool | Purpose |');
47
+ const noMarkers = removeMcpVersionMarkers(skillContent);
48
+ const start = noMarkers.indexOf('| Tool | Purpose |');
40
49
  if (start === -1) return skillContent;
41
- let end = skillContent.indexOf('\n\n', start);
42
- if (end === -1) end = skillContent.length;
50
+ let end = noMarkers.indexOf('\n\n', start);
51
+ if (end === -1) end = noMarkers.length;
43
52
 
44
- const before = skillContent.slice(0, start);
45
- const after = skillContent.slice(end);
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
 
@@ -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