openspec-playwright 0.1.34 → 0.1.35

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.
@@ -7,7 +7,7 @@ compatibility: Requires openspec CLI, Playwright (with browsers installed), and
7
7
  **Architecture**: Uses CLI + SKILLs (not `init-agents`). This follows Playwright's recommended approach for coding agents — CLI is more token-efficient than loading MCP tool schemas into context. MCP is used only for Healer (UI inspection on failure).
8
8
  metadata:
9
9
  author: openspec-playwright
10
- version: "2.6"
10
+ version: "2.7"
11
11
  ---
12
12
 
13
13
  ## Input
@@ -112,6 +112,54 @@ Use your file writing capability to create `tests/playwright/<name>.spec.ts`.
112
112
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
113
113
  - Add `@project(user)` / `@project(admin)` on role-specific tests
114
114
 
115
+ ### Anti-Pattern Warnings (Generator)
116
+
117
+ **🚫 NEVER do this — False Pass pattern:**
118
+ ```typescript
119
+ // WRONG: If button doesn't exist, test silently passes and tests nothing
120
+ const btn = page.getByRole('button', { name: '取消' }).first();
121
+ if (await btn.isVisible().catch(() => false)) {
122
+ await btn.click();
123
+ await expect(page.getByText('成功')).toBeVisible();
124
+ }
125
+ // ✅ CORRECT: Use assertion — test fails if element is missing
126
+ await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
127
+ await page.getByRole('button', { name: '取消' }).click();
128
+ await expect(page.getByText('成功')).toBeVisible();
129
+ ```
130
+
131
+ **Why it matters**: A test that passes but skipped its logic gives **false confidence**. It reports green but tests nothing. Worse — if the test modifies data, a skipped run can corrupt state for the next test.
132
+
133
+ **🚫 NEVER rely on Playwright projects for permission filtering:**
134
+ ```typescript
135
+ // WRONG: All tests run under both admin AND user projects — false "16 tests" impression
136
+ projects: [{ name: 'admin' }, { name: 'user' }]
137
+
138
+ // ✅ CORRECT: Use @tag for permission-based test filtering
139
+ test('admin only - activate subscription', { tag: '@admin' }, async ({ page }) => { ... });
140
+ test('user only - view subscription', { tag: '@user' }, async ({ page }) => { ... });
141
+ // Run with: npx playwright test --grep "@admin"
142
+ ```
143
+
144
+ **🚫 NEVER skip auth guard tests:**
145
+ The auth guard is a **critical security feature**. Skipping it leaves a gap in coverage.
146
+ ```typescript
147
+ // ✅ CORRECT: Test auth guard with a FRESH browser context (no cookies, no storage)
148
+ test('redirects unauthenticated user to login', async ({ browser }) => {
149
+ const freshContext = await browser.newContext(); // No session cookies
150
+ const freshPage = await freshContext.newPage();
151
+ await freshPage.goto(`${BASE_URL}/dashboard`);
152
+ await expect(freshPage).toHaveURL(/login|auth/);
153
+ await freshContext.close();
154
+ });
155
+ ```
156
+
157
+ **Always include error path tests** (not just happy paths):
158
+ - API returns 500 → UI error message displayed?
159
+ - API returns 404 → graceful "not found" handling?
160
+ - Network timeout → retry or error UX?
161
+ - Invalid input → validation message shown?
162
+
115
163
  **Example pattern** (from seed.spec.ts):
116
164
  ```typescript
117
165
  import { test, expect } from '@playwright/test';
@@ -220,6 +268,30 @@ If tests fail → analyze failures, use **Playwright MCP tools** to inspect UI s
220
268
  - **Test bug**: report with "likely selector change, verify manually at file:line"
221
269
  - Do NOT retry after evidence checklist — evidence is conclusive
222
270
 
271
+ 4b. **False Pass Detection (after test run — before reporting success)**
272
+
273
+ Even passing tests can give false confidence. Scan test output for silent skips:
274
+
275
+ **Indicator A — Conditional test logic:**
276
+ Look for patterns in the test file:
277
+ ```typescript
278
+ if (await locator.isVisible().catch(() => false)) { ... }
279
+ ```
280
+ → If test passes, the locator might not exist → check with `browser_snapshot`
281
+ → Report: "Test passed but may have skipped — conditional visibility check detected"
282
+
283
+ **Indicator B — Test ran too fast:**
284
+ A test covering a complex flow that completes in < 200ms is suspicious.
285
+ → Inspect with `browser_snapshot` to confirm page state
286
+ → Report: "Test duration suspiciously short — verify test logic was executed"
287
+
288
+ **Indicator C — Auth guard not tested:**
289
+ If specs mention "protected route" or "redirect to login" but no test uses a fresh browser context:
290
+ → Report: "Auth guard not verified — test uses authenticated context (cookies/storage inherited)"
291
+ → Recommendation: Add a test with `browser.newContext()` (no storageState) to verify the guard
292
+
293
+ If any false-pass indicator is found → add a **⚠️ Coverage Gap** section to the report.
294
+
223
295
  ### 9. Report results
224
296
 
225
297
  Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
@@ -265,7 +337,15 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
265
337
  ## Coverage
266
338
  - [x] Requirement 1
267
339
  - [ ] Requirement 2 (unverified)
268
- ```
340
+
341
+ ## ⚠️ Coverage Gaps
342
+ > Tests passed but coverage gaps were detected. Review carefully.
343
+
344
+ | Test | Gap | Recommendation |
345
+ |------|-----|----------------|
346
+ | ... | Conditional visibility check — test may have skipped | file:line — use `expect().toBeVisible()` |
347
+ | ... | Auth guard uses inherited session | Add fresh context test: `browser.newContext()` |
348
+ | ... | Suspiciously fast execution (<200ms) | Verify test logic was actually executed |
269
349
 
270
350
  ### Updated tasks.md
271
351
  ```
@@ -286,6 +366,7 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
286
366
  | Test fails (selector) | Healer: snapshot → fix selector → re-run (≤3 attempts) |
287
367
  | Test fails (assertion) | Healer: snapshot → fix assertion → re-run (≤3 attempts) |
288
368
  | 3 heal attempts failed | Confirm root cause → if app bug: `test.skip()` + report; if unclear: report with recommendation |
369
+ | False pass detected | Report coverage gap → add to "⚠️ Coverage Gap" section in report |
289
370
 
290
371
  ## Verification Heuristics
291
372
 
@@ -293,6 +374,8 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
293
374
  - **Selector robustness**: Prefer `data-testid`, fallback to semantic selectors
294
375
  - **False positives**: If test fails due to test bug (not app bug) → fix the test
295
376
  - **Actionability**: Every failed test needs a specific recommendation
377
+ - **No false passes**: Every passing test must actually execute its test logic — verify absence of `if (isVisible())` conditional patterns
378
+ - **Auth guard verified**: Protected routes must have a test using a fresh browser context (no inherited cookies)
296
379
 
297
380
  ## Guardrails
298
381
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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: update release script to include npm publish
3
+ - feat(skill): add anti-pattern guidance and false-pass detection (v2.7)
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.34
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.35
@@ -61,3 +61,70 @@ test.describe('Application smoke tests', () => {
61
61
  expect(criticalErrors).toHaveLength(0);
62
62
  });
63
63
  });
64
+
65
+ // ──────────────────────────────────────────────
66
+ // Example: Role-based tests with @tag
67
+ // Use tags (@admin, @user) for permission filtering instead of
68
+ // multiple Playwright projects — prevents false "N tests" impressions
69
+ // ──────────────────────────────────────────────
70
+
71
+ // test.describe('Subscription management', () => {
72
+ // test('activate subscription', { tag: '@admin' }, async ({ page }) => {
73
+ // // Admin-only test
74
+ // await page.goto(`${BASE_URL}/admin/subscriptions`);
75
+ // await expect(page.getByRole('button', { name: '激活订阅' })).toBeVisible();
76
+ // });
77
+ //
78
+ // test('view subscription', { tag: '@user' }, async ({ page }) => {
79
+ // // User-only test
80
+ // await page.goto(`${BASE_URL}/subscription`);
81
+ // await expect(page.getByText('当前订阅')).toBeVisible();
82
+ // });
83
+ // });
84
+
85
+ // Run with: npx playwright test --grep "@admin"
86
+ // Run with: npx playwright test --grep "@user"
87
+
88
+ // ──────────────────────────────────────────────
89
+ // Example: Auth guard test with FRESH browser context
90
+ // 🚫 NEVER test auth guard with the same authenticated context!
91
+ // Use browser.newContext() to create a context with NO cookies/storage
92
+ // ──────────────────────────────────────────────
93
+
94
+ // test.describe('Auth guard', () => {
95
+ // test('redirects unauthenticated user to login', async ({ browser }) => {
96
+ // const freshContext = await browser.newContext(); // No session cookies
97
+ // const freshPage = await freshContext.newPage();
98
+ // await freshPage.goto(`${BASE_URL}/dashboard`);
99
+ // await expect(freshPage).toHaveURL(/login|auth|signin/);
100
+ // await freshContext.close();
101
+ // });
102
+ // });
103
+
104
+ // ──────────────────────────────────────────────
105
+ // Example: Error path test
106
+ // Always include error scenarios, not just happy paths
107
+ // ──────────────────────────────────────────────
108
+
109
+ // test.describe('Error handling', () => {
110
+ // test('shows error message on invalid input', async ({ page }) => {
111
+ // await page.goto(`${BASE_URL}/submit`);
112
+ // await page.getByTestId('input').fill('');
113
+ // await page.getByTestId('submit').click();
114
+ // await expect(page.getByTestId('error')).toContainText('不能为空');
115
+ // });
116
+ // });
117
+
118
+ // ──────────────────────────────────────────────
119
+ // Anti-pattern warnings
120
+ // ──────────────────────────────────────────────
121
+
122
+ // 🚫 WRONG — False Pass: test silently passes if button doesn't exist
123
+ // if (await page.getByRole('button', { name: '取消' }).isVisible().catch(() => false)) {
124
+ // await page.getByRole('button', { name: '取消' }).click();
125
+ // }
126
+
127
+ // ✅ CORRECT — Use assertion: test fails if element is missing
128
+ // await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
129
+ // await page.getByRole('button', { name: '取消' }).click();
130
+ // await expect(page.getByText('操作成功')).toBeVisible();
Binary file