openspec-playwright 0.1.51 → 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
|
|
|
@@ -80,31 +80,11 @@ This validates: app server reachable, auth fixtures initialized, Playwright work
|
|
|
80
80
|
|
|
81
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. Verify BASE_URL + Read app-knowledge.md + Extract routes
|
|
83
|
+
#### 4.1. Verify BASE_URL + Read app-knowledge.md + Extract routes
|
|
84
84
|
|
|
85
|
-
**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Confirm page loaded → proceed
|
|
89
|
-
// If error → check BASE_URL in seed.spec.ts or vite.config.ts
|
|
90
|
-
```
|
|
91
|
-
A correct BASE_URL prevents wasted exploration time on unreachable routes.
|
|
92
|
-
|
|
93
|
-
**Then**, read `tests/playwright/app-knowledge.md` to understand:
|
|
94
|
-
- **Known risks**: SPA behavior, dynamic content patterns, auth method
|
|
95
|
-
- **Project conventions**: preferred selector strategy, credential format, BASE_URL
|
|
96
|
-
|
|
97
|
-
This is context, not constraint — explore with open eyes even if patterns differ from history.
|
|
98
|
-
|
|
99
|
-
**Then**, read all files in `openspec/changes/<name>/specs/*.md`. Extract every URL, route, or path mentioned:
|
|
100
|
-
|
|
101
|
-
- Full URLs: `http://localhost:3000/dashboard`, `BASE_URL + /admin`
|
|
102
|
-
- Relative paths: `/dashboard`, `/api/auth/login`, `/admin/settings`
|
|
103
|
-
- Infer routes from text: "navigate to the dashboard" → check if `/dashboard` exists
|
|
104
|
-
|
|
105
|
-
Group routes by role:
|
|
106
|
-
- **Guest routes**: `/`, `/login`, `/about` (no auth needed)
|
|
107
|
-
- **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
|
|
108
88
|
|
|
109
89
|
#### 4.2. Explore each route via Playwright MCP
|
|
110
90
|
|
|
@@ -148,66 +128,29 @@ Wait for page stability after navigation:
|
|
|
148
128
|
|
|
149
129
|
From `browser_snapshot` output, extract **interactive elements** for each route:
|
|
150
130
|
|
|
151
|
-
| Element type | What to capture |
|
|
131
|
+
| Element type | What to capture | Selector priority |
|
|
152
132
|
|---|---|---|
|
|
153
|
-
| **Buttons** | text, selector
|
|
154
|
-
| **Form fields** | name, type, label, selector | data-testid > name > label |
|
|
155
|
-
| **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` |
|
|
156
136
|
| **Headings** | text content, selector | for assertions |
|
|
157
137
|
| **Error messages** | text patterns, selector | for error path testing |
|
|
158
|
-
| **Dynamic content** | structure
|
|
159
|
-
|
|
160
|
-
**Selector strategy** (in priority order):
|
|
161
|
-
1. `[data-testid="..."]` — most stable, prefer these
|
|
162
|
-
2. `getByRole('button', { name: '...' })` — semantic, stable
|
|
163
|
-
3. `getByLabel('...')` — for form fields
|
|
164
|
-
4. `getByText('...')` — fallback, fragile
|
|
165
|
-
5. CSS selectors — last resort
|
|
138
|
+
| **Dynamic content** | structure — row counts, card layouts | for data-driven tests |
|
|
166
139
|
|
|
167
140
|
#### 4.4. Write app-exploration.md
|
|
168
141
|
|
|
169
142
|
Output: `openspec/changes/<name>/specs/playwright/app-exploration.md`
|
|
170
143
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
BASE_URL
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- **
|
|
178
|
-
- **
|
|
179
|
-
|
|
180
|
-
-
|
|
181
|
-
- login link: `a:text("登录")`
|
|
182
|
-
- signup link: `[data-testid="signup-link"]`
|
|
183
|
-
- **Screenshot**: `__screenshots__/index.png`
|
|
184
|
-
|
|
185
|
-
## Route: /dashboard (user)
|
|
186
|
-
- **Auth**: required (storageState: playwright/.auth/user.json)
|
|
187
|
-
- **URL**: ${BASE_URL}/dashboard
|
|
188
|
-
- **Ready signal**: [data-testid="dashboard-heading"] visible
|
|
189
|
-
- **Elements**:
|
|
190
|
-
- heading: `[data-testid="page-title"]`
|
|
191
|
-
- logout btn: `[data-testid="logout-btn"]`
|
|
192
|
-
- profile form: `form >> input[name="displayName"]`
|
|
193
|
-
- settings link: `nav >> text=Settings`
|
|
194
|
-
- **Screenshot**: `__screenshots__/dashboard-user.png`
|
|
195
|
-
|
|
196
|
-
## Route: /admin (admin)
|
|
197
|
-
- **Auth**: required (storageState: playwright/.auth/admin.json)
|
|
198
|
-
- **URL**: ${BASE_URL}/admin
|
|
199
|
-
- **Ready signal**: [data-testid="admin-panel"] visible
|
|
200
|
-
- **Elements**:
|
|
201
|
-
- admin panel: `[data-testid="admin-panel"]`
|
|
202
|
-
- user table: `table#user-table tbody tr`
|
|
203
|
-
- add user btn: `[data-testid="add-user-btn"]`
|
|
204
|
-
- delete btn: `button:has-text("Delete")`
|
|
205
|
-
- **Screenshot**: `__screenshots__/admin-panel.png`
|
|
206
|
-
|
|
207
|
-
## Exploration Notes
|
|
208
|
-
- Route /admin → user gets redirected to /login (no admin role)
|
|
209
|
-
- /dashboard loads user-specific data (test assertions should use toContainText, not toHaveText)
|
|
210
|
-
```
|
|
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).
|
|
211
154
|
|
|
212
155
|
#### 4.5. Edge cases
|
|
213
156
|
|
|
@@ -256,18 +199,9 @@ Create test cases:
|
|
|
256
199
|
- List each functional requirement as a test case
|
|
257
200
|
- Mark with `@role(user|admin|guest|none)` and `@auth(required|none)`
|
|
258
201
|
- Include happy path AND error paths
|
|
259
|
-
- Reference the **real route URL** from app-exploration.md
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
```markdown
|
|
263
|
-
### User can view dashboard
|
|
264
|
-
- **Route**: /dashboard (from app-exploration.md)
|
|
265
|
-
- **Auth**: required (user storageState)
|
|
266
|
-
- **Test steps**:
|
|
267
|
-
1. Go to `/dashboard`
|
|
268
|
-
2. Assert page heading: `[data-testid="page-title"]` contains "Dashboard"
|
|
269
|
-
3. Assert logout button visible: `[data-testid="logout-btn"]`
|
|
270
|
-
```
|
|
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`
|
|
271
205
|
|
|
272
206
|
**Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate.
|
|
273
207
|
|
|
@@ -283,7 +217,7 @@ Create `tests/playwright/<name>.spec.ts`:
|
|
|
283
217
|
|
|
284
218
|
#### Verify selectors before writing
|
|
285
219
|
|
|
286
|
-
**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.**
|
|
287
221
|
|
|
288
222
|
```
|
|
289
223
|
For each test case in test-plan.md:
|
|
@@ -297,7 +231,7 @@ For each test case in test-plan.md:
|
|
|
297
231
|
a. Check if selector exists in app-exploration.md
|
|
298
232
|
b. If yes → verify it's still valid via browser_snapshot
|
|
299
233
|
c. If no → find equivalent from current snapshot
|
|
300
|
-
d. Selector priority:
|
|
234
|
+
d. Selector priority: see 4.3 table
|
|
301
235
|
7. Write test code with verified selectors
|
|
302
236
|
8. If selector cannot be verified → note it for Healer (Step 9)
|
|
303
237
|
```
|
|
@@ -306,7 +240,7 @@ This ensures every selector in the generated test code has been validated agains
|
|
|
306
240
|
|
|
307
241
|
**Generate** Playwright code for each verified test case:
|
|
308
242
|
- Follow `seed.spec.ts` structure
|
|
309
|
-
- Prefer `data-testid
|
|
243
|
+
- Prefer `data-testid` selectors (see 4.3 table for priority)
|
|
310
244
|
- Include happy path AND error/edge cases
|
|
311
245
|
- Use `test.describe(...)` for grouping
|
|
312
246
|
- Each test: `test('描述性名称', async ({ page }) => { ... })`
|
|
@@ -340,34 +274,29 @@ test('redirects unauthenticated user to login', async ({ browser }) => {
|
|
|
340
274
|
|
|
341
275
|
These are the most common mistakes that cause test failures. **Always follow these rules:**
|
|
342
276
|
|
|
343
|
-
**API calls — use `request`
|
|
277
|
+
**API calls — use `page.request` directly:**
|
|
344
278
|
```typescript
|
|
345
|
-
// ❌ WRONG — page.evaluate timeout,
|
|
279
|
+
// ❌ WRONG — page.evaluate with fetch has timeout, CORS, and context issues
|
|
346
280
|
const result = await page.evaluate(async () => {
|
|
347
281
|
const res = await fetch('/api/data');
|
|
348
282
|
return res.json();
|
|
349
283
|
});
|
|
350
284
|
|
|
351
|
-
// ✅ CORRECT —
|
|
285
|
+
// ✅ CORRECT — page.request is already an APIRequestContext, use directly
|
|
352
286
|
const res = await page.request.get(`${BASE_URL}/api/data`);
|
|
353
287
|
expect(res.status()).toBe(200);
|
|
354
288
|
const data = await res.json();
|
|
355
289
|
```
|
|
356
290
|
|
|
357
|
-
**Browser context
|
|
291
|
+
**Browser context — use `close()` for BrowserContext, no cleanup for APIRequestContext:**
|
|
358
292
|
```typescript
|
|
359
|
-
//
|
|
360
|
-
const ctx = await page.request.newContext();
|
|
361
|
-
await ctx.dispose(); // actually correct, but this is CommonContext
|
|
293
|
+
// ✅ BrowserContext — close it when done
|
|
362
294
|
const context = await browser.newContext();
|
|
363
|
-
await context.close(); // ←
|
|
295
|
+
await context.close(); // ← correct
|
|
364
296
|
|
|
365
|
-
// ✅
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// For APIRequestContext (from page.request):
|
|
370
|
-
// No cleanup needed — it's managed by Playwright automatically
|
|
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
|
|
371
300
|
```
|
|
372
301
|
|
|
373
302
|
**File uploads — use `setInputFiles()`, NOT `page.evaluate()` + fetch:**
|
|
@@ -465,18 +394,15 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
|
465
394
|
| **Selector changed** | Element not found | `browser_snapshot` → fix selector → re-run |
|
|
466
395
|
| **Assertion mismatch** | Wrong content/value | `browser_snapshot` → compare → fix assertion → re-run |
|
|
467
396
|
| **Timing issue** | `waitFor`/`page.evaluate` timeout | Switch to `request` API or add `waitFor` → re-run |
|
|
468
|
-
| **
|
|
469
|
-
| **Auth expired** | 401 Unauthorized | Token may have expired — if long suite, recommend splitting or re-auth |
|
|
470
|
-
| **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 |
|
|
471
398
|
|
|
472
|
-
3. **
|
|
399
|
+
3. **Heal** (≤3 attempts): snapshot → fix → re-run
|
|
473
400
|
4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
|
|
474
401
|
|
|
475
402
|
### 10. False Pass Detection
|
|
476
403
|
|
|
477
|
-
Run after test suite completes (even if all pass):
|
|
478
|
-
|
|
479
|
-
- **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
|
|
480
406
|
- **Too fast**: < 200ms for a complex flow is suspicious
|
|
481
407
|
- **No fresh auth context**: Protected routes without `browser.newContext()`
|
|
482
408
|
|
|
@@ -489,45 +415,13 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
|
489
415
|
- Auto-heal notes
|
|
490
416
|
- Recommendations with `file:line` references
|
|
491
417
|
|
|
418
|
+
Report template: `openspec/schemas/playwright-e2e/templates/report.md`
|
|
419
|
+
|
|
492
420
|
**Update tasks.md** if all tests pass: find E2E-related items, append `✅ Verified via Playwright E2E (<timestamp>)`.
|
|
493
421
|
|
|
494
422
|
## Report Structure
|
|
495
423
|
|
|
496
|
-
|
|
497
|
-
# Playwright E2E Report — <name>
|
|
498
|
-
|
|
499
|
-
## Summary
|
|
500
|
-
| Tests | Passed | Failed | Duration | Status |
|
|
501
|
-
|-------|--------|--------|----------|--------|
|
|
502
|
-
| N | N | N | Xm Xs | ✅/❌ |
|
|
503
|
-
|
|
504
|
-
## Results
|
|
505
|
-
### Passed
|
|
506
|
-
| Test | Duration | Notes |
|
|
507
|
-
|------|----------|-------|
|
|
508
|
-
| ... | ... | ... |
|
|
509
|
-
|
|
510
|
-
### Failed
|
|
511
|
-
| Test | Error | Recommendation |
|
|
512
|
-
|------|-------|----------------|
|
|
513
|
-
| ... | ... | file:line — fix |
|
|
514
|
-
|
|
515
|
-
## Auto-Heal Log
|
|
516
|
-
- Attempt N: selector fix → result
|
|
517
|
-
|
|
518
|
-
## Coverage
|
|
519
|
-
- [x] Requirement 1
|
|
520
|
-
- [ ] Requirement 2 (unverified)
|
|
521
|
-
|
|
522
|
-
## ⚠️ Coverage Gaps
|
|
523
|
-
> Tests passed but coverage gaps detected.
|
|
524
|
-
|
|
525
|
-
| Test | Gap | Recommendation |
|
|
526
|
-
|------|-----|----------------|
|
|
527
|
-
| ... | Conditional visibility check | file:line — use `expect().toBeVisible()` |
|
|
528
|
-
| ... | Auth guard uses inherited session | Add fresh context test |
|
|
529
|
-
| ... | Suspiciously fast execution (<200ms) | Verify test logic executed |
|
|
530
|
-
```
|
|
424
|
+
Reference: `openspec/schemas/playwright-e2e/templates/report.md`
|
|
531
425
|
|
|
532
426
|
## Graceful Degradation
|
|
533
427
|
|
|
@@ -551,7 +445,7 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
|
551
445
|
- Read specs from `openspec/changes/<name>/specs/` as source of truth
|
|
552
446
|
- Do NOT generate tests that contradict the specs
|
|
553
447
|
- **DO generate real, runnable Playwright test code** — not placeholders or TODOs
|
|
554
|
-
- 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`
|
|
555
449
|
- **Always explore before generating** — Step 4 is mandatory for accurate selectors
|
|
556
450
|
- Cap auto-heal at 3 attempts
|
|
557
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
|