openspec-playwright 0.1.51 → 0.1.53

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
 
@@ -76,44 +76,33 @@ This validates: app server reachable, auth fixtures initialized, Playwright work
76
76
 
77
77
  ### 4. Explore application
78
78
 
79
- **Before writing test plan, explore the app to collect real DOM data.** This is the most critical step — it eliminates blind selector guessing.
79
+ Explore to collect real DOM data before writing test plan. This eliminates blind selector guessing.
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 from specs
83
+ #### 4.1. Verify BASE_URL + Read app-knowledge.md + Extract routes
84
84
 
85
- **First, verify BASE_URL is reachable**:
86
- ```javascript
87
- await browser_navigate(`${BASE_URL}/`)
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**: `browser_navigate(BASE_URL)` check HTTP status. If unreachable or HTTP 5xx → **STOP: app has a bug. Tell user to fix the server first.**
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
 
111
- For each route, use these tools in order:
91
+ For each route:
112
92
 
113
93
  ```
114
- browser_navigate → browser_snapshot → browser_take_screenshot
94
+ browser_navigate → browser_console_messages → browser_snapshot → browser_take_screenshot
115
95
  ```
116
96
 
97
+ **After navigating, check for app-level errors**:
98
+
99
+ | Signal | Meaning | Action |
100
+ |--------|---------|--------|
101
+ | HTTP 5xx or unreachable | Server error | **STOP** — tell user: "App has a bug (HTTP <code>). Fix it, then re-run /opsx:e2e." |
102
+ | JS error in console | App runtime error | **STOP** — tell user: "Page has JS errors. Fix them, then re-run /opsx:e2e." |
103
+ | HTTP 404 | Route not in app (metadata issue) | Continue — mark `⚠️ route not found` in app-exploration.md |
104
+ | Auth required, no credentials | Missing auth setup | Continue — skip protected routes, explore login page |
105
+
117
106
  **For guest routes** (no auth):
118
107
  ```javascript
119
108
  // Navigate directly
@@ -122,103 +111,56 @@ await browser_navigate(`${BASE_URL}/<route>`)
122
111
 
123
112
  **For protected routes** (auth required):
124
113
  ```javascript
125
- // Option A: use existing storageState (recommended if auth.setup.ts already ran)
114
+ // Option A: use existing storageState (recommended)
126
115
  // Option B: navigate to /login first, fill form, then navigate to target
127
116
  // Option C: use browser_run_code to set auth cookies directly
128
117
  ```
129
118
 
130
119
  **If credentials are not yet available**:
131
- 1. **Skip protected routes** — mark them as "⚠️ auth needed — explore after auth.setup.ts"
132
- 2. **Explore the login page itself** (it's a guest route) — extract form selectors for later auth.setup.ts
133
- 3. Document login page structure: input names, button text, form action, error patterns
134
-
135
- **Auth setup flow**:
136
- 1. Run exploration discover login page selectors (Step 4)
137
- 2. Customize auth.setup.ts with discovered selectors
138
- 3. Set E2E_USERNAME/E2E_PASSWORD
139
- 4. Run `npx playwright test --project=setup`
140
- 5. Re-run exploration for protected routes
141
-
142
- Wait for page stability after navigation:
143
- - Prefer waiting for a specific element: `browser_wait_for` with text or selector
144
- - Avoid `networkidle` / `load` — they are too slow or unreliable
145
- - Use a "page ready" signal: look for a heading, a loading spinner disappearing, or a URL change
120
+ 1. Skip protected routes — mark `⚠️ auth needed — explore after auth.setup.ts`
121
+ 2. Explore the login page itself (guest route) — extract form selectors
122
+ 3. After auth.setup.ts runs, re-run exploration for protected routes
123
+
124
+ Wait for page stability:
125
+ - Prefer `browser_wait_for` with text or selector
126
+ - Avoid `networkidle` / `load` — too slow or unreliable
127
+ - Ready signal: heading, spinner disappears, or URL change
146
128
 
147
129
  #### 4.3. Parse the snapshot
148
130
 
149
131
  From `browser_snapshot` output, extract **interactive elements** for each route:
150
132
 
151
- | Element type | What to capture | Priority |
133
+ | Element type | What to capture | Selector priority |
152
134
  |---|---|---|
153
- | **Buttons** | text, selector (`getByRole`, `getByLabel`, `data-testid`) | data-testid > role > label > text |
154
- | **Form fields** | name, type, label, selector | data-testid > name > label |
155
- | **Navigation links** | text, href, selector | text > href |
135
+ | **Buttons** | text, selector | `[data-testid]` > `getByRole` > `getByLabel` > `getByText` |
136
+ | **Form fields** | name, type, label, selector | `[data-testid]` > `name` > `label` |
137
+ | **Navigation links** | text, href, selector | `text` > `href` |
156
138
  | **Headings** | text content, selector | for assertions |
157
139
  | **Error messages** | text patterns, selector | for error path testing |
158
- | **Dynamic content** | structure (not content) — row counts, card layouts | for data-driven tests |
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
140
+ | **Dynamic content** | structure — row counts, card layouts | for data-driven tests |
166
141
 
167
142
  #### 4.4. Write app-exploration.md
168
143
 
169
144
  Output: `openspec/changes/<name>/specs/playwright/app-exploration.md`
170
145
 
171
- ```markdown
172
- # App Exploration — <name>
173
- Generated: <timestamp>
174
- BASE_URL: <from env or seed.spec.ts>
175
-
176
- ## Route: /
177
- - **Auth**: none
178
- - **URL**: ${BASE_URL}/
179
- - **Ready signal**: page has heading
180
- - **Elements**:
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
- ```
146
+ Use template: `openspec/schemas/playwright-e2e/templates/app-exploration.md`
211
147
 
212
- #### 4.5. Edge cases
148
+ Key fields per route:
149
+ - **URL**: `${BASE_URL}<path>`
150
+ - **Auth**: none / required (storageState: `<path>`)
151
+ - **Ready signal**: how to know the page is loaded
152
+ - **Elements**: interactive elements with verified selectors (see 4.3 table)
153
+ - **Screenshot**: `__screenshots__/<slug>.png`
213
154
 
214
- | Situation | What to do |
215
- |-----------|-----------|
216
- | Route 404 | Mark as "⚠️ route not found — verify URL in specs" |
217
- | Network error | Mark as "⚠️ unreachable — check if server is running" |
218
- | Auth required, no credentials | Skip routes + explore login page → document selectors → set up auth first | See 4.2 "If credentials are not yet available" |
155
+ After exploration, add route-level notes (redirects, dynamic content → see 4.5).
156
+
157
+ #### 4.5. Exploration behavior notes
158
+
159
+ | Situation | Action |
160
+ |-----------|--------|
219
161
  | SPA routing (URL changes but page doesn't reload) | Explore via navigation clicks from known routes, not direct URLs |
220
- | Page loads but no interactive elements | Try waiting longer for SPA hydration |
221
- | Dynamic content (user-specific) | Record structure, not content — use `toContainText`, not `toHaveText` |
162
+ | Page loads but no interactive elements | Wait longer for SPA hydration |
163
+ | Dynamic content (user-specific) | Record structure — use `toContainText`, not `toHaveText` |
222
164
 
223
165
  **Idempotency**: If `app-exploration.md` already exists → read it, verify routes still match specs, update only new routes or changed pages.
224
166
 
@@ -256,18 +198,9 @@ Create test cases:
256
198
  - List each functional requirement as a test case
257
199
  - Mark with `@role(user|admin|guest|none)` and `@auth(required|none)`
258
200
  - Include happy path AND error paths
259
- - Reference the **real route URL** from app-exploration.md for each test
260
- - Reference **verified selectors** from app-exploration.md instead of inferring
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
- ```
201
+ - Reference the **real route URL** and **verified selectors** from app-exploration.md
202
+
203
+ Example: see `openspec/schemas/playwright-e2e/templates/test-plan.md`
271
204
 
272
205
  **Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate.
273
206
 
@@ -283,7 +216,7 @@ Create `tests/playwright/<name>.spec.ts`:
283
216
 
284
217
  #### Verify selectors before writing
285
218
 
286
- **For each test case, verify selectors in a real browser BEFORE writing the test code.** This is the most important step — do not skip it.
219
+ **For each test case, verify selectors in a real browser BEFORE writing test code.**
287
220
 
288
221
  ```
289
222
  For each test case in test-plan.md:
@@ -297,7 +230,7 @@ For each test case in test-plan.md:
297
230
  a. Check if selector exists in app-exploration.md
298
231
  b. If yes → verify it's still valid via browser_snapshot
299
232
  c. If no → find equivalent from current snapshot
300
- d. Selector priority: data-testid > getByRole > getByLabel > getByText
233
+ d. Selector priority: see 4.3 table
301
234
  7. Write test code with verified selectors
302
235
  8. If selector cannot be verified → note it for Healer (Step 9)
303
236
  ```
@@ -306,7 +239,7 @@ This ensures every selector in the generated test code has been validated agains
306
239
 
307
240
  **Generate** Playwright code for each verified test case:
308
241
  - Follow `seed.spec.ts` structure
309
- - Prefer `data-testid`; fallback to `getByRole`, `getByLabel`, `getByText`
242
+ - Prefer `data-testid` selectors (see 4.3 table for priority)
310
243
  - Include happy path AND error/edge cases
311
244
  - Use `test.describe(...)` for grouping
312
245
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
@@ -340,34 +273,29 @@ test('redirects unauthenticated user to login', async ({ browser }) => {
340
273
 
341
274
  These are the most common mistakes that cause test failures. **Always follow these rules:**
342
275
 
343
- **API calls — use `request` API, NOT `page.evaluate()`:**
276
+ **API calls — use `page.request` directly:**
344
277
  ```typescript
345
- // ❌ WRONG — page.evaluate timeout, wrong context, CORS issues
278
+ // ❌ WRONG — page.evaluate with fetch has timeout, CORS, and context issues
346
279
  const result = await page.evaluate(async () => {
347
280
  const res = await fetch('/api/data');
348
281
  return res.json();
349
282
  });
350
283
 
351
- // ✅ CORRECT — direct HTTP, no browser context, fast
284
+ // ✅ CORRECT — page.request is already an APIRequestContext, use directly
352
285
  const res = await page.request.get(`${BASE_URL}/api/data`);
353
286
  expect(res.status()).toBe(200);
354
287
  const data = await res.json();
355
288
  ```
356
289
 
357
- **Browser context cleanup — `dispose()` NOT `close()`:**
290
+ **Browser context — use `close()` for BrowserContext, no cleanup for APIRequestContext:**
358
291
  ```typescript
359
- // WRONG — close() is not a function on APIRequestContext
360
- const ctx = await page.request.newContext();
361
- await ctx.dispose(); // actually correct, but this is CommonContext
362
- const context = await browser.newContext();
363
- await context.close(); // ← WRONG
364
-
365
- // ✅ CORRECT
292
+ // BrowserContext — close it when done
366
293
  const context = await browser.newContext();
367
- await context.close(); // close() is correct for BrowserContext
294
+ await context.close(); // correct
368
295
 
369
- // For APIRequestContext (from page.request):
370
- // No cleanup needed — it's managed by Playwright automatically
296
+ // APIRequestContext page.request is already one, no cleanup needed
297
+ const res = await page.request.get(`${BASE_URL}/api/data`);
298
+ // No dispose() or close() needed
371
299
  ```
372
300
 
373
301
  **File uploads — use `setInputFiles()`, NOT `page.evaluate()` + fetch:**
@@ -465,18 +393,15 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
465
393
  | **Selector changed** | Element not found | `browser_snapshot` → fix selector → re-run |
466
394
  | **Assertion mismatch** | Wrong content/value | `browser_snapshot` → compare → fix assertion → re-run |
467
395
  | **Timing issue** | `waitFor`/`page.evaluate` timeout | Switch to `request` API or add `waitFor` → re-run |
468
- | **Wrong API usage** | `ctx.close is not a function` | Fix: `browser.newContext()` `close()`; `request.newContext()` no cleanup needed |
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 |
396
+ | **page.evaluate with fetch** | `fetch` in browser context, CORS errors | Switch to `page.request` APIre-run |
471
397
 
472
- 3. **Attempt heal** (≤3 times): snapshot → fix → re-run
398
+ 3. **Heal** (≤3 attempts): snapshot → fix → re-run
473
399
  4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
474
400
 
475
401
  ### 10. False Pass Detection
476
402
 
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
403
+ Run after test suite completes (even if all pass). Common patterns (see Step 6 Anti-Pattern Warnings for fixes):
404
+ - **Conditional visibility**: `if (locator.isVisible().catch(() => false))` — if test passes, locator may not exist
480
405
  - **Too fast**: < 200ms for a complex flow is suspicious
481
406
  - **No fresh auth context**: Protected routes without `browser.newContext()`
482
407
 
@@ -489,45 +414,13 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
489
414
  - Auto-heal notes
490
415
  - Recommendations with `file:line` references
491
416
 
417
+ Report template: `openspec/schemas/playwright-e2e/templates/report.md`
418
+
492
419
  **Update tasks.md** if all tests pass: find E2E-related items, append `✅ Verified via Playwright E2E (<timestamp>)`.
493
420
 
494
421
  ## Report Structure
495
422
 
496
- ```markdown
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
- ```
423
+ Reference: `openspec/schemas/playwright-e2e/templates/report.md`
531
424
 
532
425
  ## Graceful Degradation
533
426
 
@@ -535,6 +428,7 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
535
428
  |----------|----------|
536
429
  | No specs | Stop — E2E requires specs |
537
430
  | Seed test fails | Stop — fix environment |
431
+ | App has JS errors or HTTP 5xx during exploration | **STOP** — tell user to fix the app first |
538
432
  | No auth required | Skip auth setup |
539
433
  | app-exploration.md exists | Read and use (never regenerate) |
540
434
  | app-knowledge.md exists | Read and use (append new patterns only) |
@@ -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`, `app-exploration.md`, `app-knowledge.md`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
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(SKILL): add BASE_URL smoke check before exploration (Step 4.1)
3
+ - fix(SKILL): stop exploration on app-level errors, continue on metadata issues
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.51
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.53
Binary file