openspec-playwright 0.1.57 → 0.1.59

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.
@@ -10,58 +10,86 @@ metadata:
10
10
 
11
11
  ## Input
12
12
 
13
- - **Change name**: `/opsx:e2e <name>` or auto-detected from context
14
- - **Specs**: `openspec/changes/<name>/specs/*.md`
13
+ - **Change name**: `/opsx:e2e <name>` or `/opsx:e2e all` (full app exploration, no OpenSpec needed)
14
+ - **Specs**: `openspec/changes/<name>/specs/*.md` (if change mode)
15
15
  - **Credentials**: `E2E_USERNAME` + `E2E_PASSWORD` env vars
16
16
 
17
17
  ## Output
18
18
 
19
- - **Test file**: `tests/playwright/<name>.spec.ts`
19
+ - **Test file**: `tests/playwright/<name>.spec.ts` (e.g. `app-all.spec.ts` for "all")
20
20
  - **Auth setup**: `tests/playwright/auth.setup.ts` (if auth required)
21
21
  - **Report**: `openspec/reports/playwright-e2e-<name>-<timestamp>.md`
22
- - **Test plan**: `openspec/changes/<name>/specs/playwright/test-plan.md`
22
+ - **Test plan**: `openspec/changes/<name>/specs/playwright/test-plan.md` (change mode only)
23
23
 
24
24
  ## Architecture
25
25
 
26
- Pipeline: **Planner** (Step 5) → **Generator** (Step 6) → **Healer** (Step 9).
26
+ Two modes, same pipeline:
27
27
 
28
- Uses CLI + SKILLs (not `init-agents`). CLI is ~4x more token-efficient than loading MCP tool schemas. MCP is used for **Explore** (Step 4, real DOM data) and **Healer** (UI inspection on failure).
28
+ | Mode | Command | Route source | Output |
29
+ |------|---------|--------------|--------|
30
+ | Change | `/opsx:e2e <name>` | OpenSpec specs | `<name>.spec.ts` |
31
+ | All | `/opsx:e2e all` | sitemap + homepage crawl | `app-all.spec.ts` |
29
32
 
30
- **Schema owns templates. CLI handles execution. Skill handles cognitive work. Exploration drives generation.**
33
+ Both modes update `app-knowledge.md` and `app-exploration.md`. All `.spec.ts` files run together as regression suite.
31
34
 
32
- ## Steps
35
+ ## Testing principles
33
36
 
34
- ### 1. Select the change
37
+ **UI first** — Test every user flow through the browser UI. E2E validates that users can accomplish tasks in the real interface, not just that the backend responds correctly.
35
38
 
36
- If a name is provided (e.g., `/opsx:e2e add-auth`), use it. Otherwise:
37
- - Infer from conversation context if the user mentioned a change
38
- - Auto-select if only one active change exists
39
- - If ambiguous, run `openspec list --json` and use the **AskUserQuestion tool** to let the user select
39
+ ```
40
+ 用户操作 浏览器 UI 后端 数据库 UI 反馈
41
+ ```
40
42
 
41
- After selecting, announce: "Using change: `<name>`" and how to override.
43
+ **API only as fallback** Use `page.request` only when UI genuinely cannot cover the scenario:
44
+ - Triggering HTTP 5xx/4xx error responses (hard to reach via UI)
45
+ - Edge cases requiring pre-condition data that UI cannot set up
46
+ - Cases where Step 4 exploration confirmed no UI element exists
42
47
 
43
- Verify specs exist:
44
- ```bash
45
- openspec status --change "<name>" --json
48
+ **Decision rule**:
49
+ ```
50
+ Can this be tested through the UI?
51
+ → Yes → page.getByRole/ByLabel/ByText + click/fill/type + assert UI
52
+ → No → record reason → use page.request
46
53
  ```
47
- If `openspec/changes/<name>/specs/` is empty, inform the user and stop. E2E requires specs.
48
54
 
49
- ### 2. Read specs and detect auth
55
+ **Never use API calls to replace routine UI flows.** If a test completes in < 200ms, it is almost certainly using `page.request` instead of real UI interactions.
56
+
57
+ ## Steps
58
+
59
+ ### 1. Select the change or mode
60
+
61
+ **Change mode** (`/opsx:e2e <name>`):
62
+ - Use provided name, or infer from context, or auto-select if only one exists
63
+ - If ambiguous → `openspec list --json` + AskUserQuestion
64
+ - Verify specs exist: `openspec status --change "<name>" --json`
65
+ - If specs empty → **STOP: E2E requires specs.** Use "all" mode instead.
66
+
67
+ **"all" mode** (`/opsx:e2e all` — no OpenSpec needed):
68
+ - Announce: "Mode: full app exploration"
69
+ - Discover routes via:
70
+ 1. Navigate to `${BASE_URL}/sitemap.xml` (if exists)
71
+ 2. Navigate to `${BASE_URL}/` → extract all links from snapshot
72
+ 3. Fallback common paths: `/`, `/login`, `/dashboard`, `/admin`, `/profile`, `/api/`
73
+ - Group routes: Guest vs Protected (by attempting direct access)
74
+
75
+ ### 2. Detect auth
76
+
77
+ **Change mode**: Read specs and extract functional requirements. Detect auth from keywords.
50
78
 
51
- Read all files from `openspec/changes/<name>/specs/*.md`. Extract functional requirements.
79
+ **"all" mode**: Detect auth by attempting to access known protected paths (e.g. `/dashboard`, `/profile`). If redirected to `/login` → auth required.
52
80
 
53
- Detect if auth is required only when BOTH conditions are met:
81
+ **Auth detection both modes** (BOTH conditions required):
54
82
 
55
83
  **Condition A — Explicit markers**: "login", "signin", "logout", "authenticate", "protected", "authenticated", "session", "unauthorized", "jwt", "token", "refresh", "middleware"
56
84
 
57
85
  **Condition B — Context indicators**: Protected routes ("/dashboard", "/profile", "/admin"), role mentions ("admin", "user"), redirect flows
58
86
 
59
- **Exclude false positives** HTTP header examples (`Authorization: Bearer ...`) and code snippets do not count.
87
+ **Exclude false positives**: HTTP header examples (`Authorization: Bearer ...`) and code snippets do not count.
60
88
 
61
- **Confidence levels**:
62
- - High (auto-proceed): Multiple explicit markers AND context indicators
63
- - Medium (proceed with note): Single explicit marker, context unclear
64
- - Low (skip auth): No explicit markers found
89
+ **Confidence**:
90
+ - High (auto-proceed): Multiple markers AND context indicators
91
+ - Medium (proceed with note): Single marker, context unclear
92
+ - Low (skip auth): No markers found
65
93
 
66
94
  ### 3. Validate environment
67
95
 
@@ -80,11 +108,11 @@ Explore to collect real DOM data before writing test plan. This eliminates blind
80
108
 
81
109
  **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
110
 
83
- #### 4.1. Verify BASE_URL + Read app-knowledge.md + Extract routes
111
+ #### 4.1. Verify BASE_URL + Read app-knowledge.md
84
112
 
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
113
+ 1. **Verify BASE_URL**: `browser_navigate(BASE_URL)` → if HTTP 5xx → **STOP: backend error. Fix app first.**
114
+ 2. **Read app-knowledge.md**: known risks, project conventions
115
+ 3. **Routes** (from Step 1): use already-discovered routes no need to re-extract
88
116
 
89
117
  #### 4.2. Explore each route via Playwright MCP
90
118
 
@@ -188,139 +216,94 @@ Read `tests/playwright/app-knowledge.md` as context for cross-change patterns.
188
216
 
189
217
  ### 5. Generate test plan
190
218
 
191
- Create `openspec/changes/<name>/specs/playwright/test-plan.md`:
219
+ > **"all" mode: skip this step — go directly to Step 6.**
192
220
 
193
- **Read inputs first**:
194
- - `openspec/changes/<name>/specs/*.md` — functional requirements
195
- - `openspec/changes/<name>/specs/playwright/app-exploration.md` — **real routes and verified selectors**
196
- - `tests/playwright/app-knowledge.md` — project-level shared knowledge
221
+ **Change mode**: Create `openspec/changes/<name>/specs/playwright/test-plan.md`.
197
222
 
198
- Create test cases:
199
- - List each functional requirement as a test case
200
- - Mark with `@role(user|admin|guest|none)` and `@auth(required|none)`
201
- - Include happy path AND error paths
202
- - Reference the **real route URL** and **verified selectors** from app-exploration.md
223
+ **Read inputs**: specs, app-exploration.md, app-knowledge.md
203
224
 
204
- Example: see `openspec/schemas/playwright-e2e/templates/test-plan.md`
225
+ **Create test cases**: functional requirement → test case, with `@role` and `@auth` tags. Reference verified selectors from app-exploration.md.
205
226
 
206
- **Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate.
227
+ Template: `openspec/schemas/playwright-e2e/templates/test-plan.md`
207
228
 
208
- ### 6. Generate test file
209
-
210
- Create `tests/playwright/<name>.spec.ts`:
211
-
212
- **Read inputs first**:
213
- - `openspec/changes/<name>/specs/playwright/test-plan.md` — test cases
214
- - `openspec/changes/<name>/specs/playwright/app-exploration.md` — **verified routes and selectors**
215
- - `tests/playwright/app-knowledge.md` — project-level patterns and conventions
216
- - `tests/playwright/seed.spec.ts` — code pattern and page object structure
229
+ **Idempotency**: If test-plan.md exists read and use, do NOT regenerate.
217
230
 
218
- #### Verify selectors before writing
231
+ ### 6. Generate test file
219
232
 
220
- **For each test case, verify selectors in a real browser BEFORE writing test code.**
233
+ **"all" mode** `tests/playwright/app-all.spec.ts` (smoke regression):
234
+ - For each discovered route: navigate → assert HTTP 200 → assert ready signal visible
235
+ - No detailed assertions — just "this page loads without crashing"
236
+ - This is a regression baseline — catches when existing pages break
221
237
 
222
- ```
223
- For each test case in test-plan.md:
224
-
225
- 1. Determine target route (from app-exploration.md)
226
- 2. Determine auth state (load storageState if @auth(required))
227
- 3. Navigate: browser_navigate to the route
228
- 4. Wait for page ready: browser_wait_for with a key element
229
- 5. Verify: browser_snapshot to confirm page loaded
230
- 6. For each selector in the test case:
231
- a. Check if selector exists in app-exploration.md
232
- b. If yes → verify it's still valid via browser_snapshot
233
- c. If no → find equivalent from current snapshot
234
- d. Selector priority: see 4.3 table
235
- 7. Write test code with verified selectors
236
- 8. If selector cannot be verified → note it for Healer (Step 9)
237
- ```
238
+ **Change mode** → `tests/playwright/<name>.spec.ts` (functional):
239
+ - Read: test-plan.md, app-exploration.md, app-knowledge.md, seed.spec.ts
240
+ - For each test case: verify selectors in real browser, then write Playwright code
238
241
 
239
- This ensures every selector in the generated test code has been validated against the live DOM.
242
+ **Selector verification (change mode)**:
243
+ 1. Navigate to route with correct auth state
244
+ 2. browser_snapshot to confirm page loaded
245
+ 3. For each selector: verify from current snapshot (see 4.3 table for priority)
246
+ 4. Write test code with verified selectors
247
+ 5. If selector unverifiable → note for Healer (Step 9)
240
248
 
241
- **Generate** Playwright code for each verified test case:
249
+ **Output format**:
242
250
  - Follow `seed.spec.ts` structure
243
- - Prefer `data-testid` selectors (see 4.3 table for priority)
244
- - Include happy path AND error/edge cases
245
251
  - Use `test.describe(...)` for grouping
246
252
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
253
+ - Prefer `data-testid` selectors (see 4.3 table)
247
254
 
248
- #### Anti-Pattern Warnings
255
+ **Code examples — UI first:**
249
256
 
250
- **🚫 False Pass** — never silently skip when element is missing:
251
257
  ```typescript
252
- // WRONG
258
+ // ✅ UI 测试 — 用户在界面上的真实操作
259
+ await page.goto(`${BASE_URL}/orders`);
260
+ await page.getByRole('button', { name: '新建订单' }).click();
261
+ await page.getByLabel('订单名称').fill('Test Order');
262
+ await page.getByRole('button', { name: '提交' }).click();
263
+ await expect(page.getByText('订单创建成功')).toBeVisible();
264
+
265
+ // ✅ Error path — 通过 UI 触发错误
266
+ await page.goto(`${BASE_URL}/orders`);
267
+ await page.getByRole('button', { name: '新建订单' }).click();
268
+ await page.getByRole('button', { name: '提交' }).click();
269
+ await expect(page.getByRole('alert')).toContainText('名称不能为空');
270
+
271
+ // ✅ API fallback — 仅在 UI 无法触发时使用
272
+ const res = await page.request.get(`${BASE_URL}/api/orders/99999`);
273
+ expect(res.status()).toBe(404);
274
+ ```
275
+
276
+ ```typescript
277
+ // 🚫 False Pass — 元素不存在时静默跳过
253
278
  if (await btn.isVisible().catch(() => false)) { ... }
279
+
254
280
  // ✅ CORRECT
255
281
  await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
256
- ```
257
282
 
258
- **🚫 Permission filtering** use `@tag` with `--grep`, not projects:
259
- ```typescript
260
- test('admin only', { tag: '@admin' }, async ({ page }) => { ... });
261
- // Run with: npx playwright test --grep "@admin"
283
+ // 🚫 API 替代 UI 失去了端到端的意义
284
+ const res = await page.request.post(`${BASE_URL}/api/login`, { data: credentials });
285
+
286
+ // CORRECT 通过 UI 登录
287
+ await page.goto(`${BASE_URL}/login`);
288
+ await page.getByLabel('邮箱').fill(process.env.E2E_USERNAME);
289
+ await page.getByLabel('密码').fill(process.env.E2E_PASSWORD);
290
+ await page.getByRole('button', { name: '登录' }).click();
291
+ await expect(page).toHaveURL(/dashboard/);
262
292
  ```
263
293
 
264
- **🚫 Auth guard** — test with FRESH browser context (no inherited cookies):
265
294
  ```typescript
266
- test('redirects unauthenticated user to login', async ({ browser }) => {
295
+ // Fresh browser context for auth guard
296
+ test('unauthenticated user redirected to login', async ({ browser }) => {
267
297
  const freshPage = await browser.newContext().newPage();
268
298
  await freshPage.goto(`${BASE_URL}/dashboard`);
269
299
  await expect(freshPage).toHaveURL(/login|auth/);
270
300
  });
271
- ```
272
-
273
- #### Playwright API Guardrails
274
-
275
- These are the most common mistakes that cause test failures. **Always follow these rules:**
276
-
277
- **API calls — use `page.request` directly:**
278
- ```typescript
279
- // ❌ WRONG — page.evaluate with fetch has timeout, CORS, and context issues
280
- const result = await page.evaluate(async () => {
281
- const res = await fetch('/api/data');
282
- return res.json();
283
- });
284
-
285
- // ✅ CORRECT — page.request is already an APIRequestContext, use directly
286
- const res = await page.request.get(`${BASE_URL}/api/data`);
287
- expect(res.status()).toBe(200);
288
- const data = await res.json();
289
- ```
290
-
291
- **Browser context — use `close()` for BrowserContext, no cleanup for APIRequestContext:**
292
- ```typescript
293
- // ✅ BrowserContext — close it when done
294
- const context = await browser.newContext();
295
- await context.close(); // ← correct
296
-
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
300
- ```
301
-
302
- **File uploads — use `setInputFiles()`, NOT `page.evaluate()` + fetch:**
303
- ```typescript
304
- // ❌ WRONG
305
- await page.evaluate(() => {
306
- const input = document.querySelector('input[type=file]');
307
- // ...
308
- });
309
301
 
310
- // ✅ CORRECT
302
+ // ✅ File uploads — UI 操作
311
303
  await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');
312
304
  ```
313
305
 
314
- **Form submissions prefer Playwright locators, not JS clicks:**
315
- ```typescript
316
- // ❌ WRONG — bypasses Playwright's actionability checks
317
- await page.evaluate(() => document.querySelector('button[type=submit]').click());
318
-
319
- // ✅ CORRECT — respects visibility, enabled, stable
320
- await page.getByRole('button', { name: 'Submit' }).click();
321
- ```
322
-
323
- **Always include error path tests** — API 500, 404, network timeout, invalid input.
306
+ Always include error path tests: UI validation messages, network failure, invalid input. Use `page.request` only for scenarios confirmed unreachable via UI.
324
307
 
325
308
  If the file exists → diff against test-plan, add only missing test cases.
326
309
 
@@ -429,13 +412,13 @@ Reference: `openspec/schemas/playwright-e2e/templates/report.md`
429
412
 
430
413
  | Scenario | Behavior |
431
414
  |----------|----------|
432
- | No specs | Stop — E2E requires specs |
433
- | Seed test fails | Stopfix environment |
434
- | App has JS errors or HTTP 5xx during exploration | **STOP** — see app-knowledge.md → Architecture section for restart instructions |
435
- | No auth required | Skip auth setup |
415
+ | No specs (change mode) | Stop — E2E requires specs. Use "all" mode instead. |
416
+ | Sitemap discovery fails ("all" mode) | Continueuse homepage links + common paths fallback |
417
+ | App has JS errors or HTTP 5xx during exploration | **STOP** — see app-knowledge.md → Architecture for restart instructions |
418
+ | app-all.spec.ts exists | Read and use (never regenerate — regression baseline) |
436
419
  | app-exploration.md exists | Read and use (never regenerate) |
437
420
  | app-knowledge.md exists | Read and use (append new patterns only) |
438
- | test-plan.md exists | Read and use (never regenerate) |
421
+ | test-plan.md exists (change mode) | Read and use (never regenerate) |
439
422
  | auth.setup.ts exists | Verify format (update only if stale) |
440
423
  | playwright.config.ts exists | Preserve all fields (add only missing) |
441
424
  | Test fails (backend) | `test.skip()` + report |
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
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(editors): use pathResolve instead of join in installForAllEditors
3
+ - chore: bump version to 0.1.59
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.57
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.59
Binary file