openspec-playwright 0.1.58 → 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.
@@ -32,6 +32,28 @@ Two modes, same pipeline:
32
32
 
33
33
  Both modes update `app-knowledge.md` and `app-exploration.md`. All `.spec.ts` files run together as regression suite.
34
34
 
35
+ ## Testing principles
36
+
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.
38
+
39
+ ```
40
+ 用户操作 → 浏览器 UI → 后端 → 数据库 → UI 反馈
41
+ ```
42
+
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
47
+
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
53
+ ```
54
+
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
+
35
57
  ## Steps
36
58
 
37
59
  ### 1. Select the change or mode
@@ -230,44 +252,58 @@ Template: `openspec/schemas/playwright-e2e/templates/test-plan.md`
230
252
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
231
253
  - Prefer `data-testid` selectors (see 4.3 table)
232
254
 
233
- **Code examples — same for both modes:**
255
+ **Code examples — UI first:**
256
+
257
+ ```typescript
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
+ ```
234
275
 
235
276
  ```typescript
236
- // 🚫 False Pass — never silently skip when element is missing
277
+ // 🚫 False Pass — 元素不存在时静默跳过
237
278
  if (await btn.isVisible().catch(() => false)) { ... }
279
+
238
280
  // ✅ CORRECT
239
281
  await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
240
282
 
241
- // 🚫 Auth guard test with FRESH browser context
242
- test('redirects unauthenticated user to login', async ({ browser }) => {
243
- const freshPage = await browser.newContext().newPage();
244
- await freshPage.goto(`${BASE_URL}/dashboard`);
245
- await expect(freshPage).toHaveURL(/login|auth/);
246
- });
283
+ // 🚫 API 替代 UI 失去了端到端的意义
284
+ const res = await page.request.post(`${BASE_URL}/api/login`, { data: credentials });
247
285
 
248
- // ✅ API calls use page.request directly
249
- const res = await page.request.get(`${BASE_URL}/api/data`);
250
- expect(res.status()).toBe(200);
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/);
251
292
  ```
252
293
 
253
294
  ```typescript
254
- // ✅ BrowserContext close when done
255
- const context = await browser.newContext();
256
- await context.close();
257
-
258
- // ✅ APIRequestContext — page.request is already one, no cleanup needed
259
- const res = await page.request.get(`${BASE_URL}/api/data`);
260
- ```
295
+ // ✅ Fresh browser context for auth guard
296
+ test('unauthenticated user redirected to login', async ({ browser }) => {
297
+ const freshPage = await browser.newContext().newPage();
298
+ await freshPage.goto(`${BASE_URL}/dashboard`);
299
+ await expect(freshPage).toHaveURL(/login|auth/);
300
+ });
261
301
 
262
- ```typescript
263
- // ✅ File uploads — use setInputFiles()
302
+ // ✅ File uploads — UI 操作
264
303
  await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');
265
-
266
- // ✅ Form submissions — prefer Playwright locators
267
- await page.getByRole('button', { name: 'Submit' }).click();
268
304
  ```
269
305
 
270
- 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.
271
307
 
272
308
  If the file exists → diff against test-plan, add only missing test cases.
273
309
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.58",
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
- - feat(SKILL): unify "all" and "change" modes — same pipeline, different scope
3
+ - chore: bump version to 0.1.59
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.58
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.59
Binary file