openspec-playwright 0.1.24 → 0.1.26

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).
6
6
  metadata:
7
7
  author: openspec-playwright
8
- version: "2.0"
8
+ version: "2.1"
9
9
  ---
10
10
 
11
11
  Run Playwright E2E verification for an OpenSpec change. This skill reads specs from `openspec/changes/<name>/specs/`, generates test files, detects auth requirements, and delegates test execution to the `openspec-pw run` CLI.
@@ -56,16 +56,47 @@ Create `openspec/changes/<name>/specs/playwright/test-plan.md` by:
56
56
 
57
57
  **Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate unless user explicitly asks.
58
58
 
59
- ### 4. Generate test file
59
+ ### 4. Generate test file (LLM-driven)
60
+
61
+ Use your file writing capability to create `tests/playwright/<name>.spec.ts`.
62
+
63
+ **Read inputs first**:
64
+ - `openspec/changes/<name>/specs/playwright/test-plan.md` — test cases with `@role` and `@auth` tags
65
+ - `tests/playwright/seed.spec.ts` — code pattern, BASE_URL, and page object structure
66
+
67
+ **Generate Playwright code** for each test case from test-plan.md:
68
+ - Follow the same structure as `seed.spec.ts`
69
+ - Prefer `data-testid` selectors; fallback to `input[name="..."]` or semantic selectors
70
+ - Include **happy path** AND **error/edge cases**
71
+ - Use `test.describe(...)` for grouping related tests
72
+ - Each test: `test('描述性名称', async ({ page }) => { ... })`
73
+ - Add `@project(user)` / `@project(admin)` on role-specific tests
74
+
75
+ **Example pattern** (from seed.spec.ts):
76
+ ```typescript
77
+ import { test, expect } from '@playwright/test';
78
+
79
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
80
+
81
+ test.describe('Feature Name', () => {
82
+ test('happy path - action succeeds', async ({ page }) => {
83
+ await page.goto(`${BASE_URL}/path`);
84
+ await page.getByTestId('element').click();
85
+ await expect(page.getByTestId('result')).toBeVisible();
86
+ });
87
+
88
+ test('error case - invalid input shows message', async ({ page }) => {
89
+ await page.goto(`${BASE_URL}/path`);
90
+ await page.getByTestId('input').fill('invalid');
91
+ await page.getByTestId('submit').click();
92
+ await expect(page.getByTestId('error')).toContainText('Error text');
93
+ });
94
+ });
95
+ ```
60
96
 
61
- Create `tests/playwright/<name>.spec.ts`:
62
- - Follow the page object pattern from `tests/playwright/seed.spec.ts`
63
- - Prefer `data-testid` selectors, fall back to semantic selectors
64
- - Each test maps to one test case from the test-plan
65
- - Use `@project(admin)` / `@project(user)` for role-specific tests
66
- - Cover happy path AND key error paths
97
+ **Write the file** using the Write tool. If the file already exists → diff against test-plan, add only missing test cases, preserve existing implementations.
67
98
 
68
- **Idempotency**: If test file exists diff against test-plan, add only missing test cases, preserve existing implementations.
99
+ **Selector guidance**: If no `data-testid` exists in the app, prefer `getByRole`, `getByLabel`, `getByText` over fragile selectors like CSS paths.
69
100
 
70
101
  ### 5. Configure auth (if required)
71
102
 
@@ -115,10 +146,20 @@ If tests fail → analyze failures, use Playwright MCP tools to inspect UI state
115
146
 
116
147
  ### 8. Report results
117
148
 
118
- Read the report generated by `openspec-pw run` and present it to the user:
149
+ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
150
+
151
+ **Present results to user**:
119
152
  - Summary table (tests run, passed, failed, duration, final status)
120
153
  - Any auto-heal notes
121
- - Recommendations for failed tests (with specific file:line references)
154
+ - Recommendations for failed tests (with specific `file:line` references)
155
+
156
+ **Update tasks.md** (if all tests pass):
157
+ - Read `openspec/changes/<name>/tasks.md`
158
+ - Find tasks related to E2E testing (look for `[ ]` items mentioning "test", "e2e", "playwright", "verify")
159
+ - Append a verification note: e.g. `✅ Verified via Playwright E2E (<timestamp>)`
160
+ - Write the updated content back using the Edit tool
161
+
162
+ **If tests fail**: Suggest which spec items remain unverified and what needs fixing.
122
163
 
123
164
  ---
124
165
 
@@ -133,6 +174,7 @@ Read the report generated by `openspec-pw run` and present it to the user:
133
174
 
134
175
  - Read specs from `openspec/changes/<name>/specs/` as source of truth
135
176
  - Do NOT generate tests that contradict the specs
177
+ - **DO generate real, runnable Playwright test code** — not placeholders or TODO comments. Every test case in test-plan.md must produce an actual `test(...)` block.
136
178
  - Do NOT overwrite files outside: `specs/playwright/`, `tests/playwright/`, `openspec/reports/`, `playwright.config.ts`, `tests/auth.setup.ts`
137
179
  - Cap auto-heal at 3 attempts
138
180
  - If no change specified → always ask user to select
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/release-notes.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ## What's Changed
2
2
 
3
- - fix: playwright.config.ts detects package.json in subdirectories
3
+ - feat(skill): LLM-driven test generation and tasks.md integration
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.24
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.26
@@ -1,8 +1,8 @@
1
1
  import { defineConfig, devices } from '@playwright/test';
2
- import { readFileSync, existsSync } from 'fs';
2
+ import { readFileSync, existsSync, readdirSync } from 'fs';
3
3
  import { join } from 'path';
4
4
 
5
- // ─── Detect project root (find package.json walking up) ───
5
+ // ─── Detect project root (where openspec/ lives) ───
6
6
  function findProjectRoot(start: string): string {
7
7
  let dir = start;
8
8
  for (let i = 0; i < 10; i++) {
@@ -14,7 +14,35 @@ function findProjectRoot(start: string): string {
14
14
  return start;
15
15
  }
16
16
 
17
+ // ─── Find the npm project root (where package.json with scripts lives) ───
18
+ // Checks project root first, then immediate subdirectories
19
+ function findNpmRoot(projectRoot: string): string {
20
+ const pkgAtRoot = join(projectRoot, 'package.json');
21
+ if (existsSync(pkgAtRoot)) {
22
+ const pkg = JSON.parse(readFileSync(pkgAtRoot, 'utf-8'));
23
+ if (pkg.scripts?.dev || pkg.scripts?.start || pkg.scripts?.serve || pkg.scripts?.preview) {
24
+ return projectRoot;
25
+ }
26
+ }
27
+ // Check immediate subdirectories (e.g., openspec/ and imap/ are siblings)
28
+ try {
29
+ const entries = readdirSync(projectRoot, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
32
+ const subPkg = join(projectRoot, entry.name, 'package.json');
33
+ if (existsSync(subPkg)) {
34
+ const sub = JSON.parse(readFileSync(subPkg, 'utf-8'));
35
+ if (sub.scripts?.dev || sub.scripts?.start) {
36
+ return join(projectRoot, entry.name);
37
+ }
38
+ }
39
+ }
40
+ } catch {}
41
+ return projectRoot;
42
+ }
43
+
17
44
  const projectRoot = findProjectRoot(__dirname);
45
+ const npmRoot = findNpmRoot(projectRoot);
18
46
 
19
47
  // ─── BASE_URL: prefer env, then seed.spec.ts, then default ───
20
48
  const seedSpec = join(projectRoot, 'tests', 'playwright', 'seed.spec.ts');
@@ -25,13 +53,17 @@ if (existsSync(seedSpec)) {
25
53
  if (m) baseUrl = m[1];
26
54
  }
27
55
 
28
- // ─── Dev command: detect from package.json scripts ───
29
- const pkgPath = join(projectRoot, 'package.json');
56
+ // ─── Dev command: detect from the npm project ───
30
57
  let devCmd = 'npm run dev';
31
- if (existsSync(pkgPath)) {
32
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
58
+ const npmPkg = join(npmRoot, 'package.json');
59
+ if (existsSync(npmPkg)) {
60
+ const pkg = JSON.parse(readFileSync(npmPkg, 'utf-8'));
33
61
  const scripts = pkg.scripts ?? {};
34
62
  devCmd = scripts.dev ?? scripts.start ?? scripts.serve ?? scripts.preview ?? devCmd;
63
+ // Prefix with cd if npmRoot differs from projectRoot
64
+ if (npmRoot !== projectRoot) {
65
+ devCmd = `cd ${npmRoot} && ${devCmd}`;
66
+ }
35
67
  }
36
68
 
37
69
  export default defineConfig({
Binary file