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 —
|
|
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 —
|
|
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
|
-
// 🚫
|
|
242
|
-
|
|
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
|
-
// ✅
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
// ✅
|
|
255
|
-
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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:
|
|
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
package/release-notes.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## What's Changed
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- chore: bump version to 0.1.59
|
|
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.59
|
|
Binary file
|