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.
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
|
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
package/release-notes.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## What's Changed
|
|
2
2
|
|
|
3
|
-
-
|
|
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.
|
|
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 (
|
|
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
|
|
29
|
-
const pkgPath = join(projectRoot, 'package.json');
|
|
56
|
+
// ─── Dev command: detect from the npm project ───
|
|
30
57
|
let devCmd = 'npm run dev';
|
|
31
|
-
|
|
32
|
-
|
|
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
|