openspec-playwright 0.1.50 → 0.1.52
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.
|
@@ -52,7 +52,7 @@ Read all files from `openspec/changes/<name>/specs/*.md`. Extract functional req
|
|
|
52
52
|
|
|
53
53
|
Detect if auth is required only when BOTH conditions are met:
|
|
54
54
|
|
|
55
|
-
**Condition A — Explicit markers**: "login", "signin", "logout", "authenticate", "protected", "authenticated", "session", "unauthorized"
|
|
55
|
+
**Condition A — Explicit markers**: "login", "signin", "logout", "authenticate", "protected", "authenticated", "session", "unauthorized", "jwt", "token", "refresh", "middleware"
|
|
56
56
|
|
|
57
57
|
**Condition B — Context indicators**: Protected routes ("/dashboard", "/profile", "/admin"), role mentions ("admin", "user"), redirect flows
|
|
58
58
|
|
|
@@ -78,25 +78,13 @@ This validates: app server reachable, auth fixtures initialized, Playwright work
|
|
|
78
78
|
|
|
79
79
|
**Before writing test plan, explore the app to collect real DOM data.** This is the most critical step — it eliminates blind selector guessing.
|
|
80
80
|
|
|
81
|
-
**Prerequisites**: seed test pass. If auth is required, ensure `auth.setup.ts` has been run (Step 7).
|
|
81
|
+
**Prerequisites**: seed test pass. If auth is required, ensure `auth.setup.ts` has been run (Step 7). BASE_URL must be verified reachable (see 4.1).
|
|
82
82
|
|
|
83
|
-
#### 4.1. Read app-knowledge.md + Extract routes
|
|
83
|
+
#### 4.1. Verify BASE_URL + Read app-knowledge.md + Extract routes
|
|
84
84
|
|
|
85
|
-
**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
This is context, not constraint — explore with open eyes even if patterns differ from history.
|
|
90
|
-
|
|
91
|
-
**Then**, read all files in `openspec/changes/<name>/specs/*.md`. Extract every URL, route, or path mentioned:
|
|
92
|
-
|
|
93
|
-
- Full URLs: `http://localhost:3000/dashboard`, `BASE_URL + /admin`
|
|
94
|
-
- Relative paths: `/dashboard`, `/api/auth/login`, `/admin/settings`
|
|
95
|
-
- Infer routes from text: "navigate to the dashboard" → check if `/dashboard` exists
|
|
96
|
-
|
|
97
|
-
Group routes by role:
|
|
98
|
-
- **Guest routes**: `/`, `/login`, `/about` (no auth needed)
|
|
99
|
-
- **Protected routes**: `/dashboard`, `/profile`, `/admin` (auth required)
|
|
85
|
+
1. **Verify BASE_URL reachable**: `browser_navigate(BASE_URL)` → if error, check seed.spec.ts or vite.config.ts
|
|
86
|
+
2. **Read app-knowledge.md**: understand known risks (SPA, dynamic content) and project conventions
|
|
87
|
+
3. **Read specs**: extract all URLs/paths, group by Guest vs Protected
|
|
100
88
|
|
|
101
89
|
#### 4.2. Explore each route via Playwright MCP
|
|
102
90
|
|
|
@@ -140,66 +128,29 @@ Wait for page stability after navigation:
|
|
|
140
128
|
|
|
141
129
|
From `browser_snapshot` output, extract **interactive elements** for each route:
|
|
142
130
|
|
|
143
|
-
| Element type | What to capture |
|
|
131
|
+
| Element type | What to capture | Selector priority |
|
|
144
132
|
|---|---|---|
|
|
145
|
-
| **Buttons** | text, selector
|
|
146
|
-
| **Form fields** | name, type, label, selector | data-testid > name > label |
|
|
147
|
-
| **Navigation links** | text, href, selector | text > href |
|
|
133
|
+
| **Buttons** | text, selector | `[data-testid]` > `getByRole` > `getByLabel` > `getByText` |
|
|
134
|
+
| **Form fields** | name, type, label, selector | `[data-testid]` > `name` > `label` |
|
|
135
|
+
| **Navigation links** | text, href, selector | `text` > `href` |
|
|
148
136
|
| **Headings** | text content, selector | for assertions |
|
|
149
137
|
| **Error messages** | text patterns, selector | for error path testing |
|
|
150
|
-
| **Dynamic content** | structure
|
|
151
|
-
|
|
152
|
-
**Selector strategy** (in priority order):
|
|
153
|
-
1. `[data-testid="..."]` — most stable, prefer these
|
|
154
|
-
2. `getByRole('button', { name: '...' })` — semantic, stable
|
|
155
|
-
3. `getByLabel('...')` — for form fields
|
|
156
|
-
4. `getByText('...')` — fallback, fragile
|
|
157
|
-
5. CSS selectors — last resort
|
|
138
|
+
| **Dynamic content** | structure — row counts, card layouts | for data-driven tests |
|
|
158
139
|
|
|
159
140
|
#### 4.4. Write app-exploration.md
|
|
160
141
|
|
|
161
142
|
Output: `openspec/changes/<name>/specs/playwright/app-exploration.md`
|
|
162
143
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
BASE_URL
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- **
|
|
170
|
-
- **
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
- login link: `a:text("登录")`
|
|
174
|
-
- signup link: `[data-testid="signup-link"]`
|
|
175
|
-
- **Screenshot**: `__screenshots__/index.png`
|
|
176
|
-
|
|
177
|
-
## Route: /dashboard (user)
|
|
178
|
-
- **Auth**: required (storageState: playwright/.auth/user.json)
|
|
179
|
-
- **URL**: ${BASE_URL}/dashboard
|
|
180
|
-
- **Ready signal**: [data-testid="dashboard-heading"] visible
|
|
181
|
-
- **Elements**:
|
|
182
|
-
- heading: `[data-testid="page-title"]`
|
|
183
|
-
- logout btn: `[data-testid="logout-btn"]`
|
|
184
|
-
- profile form: `form >> input[name="displayName"]`
|
|
185
|
-
- settings link: `nav >> text=Settings`
|
|
186
|
-
- **Screenshot**: `__screenshots__/dashboard-user.png`
|
|
187
|
-
|
|
188
|
-
## Route: /admin (admin)
|
|
189
|
-
- **Auth**: required (storageState: playwright/.auth/admin.json)
|
|
190
|
-
- **URL**: ${BASE_URL}/admin
|
|
191
|
-
- **Ready signal**: [data-testid="admin-panel"] visible
|
|
192
|
-
- **Elements**:
|
|
193
|
-
- admin panel: `[data-testid="admin-panel"]`
|
|
194
|
-
- user table: `table#user-table tbody tr`
|
|
195
|
-
- add user btn: `[data-testid="add-user-btn"]`
|
|
196
|
-
- delete btn: `button:has-text("Delete")`
|
|
197
|
-
- **Screenshot**: `__screenshots__/admin-panel.png`
|
|
198
|
-
|
|
199
|
-
## Exploration Notes
|
|
200
|
-
- Route /admin → user gets redirected to /login (no admin role)
|
|
201
|
-
- /dashboard loads user-specific data (test assertions should use toContainText, not toHaveText)
|
|
202
|
-
```
|
|
144
|
+
Use template: `openspec/schemas/playwright-e2e/templates/app-exploration.md`
|
|
145
|
+
|
|
146
|
+
Key fields per route:
|
|
147
|
+
- **URL**: `${BASE_URL}<path>`
|
|
148
|
+
- **Auth**: none / required (storageState: `<path>`)
|
|
149
|
+
- **Ready signal**: how to know the page is loaded
|
|
150
|
+
- **Elements**: interactive elements with verified selectors (see 4.3 table)
|
|
151
|
+
- **Screenshot**: `__screenshots__/<slug>.png`
|
|
152
|
+
|
|
153
|
+
After exploration, add route-level notes (redirects, dynamic content → see 4.5).
|
|
203
154
|
|
|
204
155
|
#### 4.5. Edge cases
|
|
205
156
|
|
|
@@ -248,18 +199,9 @@ Create test cases:
|
|
|
248
199
|
- List each functional requirement as a test case
|
|
249
200
|
- Mark with `@role(user|admin|guest|none)` and `@auth(required|none)`
|
|
250
201
|
- Include happy path AND error paths
|
|
251
|
-
- Reference the **real route URL** from app-exploration.md
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
```markdown
|
|
255
|
-
### User can view dashboard
|
|
256
|
-
- **Route**: /dashboard (from app-exploration.md)
|
|
257
|
-
- **Auth**: required (user storageState)
|
|
258
|
-
- **Test steps**:
|
|
259
|
-
1. Go to `/dashboard`
|
|
260
|
-
2. Assert page heading: `[data-testid="page-title"]` contains "Dashboard"
|
|
261
|
-
3. Assert logout button visible: `[data-testid="logout-btn"]`
|
|
262
|
-
```
|
|
202
|
+
- Reference the **real route URL** and **verified selectors** from app-exploration.md
|
|
203
|
+
|
|
204
|
+
Example: see `openspec/schemas/playwright-e2e/templates/test-plan.md`
|
|
263
205
|
|
|
264
206
|
**Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate.
|
|
265
207
|
|
|
@@ -275,7 +217,7 @@ Create `tests/playwright/<name>.spec.ts`:
|
|
|
275
217
|
|
|
276
218
|
#### Verify selectors before writing
|
|
277
219
|
|
|
278
|
-
**For each test case, verify selectors in a real browser BEFORE writing
|
|
220
|
+
**For each test case, verify selectors in a real browser BEFORE writing test code.**
|
|
279
221
|
|
|
280
222
|
```
|
|
281
223
|
For each test case in test-plan.md:
|
|
@@ -289,7 +231,7 @@ For each test case in test-plan.md:
|
|
|
289
231
|
a. Check if selector exists in app-exploration.md
|
|
290
232
|
b. If yes → verify it's still valid via browser_snapshot
|
|
291
233
|
c. If no → find equivalent from current snapshot
|
|
292
|
-
d. Selector priority:
|
|
234
|
+
d. Selector priority: see 4.3 table
|
|
293
235
|
7. Write test code with verified selectors
|
|
294
236
|
8. If selector cannot be verified → note it for Healer (Step 9)
|
|
295
237
|
```
|
|
@@ -298,7 +240,7 @@ This ensures every selector in the generated test code has been validated agains
|
|
|
298
240
|
|
|
299
241
|
**Generate** Playwright code for each verified test case:
|
|
300
242
|
- Follow `seed.spec.ts` structure
|
|
301
|
-
- Prefer `data-testid
|
|
243
|
+
- Prefer `data-testid` selectors (see 4.3 table for priority)
|
|
302
244
|
- Include happy path AND error/edge cases
|
|
303
245
|
- Use `test.describe(...)` for grouping
|
|
304
246
|
- Each test: `test('描述性名称', async ({ page }) => { ... })`
|
|
@@ -332,34 +274,29 @@ test('redirects unauthenticated user to login', async ({ browser }) => {
|
|
|
332
274
|
|
|
333
275
|
These are the most common mistakes that cause test failures. **Always follow these rules:**
|
|
334
276
|
|
|
335
|
-
**API calls — use `request`
|
|
277
|
+
**API calls — use `page.request` directly:**
|
|
336
278
|
```typescript
|
|
337
|
-
// ❌ WRONG — page.evaluate timeout,
|
|
279
|
+
// ❌ WRONG — page.evaluate with fetch has timeout, CORS, and context issues
|
|
338
280
|
const result = await page.evaluate(async () => {
|
|
339
281
|
const res = await fetch('/api/data');
|
|
340
282
|
return res.json();
|
|
341
283
|
});
|
|
342
284
|
|
|
343
|
-
// ✅ CORRECT —
|
|
285
|
+
// ✅ CORRECT — page.request is already an APIRequestContext, use directly
|
|
344
286
|
const res = await page.request.get(`${BASE_URL}/api/data`);
|
|
345
287
|
expect(res.status()).toBe(200);
|
|
346
288
|
const data = await res.json();
|
|
347
289
|
```
|
|
348
290
|
|
|
349
|
-
**Browser context
|
|
291
|
+
**Browser context — use `close()` for BrowserContext, no cleanup for APIRequestContext:**
|
|
350
292
|
```typescript
|
|
351
|
-
//
|
|
352
|
-
const ctx = await page.request.newContext();
|
|
353
|
-
await ctx.dispose(); // actually correct, but this is CommonContext
|
|
354
|
-
const context = await browser.newContext();
|
|
355
|
-
await context.close(); // ← WRONG
|
|
356
|
-
|
|
357
|
-
// ✅ CORRECT
|
|
293
|
+
// ✅ BrowserContext — close it when done
|
|
358
294
|
const context = await browser.newContext();
|
|
359
|
-
await context.close(); //
|
|
295
|
+
await context.close(); // ← correct
|
|
360
296
|
|
|
361
|
-
//
|
|
362
|
-
|
|
297
|
+
// ✅ APIRequestContext — page.request is already one, no cleanup needed
|
|
298
|
+
const res = await page.request.get(`${BASE_URL}/api/data`);
|
|
299
|
+
// No dispose() or close() needed
|
|
363
300
|
```
|
|
364
301
|
|
|
365
302
|
**File uploads — use `setInputFiles()`, NOT `page.evaluate()` + fetch:**
|
|
@@ -457,18 +394,15 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
|
457
394
|
| **Selector changed** | Element not found | `browser_snapshot` → fix selector → re-run |
|
|
458
395
|
| **Assertion mismatch** | Wrong content/value | `browser_snapshot` → compare → fix assertion → re-run |
|
|
459
396
|
| **Timing issue** | `waitFor`/`page.evaluate` timeout | Switch to `request` API or add `waitFor` → re-run |
|
|
460
|
-
| **
|
|
461
|
-
| **Auth expired** | 401 Unauthorized | Token may have expired — if long suite, recommend splitting or re-auth |
|
|
462
|
-
| **page.evaluate failure** | `fetch` in browser context, CORS errors | Switch to `page.request` API → re-run |
|
|
397
|
+
| **page.evaluate with fetch** | `fetch` in browser context, CORS errors | Switch to `page.request` API → re-run |
|
|
463
398
|
|
|
464
|
-
3. **
|
|
399
|
+
3. **Heal** (≤3 attempts): snapshot → fix → re-run
|
|
465
400
|
4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
|
|
466
401
|
|
|
467
402
|
### 10. False Pass Detection
|
|
468
403
|
|
|
469
|
-
Run after test suite completes (even if all pass):
|
|
470
|
-
|
|
471
|
-
- **Conditional logic**: Look for `if (locator.isVisible().catch(() => false))` — if test passes, locator may not exist
|
|
404
|
+
Run after test suite completes (even if all pass). Common patterns (see Step 6 Anti-Pattern Warnings for fixes):
|
|
405
|
+
- **Conditional visibility**: `if (locator.isVisible().catch(() => false))` — if test passes, locator may not exist
|
|
472
406
|
- **Too fast**: < 200ms for a complex flow is suspicious
|
|
473
407
|
- **No fresh auth context**: Protected routes without `browser.newContext()`
|
|
474
408
|
|
|
@@ -481,45 +415,13 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
|
481
415
|
- Auto-heal notes
|
|
482
416
|
- Recommendations with `file:line` references
|
|
483
417
|
|
|
418
|
+
Report template: `openspec/schemas/playwright-e2e/templates/report.md`
|
|
419
|
+
|
|
484
420
|
**Update tasks.md** if all tests pass: find E2E-related items, append `✅ Verified via Playwright E2E (<timestamp>)`.
|
|
485
421
|
|
|
486
422
|
## Report Structure
|
|
487
423
|
|
|
488
|
-
|
|
489
|
-
# Playwright E2E Report — <name>
|
|
490
|
-
|
|
491
|
-
## Summary
|
|
492
|
-
| Tests | Passed | Failed | Duration | Status |
|
|
493
|
-
|-------|--------|--------|----------|--------|
|
|
494
|
-
| N | N | N | Xm Xs | ✅/❌ |
|
|
495
|
-
|
|
496
|
-
## Results
|
|
497
|
-
### Passed
|
|
498
|
-
| Test | Duration | Notes |
|
|
499
|
-
|------|----------|-------|
|
|
500
|
-
| ... | ... | ... |
|
|
501
|
-
|
|
502
|
-
### Failed
|
|
503
|
-
| Test | Error | Recommendation |
|
|
504
|
-
|------|-------|----------------|
|
|
505
|
-
| ... | ... | file:line — fix |
|
|
506
|
-
|
|
507
|
-
## Auto-Heal Log
|
|
508
|
-
- Attempt N: selector fix → result
|
|
509
|
-
|
|
510
|
-
## Coverage
|
|
511
|
-
- [x] Requirement 1
|
|
512
|
-
- [ ] Requirement 2 (unverified)
|
|
513
|
-
|
|
514
|
-
## ⚠️ Coverage Gaps
|
|
515
|
-
> Tests passed but coverage gaps detected.
|
|
516
|
-
|
|
517
|
-
| Test | Gap | Recommendation |
|
|
518
|
-
|------|-----|----------------|
|
|
519
|
-
| ... | Conditional visibility check | file:line — use `expect().toBeVisible()` |
|
|
520
|
-
| ... | Auth guard uses inherited session | Add fresh context test |
|
|
521
|
-
| ... | Suspiciously fast execution (<200ms) | Verify test logic executed |
|
|
522
|
-
```
|
|
424
|
+
Reference: `openspec/schemas/playwright-e2e/templates/report.md`
|
|
523
425
|
|
|
524
426
|
## Graceful Degradation
|
|
525
427
|
|
|
@@ -543,7 +445,7 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
|
543
445
|
- Read specs from `openspec/changes/<name>/specs/` as source of truth
|
|
544
446
|
- Do NOT generate tests that contradict the specs
|
|
545
447
|
- **DO generate real, runnable Playwright test code** — not placeholders or TODOs
|
|
546
|
-
- Do NOT overwrite files outside: `specs/playwright/`, `tests/playwright/`, `openspec/reports/`, `playwright.config.ts`, `auth.setup.ts
|
|
448
|
+
- Do NOT overwrite files outside: `specs/playwright/`, `tests/playwright/`, `openspec/reports/`, `playwright.config.ts`, `auth.setup.ts`
|
|
547
449
|
- **Always explore before generating** — Step 4 is mandatory for accurate selectors
|
|
548
450
|
- Cap auto-heal at 3 attempts
|
|
549
451
|
- 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
|
+
- refactor(SKILL): trim 107 lines — remove duplication, inline templates, emotional language
|
|
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.52
|
|
Binary file
|