openspec-playwright 0.2.5 → 0.2.7
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.
- package/.claude/skills/openspec-e2e/SKILL.md +172 -270
- package/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +190 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +3 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: Requires openspec CLI, Playwright (with browsers installed), and @playwright/mcp (globally installed via `claude mcp add playwright npx @playwright/mcp@latest`).
|
|
6
6
|
metadata:
|
|
7
7
|
author: openspec-playwright
|
|
8
|
-
version: "2.
|
|
8
|
+
version: "2.19"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Input
|
|
@@ -20,6 +20,7 @@ metadata:
|
|
|
20
20
|
- **Page Objects** (all mode): `tests/playwright/pages/<Route>Page.ts`
|
|
21
21
|
- **Auth setup**: `tests/playwright/auth.setup.ts` (if auth required)
|
|
22
22
|
- **Report**: `openspec/reports/playwright-e2e-<name>-<timestamp>.md`
|
|
23
|
+
- **App Bug Registry**: `openspec/reports/app-bug-registry.md` (cumulative, per-project)
|
|
23
24
|
- **Test plan**: `openspec/changes/<name>/specs/playwright/test-plan.md` (change mode only)
|
|
24
25
|
|
|
25
26
|
## Architecture
|
|
@@ -59,6 +60,46 @@ Can the user SEE this on screen?
|
|
|
59
60
|
→ No → Record reason → page.request acceptable
|
|
60
61
|
```
|
|
61
62
|
|
|
63
|
+
**Business logic assertion rule — numerical/calculated values MUST use API assertion**:
|
|
64
|
+
|
|
65
|
+
UI assertions verify **rendering** correctness, not **calculation** correctness. A UI that correctly displays a wrong value will pass UI-only tests.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Is the assertion about a computed/counted/calculated value?
|
|
69
|
+
(e.g., balance, total, discount, count, percentage, score, ranking)
|
|
70
|
+
→ Yes → Use page.request to fetch backend data → assert the raw value
|
|
71
|
+
→ No → UI assertion is sufficient
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Examples where API assertion is required:**
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// ❌ UI-only assertion — hides calculation bugs
|
|
78
|
+
await page.getByText('¥800').click(); // buy item
|
|
79
|
+
await expect(page.getByText('总金额: ¥800')).toBeVisible(); // passes even if backend rounded wrong
|
|
80
|
+
|
|
81
|
+
// ✅ API assertion — catches calculation bugs
|
|
82
|
+
const order = await page.request.get(`${BASE_URL}/api/orders/${orderId}`);
|
|
83
|
+
const body = await order.json();
|
|
84
|
+
expect(body.total).toBe(800); // backend calculation is verified
|
|
85
|
+
|
|
86
|
+
// ❌ UI-only assertion — hides optimistic update failures
|
|
87
|
+
await page.getByRole('button', { name: '点赞' }).click();
|
|
88
|
+
await expect(page.getByText('1 个赞')).toBeVisible(); // passes even if POST failed silently
|
|
89
|
+
|
|
90
|
+
// ✅ API assertion — catches backend sync failures
|
|
91
|
+
const post = await page.request.get(`${BASE_URL}/api/posts/${postId}`);
|
|
92
|
+
const body = await post.json();
|
|
93
|
+
expect(body.likeCount).toBeGreaterThan(0); // backend state is verified
|
|
94
|
+
|
|
95
|
+
// ✅ Optimistic update with API verification
|
|
96
|
+
await page.getByRole('button', { name: '点赞' }).click();
|
|
97
|
+
await expect(page.getByText('1 个赞')).toBeVisible(); // optimistic UI
|
|
98
|
+
await page.waitForResponse(r => r.url().includes('/api/like')); // wait for backend
|
|
99
|
+
const post = await page.request.get(`${BASE_URL}/api/posts/${postId}`);
|
|
100
|
+
expect((await post.json()).likeCount).toBeGreaterThan(0); // verify persistence
|
|
101
|
+
```
|
|
102
|
+
|
|
62
103
|
**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.
|
|
63
104
|
|
|
64
105
|
## Steps
|
|
@@ -231,76 +272,17 @@ From `browser_snapshot` + `browser_evaluate`, identify these special elements pe
|
|
|
231
272
|
| File upload | `<input type="file">` | `accept` attribute, `multiple` flag | Medium |
|
|
232
273
|
| Drag-and-drop | drag events in JS | Simulate DnD via coordinate clicks | Low |
|
|
233
274
|
| Date picker | specific `data-testid` or class patterns | Click triggers → evaluate value | Low (skip unless specs mention) |
|
|
234
|
-
| Infinite scroll | Dynamic row insertion | Count elements before/after scroll | Low |
|
|
235
|
-
| WebSocket / SSE | No DOM signal | Check `browser_console_messages` for WS events | Low |
|
|
236
|
-
|
|
237
|
-
**For each detected special element, capture:**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let context = 'unknown';
|
|
246
|
-
if (c.getContext('webgl2')) context = 'webgl2';
|
|
247
|
-
else if (c.getContext('webgl')) context = 'webgl';
|
|
248
|
-
else if (c.getContext('2d')) context = '2d';
|
|
249
|
-
return {
|
|
250
|
-
id: c.id || '',
|
|
251
|
-
context,
|
|
252
|
-
width: c.width,
|
|
253
|
-
height: c.height,
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Iframe — record frameLocator
|
|
258
|
-
// Note: iframe has src or name attribute
|
|
259
|
-
|
|
260
|
-
// Rich text editor — get content
|
|
261
|
-
const editorContent = await browser_evaluate(() => {
|
|
262
|
-
const el = document.querySelector('[contenteditable]');
|
|
263
|
-
return el ? { tag: el.tagName, content: el.innerHTML, length: el.textContent.length } : null;
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Video / Audio — get state via evaluate (snapshot doesn't expose tagName)
|
|
267
|
-
const mediaState = await browser_evaluate(() => {
|
|
268
|
-
const v = document.querySelector('video');
|
|
269
|
-
if (v) return { type: 'video', paused: v.paused, duration: v.duration };
|
|
270
|
-
const a = document.querySelector('audio');
|
|
271
|
-
if (a) return { type: 'audio', paused: a.paused, duration: a.duration };
|
|
272
|
-
return null;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// contenteditable — detect via evaluate
|
|
276
|
-
const isContentEditable = await browser_evaluate(() => {
|
|
277
|
-
const el = document.querySelector('[contenteditable]');
|
|
278
|
-
return !!el;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// CAPTCHA — detect type
|
|
282
|
-
const captchaInfo = await browser_evaluate(() => {
|
|
283
|
-
const recaptcha = document.querySelector('.g-recaptcha, [data-sitekey]');
|
|
284
|
-
if (recaptcha) return { type: 'recaptcha', sitekey: recaptcha.getAttribute('data-sitekey') };
|
|
285
|
-
const hcaptcha = document.querySelector('.h-captcha');
|
|
286
|
-
if (hcaptcha) return { type: 'hcaptcha', sitekey: hcaptcha.getAttribute('data-sitekey') };
|
|
287
|
-
const turnstile = document.querySelector('[data-sitekey*="cloudflare"]');
|
|
288
|
-
if (turnstile) return { type: 'turnstile' };
|
|
289
|
-
const canvas = document.querySelector('canvas[class*="captcha"]');
|
|
290
|
-
if (canvas) return { type: 'canvas-captcha' };
|
|
291
|
-
const slider = document.querySelector('[class*="slider"], [class*="drag"]');
|
|
292
|
-
if (slider) return { type: 'slider-captcha' };
|
|
293
|
-
return null;
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// OTP input — detect
|
|
297
|
-
const otpInfo = await browser_evaluate(() => {
|
|
298
|
-
const inputs = document.querySelectorAll('input');
|
|
299
|
-
const otpInputs = Array.from(inputs).filter(i => i.maxLength === 1 && i.type === 'text' || i.type === 'tel');
|
|
300
|
-
if (otpInputs.length >= 4) return { type: 'otp-sms', digits: otpInputs.length };
|
|
301
|
-
return null;
|
|
302
|
-
});
|
|
303
|
-
```
|
|
275
|
+
| Infinite scroll | Dynamic row insertion | Count elements before/after scroll | Low (skip unless specs mention dynamic lists/pagination) |
|
|
276
|
+
| WebSocket / SSE | No DOM signal | Check `browser_console_messages` for WS events | Low (check only if app uses real-time features) |
|
|
277
|
+
|
|
278
|
+
**For each detected special element, capture via `browser_evaluate` with targeted DOM queries:**
|
|
279
|
+
- Canvas: `getContext('webgl2'/'webgl'/'2d')`, `width`, `height`
|
|
280
|
+
- Iframe: `src` attribute → use `frameLocator` in tests
|
|
281
|
+
- CAPTCHA: `.g-recaptcha`, `.h-captcha`, `[data-sitekey]`, canvas+slider detection
|
|
282
|
+
- OTP: `input` elements with `maxLength === 1` or `type === 'tel'`
|
|
283
|
+
- Rich text: `[contenteditable]` → `innerHTML`, `textContent.length`
|
|
284
|
+
- Video/Audio: `querySelector('video'/'audio')` → `paused`, `duration`
|
|
285
|
+
- Shadow DOM: `role="generic"` with no children → check `shadowRoot`
|
|
304
286
|
|
|
305
287
|
Record findings in `app-exploration.md` → **Special Elements Detected** table.
|
|
306
288
|
|
|
@@ -377,7 +359,7 @@ Reply **yes** to proceed, or tell me to exclude routes or adjust strategies.
|
|
|
377
359
|
|
|
378
360
|
Template: `.claude/skills/openspec-e2e/templates/test-plan.md`
|
|
379
361
|
|
|
380
|
-
**Idempotency**: If test-plan.md exists → read and use, do
|
|
362
|
+
**Idempotency**: If test-plan.md exists → read and use, **but you MAY supplement missing test cases**. "Do not regenerate" means: do not discard existing cases, but you CAN add new ones discovered during Step 4 exploration that weren't in the original spec (e.g., empty states, error paths found during DOM exploration).
|
|
381
363
|
|
|
382
364
|
**⚠️ Human verification — STOP before generating code.**
|
|
383
365
|
|
|
@@ -408,15 +390,9 @@ After creating (or reading existing) test-plan.md, **stop and display the test p
|
|
|
408
390
|
- `<element or scenario not testable>`
|
|
409
391
|
````
|
|
410
392
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
**Why this matters**: Step 5 is the last human-reviewable checkpoint before code generation. Once test code is written, fixes address *how* tests run, not *what* they verify. Reviewing the test plan takes seconds and catches logic errors that Healer cannot fix.
|
|
393
|
+
**Important**: Only list special elements that were actually detected in Step 4. Do not pre-populate with all possible types. If no special elements were found → omit the **Special Elements** section entirely.
|
|
414
394
|
|
|
415
|
-
**
|
|
416
|
-
- All scenarios from OpenSpec specs are covered
|
|
417
|
-
- Special elements (Canvas, Iframe, Video, Audio, CAPTCHA, OTP, File Upload, Drag-drop, WebSocket) have correct automation strategy
|
|
418
|
-
- Auth states and roles are accurate
|
|
419
|
-
- Nothing important is missing
|
|
395
|
+
Then ask: "Does this coverage match your intent? Reply **yes** to proceed, or tell me what to add/change."
|
|
420
396
|
|
|
421
397
|
If the user requests changes → update test-plan.md → re-display summary → re-confirm → proceed.
|
|
422
398
|
|
|
@@ -424,7 +400,7 @@ If the user requests changes → update test-plan.md → re-display summary →
|
|
|
424
400
|
|
|
425
401
|
**"all" mode**: Build and expand Page Objects for future Change tests.
|
|
426
402
|
|
|
427
|
-
**Prerequisite
|
|
403
|
+
**Prerequisite** (change mode only): If `app-exploration.md` does not exist → **STOP**. Run Step 4 first. For **all mode**, exploration is embedded dynamically in Step 6 — no pre-existing app-exploration.md is required.
|
|
428
404
|
|
|
429
405
|
**Page Object pattern** — read before writing any page file:
|
|
430
406
|
|
|
@@ -466,7 +442,7 @@ For each discovered route:
|
|
|
466
442
|
2. Navigate to route with correct auth state
|
|
467
443
|
3. browser_snapshot to extract interactive elements (see 4.3 table)
|
|
468
444
|
4. Write or update `pages/<Route>Page.ts` — extend with newly discovered elements
|
|
469
|
-
5. Also write `tests/playwright/app-all.spec.ts` — smoke test
|
|
445
|
+
5. Also write `tests/playwright/app-all.spec.ts` — smoke test. **Minimum standard**: verify at least one heading or key interactive element is visible — not just "no crash". If the page loads but shows an empty shell or an error, the test must fail.
|
|
470
446
|
|
|
471
447
|
**Output priority**: Page Objects (`pages/*.ts`) are the primary asset. Smoke test is secondary. Existing Page Objects are never overwritten — only extended.
|
|
472
448
|
|
|
@@ -495,84 +471,13 @@ Is this assertion about a visible UI result?
|
|
|
495
471
|
|
|
496
472
|
**Test coverage — empty states**: For list/detail pages, explore the empty state. If the app shows a "no data" UI when the list is empty, generate a test to verify it. Empty states are often missing from specs but are real user paths.
|
|
497
473
|
|
|
498
|
-
**Test coverage — special elements**: Check `app-exploration.md` → **Special Elements Detected** table. For each special element
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
await expect(canvas).toBeVisible();
|
|
506
|
-
const box = await canvas.boundingBox();
|
|
507
|
-
expect(box.width).toBeGreaterThan(0);
|
|
508
|
-
await canvas.screenshot({ path: '__screenshots__/canvas.png' });
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// Canvas — 2D pixel verification
|
|
512
|
-
test('canvas 2D content is not blank', async ({ page }) => {
|
|
513
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
514
|
-
const hasContent = await page.evaluate(() => {
|
|
515
|
-
const c = document.querySelector('canvas');
|
|
516
|
-
if (!c) return false;
|
|
517
|
-
const ctx = c.getContext('2d');
|
|
518
|
-
if (!ctx) return false;
|
|
519
|
-
const data = ctx.getImageData(0, 0, c.width, c.height).data;
|
|
520
|
-
return data.some((v, i) => i % 4 !== 3 && v !== 0); // non-transparent non-black pixel
|
|
521
|
-
});
|
|
522
|
-
expect(hasContent).toBe(true);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// Canvas — WebGL screenshot
|
|
526
|
-
test('canvas WebGL renders', async ({ page }) => {
|
|
527
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
528
|
-
const canvas = page.locator('canvas');
|
|
529
|
-
await expect(canvas).toBeVisible();
|
|
530
|
-
await canvas.screenshot({ path: '__screenshots__/webgl.png' });
|
|
531
|
-
// No pixel comparison — WebGL rendering may vary
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
// Iframe — switch context
|
|
535
|
-
test('iframe content is accessible', async ({ page }) => {
|
|
536
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
537
|
-
const frame = page.frameLocator('iframe[name="<name>"]');
|
|
538
|
-
await expect(frame.locator('<selector-inside-frame>')).toBeVisible();
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Rich text editor — evaluate content
|
|
542
|
-
test('editor content persists', async ({ page }) => {
|
|
543
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
544
|
-
const editor = page.locator('[contenteditable]');
|
|
545
|
-
await editor.click();
|
|
546
|
-
await page.keyboard.type('Hello E2E');
|
|
547
|
-
const content = await page.evaluate(() => {
|
|
548
|
-
const el = document.querySelector('[contenteditable]');
|
|
549
|
-
return el?.textContent;
|
|
550
|
-
});
|
|
551
|
-
expect(content).toContain('Hello E2E');
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// Video — playback state
|
|
555
|
-
test('video can be played', async ({ page }) => {
|
|
556
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
557
|
-
const video = page.locator('video');
|
|
558
|
-
await expect(video).toBeVisible();
|
|
559
|
-
await video.evaluate((v: HTMLVideoElement) => { v.play(); });
|
|
560
|
-
const isPlaying = await video.evaluate((v: HTMLVideoElement) => !v.paused);
|
|
561
|
-
expect(isPlaying).toBe(true);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
// Audio — playback state
|
|
565
|
-
test('audio can be played', async ({ page }) => {
|
|
566
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
567
|
-
const audio = page.locator('audio');
|
|
568
|
-
await expect(audio).toBeVisible();
|
|
569
|
-
await audio.evaluate((a: HTMLAudioElement) => { a.play(); });
|
|
570
|
-
const isPlaying = await audio.evaluate((a: HTMLAudioElement) => !a.paused);
|
|
571
|
-
expect(isPlaying).toBe(true);
|
|
572
|
-
});
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
See `.claude/skills/openspec-e2e/templates/test-plan.md` → **Special Element Test Cases** for full templates including Canvas, Video, Audio, Iframe, and Rich Text Editor.
|
|
474
|
+
**Test coverage — special elements**: Check `app-exploration.md` → **Special Elements Detected** table. For each special element, generate tests using templates from `.claude/skills/openspec-e2e/templates/test-plan.md` → **Special Element Test Cases**:
|
|
475
|
+
- Canvas: screenshot + boundingBox → dimensions > 0, or 2D pixel verification
|
|
476
|
+
- WebGL: screenshot only (no pixel comparison — rendering varies)
|
|
477
|
+
- Iframe: `frameLocator` + assert inner content visible
|
|
478
|
+
- Rich text: `contenteditable` → type + `textContent` assertion
|
|
479
|
+
- Video/Audio: `play()` → assert `!paused`
|
|
480
|
+
- CAPTCHA/OTP/File upload/Drag-drop: See AI-Opaque Elements section in templates
|
|
576
481
|
|
|
577
482
|
**Test coverage — AI-opaque elements**: For CAPTCHA, OTP, slider CAPTCHA, file upload, and drag-drop — elements that Playwright cannot reliably automate:
|
|
578
483
|
|
|
@@ -584,24 +489,7 @@ See `.claude/skills/openspec-e2e/templates/test-plan.md` → **Special Element T
|
|
|
584
489
|
- **Drag-drop**: Use `page.dragAndDrop()` or `page.evaluate()` with custom event dispatching
|
|
585
490
|
3. If the element is truly non-automatable, write `test.skip()` with a comment explaining why, and mark with `/handoff` for manual testing
|
|
586
491
|
|
|
587
|
-
**Test coverage — performance**:
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
// Performance — Core Web Vitals
|
|
591
|
-
test('page loads within performance budget', async ({ page }) => {
|
|
592
|
-
await page.goto(`${BASE_URL}/<route>`);
|
|
593
|
-
await expect(page.getByRole('heading')).toBeVisible();
|
|
594
|
-
const timings = await page.evaluate(() => {
|
|
595
|
-
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
|
|
596
|
-
return {
|
|
597
|
-
ttfb: nav.responseStart - nav.requestStart,
|
|
598
|
-
lcp: nav.loadEventEnd - nav.requestStart,
|
|
599
|
-
};
|
|
600
|
-
});
|
|
601
|
-
expect(timings.ttfb).toBeLessThan(500);
|
|
602
|
-
expect(timings.lcp).toBeLessThan(2500);
|
|
603
|
-
});
|
|
604
|
-
```
|
|
492
|
+
**Test coverage — performance**: Generate a Core Web Vitals test **only if** the OpenSpec spec or app-exploration.md specifies explicit performance targets (e.g., "LCP must be under 2s"). If no business target is defined, skip performance testing — hard-coded thresholds (lcp < 2500ms) produce false passes and add noise.
|
|
605
493
|
|
|
606
494
|
```typescript
|
|
607
495
|
// 🚫 Avoid for special elements:
|
|
@@ -665,83 +553,14 @@ test('user can login', async ({ page }) => {
|
|
|
665
553
|
|
|
666
554
|
**If a shared page object doesn't exist yet**: define it inline in the spec AND write it to `tests/playwright/pages/<PageName>.ts` so future tests can reuse it.
|
|
667
555
|
|
|
668
|
-
#### 6.2. Selector
|
|
556
|
+
#### 6.2. Selector patterns
|
|
669
557
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
page.locator('.skeleton-overlay')
|
|
675
|
-
|
|
676
|
-
// ✅ Robust — semantic selectors survive style changes
|
|
677
|
-
page.getByRole('button', { name: '通知' })
|
|
678
|
-
page.getByTestId('header-bar')
|
|
679
|
-
page.getByText('加载中')
|
|
680
|
-
|
|
681
|
-
// 🚫 Fragile — CSS ID selectors can duplicate in React HMR
|
|
682
|
-
page.locator('#avatarBtn')
|
|
683
|
-
page.locator('#userAvatarBtn')
|
|
684
|
-
|
|
685
|
-
// ✅ Robust — prefer role/label/testid over CSS ID
|
|
686
|
-
page.getByTestId('user-avatar')
|
|
687
|
-
page.getByRole('button', { name: '用户菜单' })
|
|
688
|
-
|
|
689
|
-
// 🚫 Missing wait — leads to random CI failures
|
|
690
|
-
await page.locator('.submit-btn').click();
|
|
691
|
-
|
|
692
|
-
// ✅ Safe — scroll into view first
|
|
693
|
-
await page.locator('.submit-btn').scrollIntoViewIfNeeded();
|
|
694
|
-
await page.locator('.submit-btn').click();
|
|
695
|
-
|
|
696
|
-
// ✅ Better — use BasePage click with built-in wait
|
|
697
|
-
const app = new AppPage(page);
|
|
698
|
-
await app.click(app.byRole('button', { name: '提交' }));
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
**Code examples — UI first:**
|
|
702
|
-
|
|
703
|
-
```typescript
|
|
704
|
-
// ✅ UI 测试 — fill 后必须验证值,确保框架同步完成
|
|
705
|
-
const app = new AppPage(page);
|
|
706
|
-
await app.goto(`${BASE_URL}/orders`);
|
|
707
|
-
await app.click(app.byRole('button', { name: '新建订单' }));
|
|
708
|
-
await app.fillAndVerify(app.byLabel('订单名称'), 'Test Order');
|
|
709
|
-
await app.click(app.byRole('button', { name: '提交' }));
|
|
710
|
-
await expect(page.getByText('订单创建成功')).toBeVisible();
|
|
711
|
-
|
|
712
|
-
// ✅ Error path
|
|
713
|
-
await page.goto(`${BASE_URL}/orders`);
|
|
714
|
-
await page.getByRole("button", { name: "提交" }).click();
|
|
715
|
-
await expect(page.getByRole("alert")).toContainText("名称不能为空");
|
|
716
|
-
|
|
717
|
-
// ✅ API fallback (only when UI cannot reach the scenario)
|
|
718
|
-
const res = await page.request.get(`${BASE_URL}/api/orders/99999`);
|
|
719
|
-
expect(res.status()).toBe(404);
|
|
720
|
-
|
|
721
|
-
// ✅ Auth guard — fresh browser context (no cookies)
|
|
722
|
-
test("redirects to login when unauthenticated", async ({ browser }) => {
|
|
723
|
-
const freshPage = await browser.newContext().newPage();
|
|
724
|
-
await freshPage.goto(`${BASE_URL}/dashboard`);
|
|
725
|
-
await expect(freshPage).toHaveURL(/login|auth/);
|
|
726
|
-
});
|
|
558
|
+
| Prefer (robust) | Avoid (fragile) |
|
|
559
|
+
| — | — |
|
|
560
|
+
| `getByRole`, `getByTestId`, `getByLabel` | CSS class (`'.notification-bell'`), CSS ID (`'#avatarBtn'`) |
|
|
561
|
+
| `waitForSelector(targetElement)` | hardcoded `200ms` / `500ms` delays |
|
|
727
562
|
|
|
728
|
-
|
|
729
|
-
await page.getByRole("button", { name: "退出登录" }).click();
|
|
730
|
-
await expect(page).toHaveURL(/login|auth/);
|
|
731
|
-
const freshPage2 = await browser.newContext().newPage();
|
|
732
|
-
await freshPage2.goto(`${BASE_URL}/dashboard`);
|
|
733
|
-
await expect(freshPage2).toHaveURL(/login|auth/); // session revoked
|
|
734
|
-
|
|
735
|
-
// ✅ Browser history — SPA back/forward
|
|
736
|
-
await page.goto(`${BASE_URL}/list`);
|
|
737
|
-
await page.getByRole("link", { name: "详情" }).first().click();
|
|
738
|
-
await expect(page).toHaveURL(/detail/);
|
|
739
|
-
await page.goBack();
|
|
740
|
-
await expect(page).toHaveURL(/list/);
|
|
741
|
-
|
|
742
|
-
// ✅ File uploads
|
|
743
|
-
await page.locator('input[type="file"]').setInputFiles("/path/to/file.pdf");
|
|
744
|
-
```
|
|
563
|
+
See `.claude/skills/openspec-e2e/templates/e2e-test.ts` for full examples of Page Object pattern, UI-first flows, error paths, auth guards, session handling, and visual regression.
|
|
745
564
|
|
|
746
565
|
If the file exists → diff against test-plan, add only missing test cases.
|
|
747
566
|
|
|
@@ -795,13 +614,15 @@ If playwright.config.ts exists → READ first, preserve ALL existing fields, add
|
|
|
795
614
|
### 9. Execute tests
|
|
796
615
|
|
|
797
616
|
```bash
|
|
798
|
-
openspec-pw run <name> [--project <role>]
|
|
617
|
+
openspec-pw run <name> [--project <role>] [--headed] [--update-snapshots]
|
|
799
618
|
```
|
|
800
619
|
|
|
801
620
|
The CLI handles: server lifecycle, port mismatch, report generation.
|
|
802
621
|
|
|
803
622
|
If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
804
623
|
|
|
624
|
+
**Browser visibility**: The Healer uses browser MCP tools (snapshot, screenshot, console messages) to inspect failures — no need for `--headed`. If you want to **watch the browser yourself** during debugging, add `--headed`: `openspec-pw run <name> --headed`.
|
|
625
|
+
|
|
805
626
|
**Healer MCP tools** (in order of use):
|
|
806
627
|
|
|
807
628
|
| Tool | Purpose |
|
|
@@ -815,22 +636,67 @@ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
|
|
|
815
636
|
|
|
816
637
|
**Healer — Phase 1: Triage**
|
|
817
638
|
|
|
818
|
-
When a test fails, classify before attempting repair
|
|
639
|
+
When a test fails, classify before attempting repair.
|
|
640
|
+
|
|
641
|
+
**Batch Failure Detection — run this FIRST when multiple tests fail:**
|
|
642
|
+
|
|
643
|
+
```
|
|
644
|
+
Collect ALL failing test names + their failure reasons.
|
|
645
|
+
Group by: same route + same action + same error pattern.
|
|
646
|
+
If ≥2 tests fall into the same group:
|
|
647
|
+
→ Pause individual healing
|
|
648
|
+
→ Navigate to that route + perform the action manually
|
|
649
|
+
→ Check browser_console_messages + browser_network_requests
|
|
650
|
+
→ If console error or 4xx/5xx present:
|
|
651
|
+
→ This is an App Bug (backend/API change), NOT Test Bugs
|
|
652
|
+
→ Classify all tests in this group as App Bug
|
|
653
|
+
→ Skip all → record 1 App Bug in registry (not N bugs)
|
|
654
|
+
→ Skip the rest of individual Triage for this group
|
|
655
|
+
→ If no console/network error but all still fail:
|
|
656
|
+
→ Likely a shared state issue → RAFT
|
|
657
|
+
→ Skip all → note RAFT in report
|
|
658
|
+
Proceed with individual Triage only for tests NOT in a batch failure group.
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**After Batch Detection, individual Triage:**
|
|
819
662
|
|
|
820
663
|
| Failure Type | Signal | Classification | Action |
|
|
821
664
|
| — | — | — | — |
|
|
822
|
-
| **Network/Backend** | `net::ERR`, 4xx/5xx in console/network | **App Bug** | `test.skip()` +
|
|
823
|
-
| **JS Runtime Error** | Console error (non-network) | **App Bug** | `test.skip()` +
|
|
665
|
+
| **Network/Backend** | `net::ERR`, 4xx/5xx in console/network | **App Bug** | `test.skip()` + record in `app-bug-registry.md` |
|
|
666
|
+
| **JS Runtime Error** | Console error (non-network) | **App Bug** | `test.skip()` + record in `app-bug-registry.md` |
|
|
824
667
|
| **Auth Expired** | Redirected to login mid-test | **Flaky** | Re-run auth.setup → re-run |
|
|
825
668
|
| **Selector Not Found** | Element not found | **Test Bug** | → Phase 2 Healer |
|
|
826
669
|
| **Assertion Mismatch** | Wrong content/value | **Ambiguous** | → Phase 2 Healer |
|
|
827
670
|
| **Timeout** | waitFor/evaluate timeout | **Flaky** | Retry isolated: `openspec-pw run <name> --grep "<test-name>"` (1×, not counted in heal attempts). If it passes isolated but fails in suite → **RAFT**. If it consistently times out → check framework: React 19 / Next.js App Router: add `page.waitForLoadState('networkidle')`. Vue/Angular/React 18 / Plain JS / jQuery: use `waitForSelector(targetElement)` instead of timeout tuning. |
|
|
828
671
|
| **Same test fails in suite, passes isolated** | — | **RAFT** | `test.skip()` in suite, note RAFT in report |
|
|
829
672
|
|
|
830
|
-
- **App Bug** → skip immediately (no healing needed)
|
|
673
|
+
- **App Bug** → skip immediately (no healing needed) → record in App Bug Registry
|
|
831
674
|
- **Flaky** → retry once isolated
|
|
832
675
|
- **Test Bug / Ambiguous** → Phase 2
|
|
833
676
|
|
|
677
|
+
#### App Bug Registry
|
|
678
|
+
|
|
679
|
+
For every App Bug classified in Phase 1, record it in `openspec/reports/app-bug-registry.md` (create if missing):
|
|
680
|
+
|
|
681
|
+
```markdown
|
|
682
|
+
# App Bug Registry
|
|
683
|
+
|
|
684
|
+
<!-- Auto-generated. Do not edit manually. -->
|
|
685
|
+
|
|
686
|
+
## Active App Bugs
|
|
687
|
+
|
|
688
|
+
| # | Test | Route | Signal | First Detected | Status |
|
|
689
|
+
|---|------|-------|--------|---------------|--------|
|
|
690
|
+
| 1 | test-name | /route | net::ERR_CONNECTION_REFUSED | 2026-04-09 | open |
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Update rules**:
|
|
694
|
+
- **New App Bug**: Append new row, increment `#`
|
|
695
|
+
- **App Bug re-run and now passes**: Keep row, change status to `resolved` + add `Resolved` column with date
|
|
696
|
+
- **Keep all rows** (never delete) — the resolved count is the signal that bugs are being fixed
|
|
697
|
+
|
|
698
|
+
> **Why this matters**: `test.skip()` hides App Bugs from the pass/fail count. Without an explicit registry, "all tests passed" is a false positive when App Bugs exist. The registry makes the invisible visible.
|
|
699
|
+
|
|
834
700
|
> **Global attempt guard**: Each test has an independent heal counter (max 3 per test). If the same test enters Phase 2 more than once and reaches the cap each time → treat as "consecutive escalation without progress" → Phase 3 immediately.
|
|
835
701
|
|
|
836
702
|
> **Type ≠ Blame**: "Test Bug" means the assertion or selector is wrong — it does NOT mean "blame the test author." The test was generated from the spec. Root cause may be spec ambiguity, spec→test generation error, or app→spec deviation. Only a human can determine blame.
|
|
@@ -848,13 +714,38 @@ After Triage classifies failure as "Test Bug" or "Ambiguous":
|
|
|
848
714
|
MATCH: <yes/no>
|
|
849
715
|
```
|
|
850
716
|
4. If MATCH=no:
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
717
|
+
|
|
718
|
+
**⚠️ Assertion modification guard — never skip Phase 3 unless ALL conditions are met:**
|
|
719
|
+
|
|
720
|
+
- The test has **never passed with the current assertion** (newly generated test, or this is the first time this assertion fails)
|
|
721
|
+
- The ACTUAL value is **verifiably** from a different spec section (e.g., this test was in the wrong describe block)
|
|
722
|
+
- You can point to the **specific line in the spec** that defines the ACTUAL behavior
|
|
723
|
+
|
|
724
|
+
**If ANY condition is uncertain → Phase 3 immediately.** Do NOT modify the assertion.
|
|
725
|
+
|
|
726
|
+
**Safe to fix without Phase 3:**
|
|
727
|
+
- Typo in assertion (e.g., "Submmit" vs "Submit" in the expected text)
|
|
728
|
+
- Selector was correct but the element was moved to a different location (same text, different selector)
|
|
729
|
+
- Explicit spec drift confirmed by reading the spec (e.g., spec says "button says Submit" but test says "button says Submit Form")
|
|
730
|
+
|
|
731
|
+
**Never fix without Phase 3:**
|
|
732
|
+
- App behavior changed after an action (e.g., "after clicking submit, balance should decrease" → ACTUAL shows no change → **Phase 3**, could be optimistic update bug, backend failure, or spec mismatch)
|
|
733
|
+
- Data values differ (e.g., expected "¥1000" but got "¥999" → **Phase 3**, could be rounding, discount, or calculation bug)
|
|
734
|
+
- Missing elements after interaction (e.g., "after creating order, success message should appear" → no message → **Phase 3**)
|
|
735
|
+
|
|
854
736
|
5. If selector issue → find equivalent stable selector from snapshot
|
|
855
737
|
6. Apply fix → re-run **only that test** (attempt 1/3)
|
|
856
738
|
7. If healed → append to `app-knowledge.md` → **Selector Fixes** table (route, old → new selector, reason)
|
|
857
739
|
|
|
740
|
+
**Element Missing handling (when browser_snapshot shows element not found):**
|
|
741
|
+
|
|
742
|
+
| Situation | Check | Action |
|
|
743
|
+
| — | — | — |
|
|
744
|
+
| JS error in console after action | `browser_console_messages` | **App Bug** → Phase 1 → App Bug classification |
|
|
745
|
+
| Auth redirected mid-action | URL changed to `/login` | **Flaky** → re-run with fresh auth |
|
|
746
|
+
| SPA route didn't update | URL is correct but element missing | Wait for SPA hydration → `page.waitForLoadState('networkidle')` or `waitForSelector(target)` |
|
|
747
|
+
| Element genuinely missing | None of the above | **Test Bug** → find alternative selector or **Phase 3** if no equivalent exists |
|
|
748
|
+
|
|
858
749
|
**Healer — Phase 3: Escalate**
|
|
859
750
|
|
|
860
751
|
When Phase 2 tried ≥3 heals without success, OR ASSERTION vs ACTUAL comparison is ambiguous:
|
|
@@ -900,7 +791,7 @@ openspec-pw run <change-name>
|
|
|
900
791
|
```
|
|
901
792
|
`/opsx:e2e <change-name>` re-runs the full 11-step workflow — unnecessary after Phase 3. The test file and auth context are already correct. Use `openspec-pw run` to verify fixes directly.
|
|
902
793
|
|
|
903
|
-
### 10. False Pass Detection + RAFT Detection
|
|
794
|
+
### 10. False Pass Detection + RAFT Detection + App Bug Accumulation
|
|
904
795
|
|
|
905
796
|
Run after test suite completes (even if all pass).
|
|
906
797
|
|
|
@@ -916,11 +807,23 @@ Run after test suite completes (even if all pass).
|
|
|
916
807
|
- This is **NOT** a test bug or app bug. Mark as RAFT, add `test.skip()` in suite, note in report
|
|
917
808
|
- RAFTs are infrastructure coupling issues (CPU/memory/I/O contention), not fixable by changing test or app
|
|
918
809
|
|
|
810
|
+
**App Bug Accumulation Detection** (most critical):
|
|
811
|
+
|
|
812
|
+
1. Read `openspec/reports/app-bug-registry.md`
|
|
813
|
+
2. Count `open` status rows
|
|
814
|
+
3. If ≥ 3 active App Bugs → add "⚠️ App Bug accumulation: N bugs unresolved" to report summary. **Do NOT suppress or hide this warning.** The test suite may report "all passed" (skipped ≠ failed), but N broken features is not a passing system.
|
|
815
|
+
4. Check for **App Bugs that became "resolved"** (passing on re-run) → this is the signal that bugs were fixed. Update `app-bug-registry.md` accordingly, and remove `test.skip()` from those tests so they re-enter the regression suite.
|
|
816
|
+
|
|
919
817
|
### 11. Report results
|
|
920
818
|
|
|
921
819
|
Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
922
820
|
|
|
923
|
-
- Summary table with failure type breakdown (App Bugs, Test Bugs/healed, Flaky-RAFT, Human Escalations)
|
|
821
|
+
- **Summary table** with failure type breakdown (App Bugs, Test Bugs/healed, Flaky-RAFT, Human Escalations)
|
|
822
|
+
- **App Bug Summary**: Table of all active App Bugs from `app-bug-registry.md` — test name, route, signal, first detected. If ≥ 3 active → display "⚠️ App Bug accumulation warning" prominently.
|
|
823
|
+
- **Conditional "All Pass" conclusion**:
|
|
824
|
+
- ✅ **"All tests passed"** — only if 0 active App Bugs and 0 skipped tests
|
|
825
|
+
- ⚠️ **"All tests passed (N skipped)"** — if skipped tests exist but no active App Bugs
|
|
826
|
+
- ⚠️ **"All tests passed (N skipped, M App Bugs unresolved)"** — if active App Bugs exist
|
|
924
827
|
- Failure Classification table (test, type, action, healed?)
|
|
925
828
|
- Auto-heal log (assertion vs actual comparison, fix applied, result)
|
|
926
829
|
- RAFT Summary (if any detected)
|
|
@@ -929,11 +832,9 @@ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
|
|
|
929
832
|
|
|
930
833
|
Report template: `.claude/skills/openspec-e2e/templates/report.md`
|
|
931
834
|
|
|
932
|
-
**Update tasks.md
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
Reference: `.claude/skills/openspec-e2e/templates/report.md`
|
|
835
|
+
**Update tasks.md**:
|
|
836
|
+
- If 0 active App Bugs → find E2E-related items, append `✅ Verified via Playwright E2E (<timestamp>)`.
|
|
837
|
+
- If active App Bugs exist → **do not mark as verified**. Append instead: `⚠️ App Bug blocked: <bug summary> (<timestamp>)`.
|
|
937
838
|
|
|
938
839
|
## Graceful Degradation
|
|
939
840
|
|
|
@@ -945,11 +846,12 @@ Reference: `.claude/skills/openspec-e2e/templates/report.md`
|
|
|
945
846
|
| JS errors or HTTP 5xx during exploration | **STOP** → user fixes app → re-run `/opsx:e2e <name>` to re-explore from Step 4 |
|
|
946
847
|
| Sitemap fails ("all" mode) | Continue with homepage links fallback |
|
|
947
848
|
| File already exists (app-exploration, test-plan, app-all.spec.ts, Page Objects) | Read and use — never regenerate |
|
|
948
|
-
| Test fails (network/backend) | **App Bug** — `test.skip()` +
|
|
849
|
+
| Test fails (network/backend) | **App Bug** — `test.skip()` + record in `app-bug-registry.md` |
|
|
949
850
|
| Test fails (selector/assertion) | **Test Bug/Ambiguous** — Healer Phase 1→2 (≤3 attempts) |
|
|
950
851
|
| RAFT detected (suite fail, isolated pass) | **Flaky** — `test.skip()` in suite, note RAFT in report |
|
|
951
852
|
| Phase 3 escalation | **Human needed** — STOP + ask user |
|
|
952
853
|
| False pass detected | Add "⚠️ Coverage Gap" to report |
|
|
854
|
+
| App Bug skip accumulation (≥3 active App Bugs) | **Warning** — add "⚠️ App Bug accumulation: N bugs unresolved" to report summary. Do not suppress. |
|
|
953
855
|
|
|
954
856
|
## Guardrails
|
|
955
857
|
|
|
@@ -968,6 +870,6 @@ Reference: `.claude/skills/openspec-e2e/templates/report.md`
|
|
|
968
870
|
|
|
969
871
|
> `tests/playwright/` — spec files, Page Objects, auth, credentials, app-knowledge.md
|
|
970
872
|
> `openspec/changes/<name>/specs/playwright/` — app-exploration.md, test-plan.md (change mode)
|
|
971
|
-
> `openspec/reports/` — test reports
|
|
873
|
+
> `openspec/reports/` — test reports, app-bug-registry.md
|
|
972
874
|
|
|
973
875
|
**Never write to:** any other directory
|
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ Claude Code — E2E workflow is driven by SKILL.md using Playwright MCP tools (`
|
|
|
36
36
|
openspec-pw init # Initialize integration (one-time setup)
|
|
37
37
|
openspec-pw update # Update CLI and commands to latest version
|
|
38
38
|
openspec-pw doctor # Check prerequisites
|
|
39
|
+
openspec-pw audit # Audit tests for orphaned specs and issues
|
|
40
|
+
openspec-pw migrate # Migrate old test files to new structure
|
|
39
41
|
openspec-pw uninstall # Remove integration from the project
|
|
40
42
|
```
|
|
41
43
|
|
|
@@ -156,6 +158,7 @@ CLI (openspec-pw)
|
|
|
156
158
|
├── update → Syncs commands, skill & templates from npm
|
|
157
159
|
├── run → Executes E2E tests with server lifecycle
|
|
158
160
|
├── migrate → Migrates old test files to new structure
|
|
161
|
+
├── audit → Audits tests for orphaned specs and issues
|
|
159
162
|
├── doctor → Checks prerequisites
|
|
160
163
|
└── uninstall → Removes integration from the project
|
|
161
164
|
|
package/README.zh-CN.md
CHANGED
|
@@ -49,6 +49,8 @@ Claude Code — E2E 工作流由 SKILL.md 驱动,使用 Playwright MCP 工具
|
|
|
49
49
|
openspec-pw init # 初始化集成(一次性设置)
|
|
50
50
|
openspec-pw update # 更新 CLI 和命令到最新版本
|
|
51
51
|
openspec-pw doctor # 检查前置条件
|
|
52
|
+
openspec-pw audit # 检查测试文件是否有孤儿文件和配置问题
|
|
53
|
+
openspec-pw migrate # 迁移旧测试文件到新目录结构
|
|
52
54
|
openspec-pw uninstall # 移除项目中的集成
|
|
53
55
|
```
|
|
54
56
|
|
|
@@ -157,6 +159,7 @@ CLI (openspec-pw)
|
|
|
157
159
|
├── update → 从 npm 同步命令、skill 和模板
|
|
158
160
|
├── run → 执行 E2E 测试并管理服务器生命周期
|
|
159
161
|
├── migrate → 迁移旧测试文件到新目录结构
|
|
162
|
+
├── audit → 检查测试文件是否有孤儿文件和配置问题
|
|
160
163
|
├── doctor → 检查前置条件
|
|
161
164
|
└── uninstall → 移除项目中的集成
|
|
162
165
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function audit(): Promise<void>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
export async function audit() {
|
|
6
|
+
const projectRoot = process.cwd();
|
|
7
|
+
const testsDir = join(projectRoot, "tests", "playwright");
|
|
8
|
+
if (!existsSync(testsDir)) {
|
|
9
|
+
console.log(chalk.yellow(" tests/playwright/ not found. Run `openspec-pw init` first.\n"));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.log(chalk.blue("\n🔍 OpenSpec Playwright: Audit\n"));
|
|
13
|
+
const results = [];
|
|
14
|
+
// 1. Get sitemap routes
|
|
15
|
+
const sitemapRoutes = await getSitemapRoutes(projectRoot);
|
|
16
|
+
const allRoutes = sitemapRoutes ?? [];
|
|
17
|
+
// 2. Get OpenSpec change names
|
|
18
|
+
const changeNames = await getChangeNames(projectRoot);
|
|
19
|
+
// 3. Scan all spec files recursively
|
|
20
|
+
const specFiles = collectSpecFiles(testsDir);
|
|
21
|
+
// 4. Audit each spec file
|
|
22
|
+
const SHARED_FILES = new Set([
|
|
23
|
+
"seed.spec.ts",
|
|
24
|
+
"app-all.spec.ts",
|
|
25
|
+
"auth.setup.ts",
|
|
26
|
+
"credentials.yaml",
|
|
27
|
+
"app-knowledge.md",
|
|
28
|
+
"playwright.config.ts",
|
|
29
|
+
"mcp-tools.md",
|
|
30
|
+
]);
|
|
31
|
+
for (const file of specFiles) {
|
|
32
|
+
const relPath = file.replace(testsDir + "/", "");
|
|
33
|
+
const content = readFileSync(file, "utf-8");
|
|
34
|
+
// Skip shared files
|
|
35
|
+
const fileName = relPath.split("/").pop() ?? "";
|
|
36
|
+
if (SHARED_FILES.has(fileName))
|
|
37
|
+
continue;
|
|
38
|
+
// 4a. Orphaned spec file: no matching OpenSpec change
|
|
39
|
+
const changeName = fileName.replace(".spec.ts", "");
|
|
40
|
+
// Check if this is a root-level old-style file
|
|
41
|
+
if (!relPath.includes("/")) {
|
|
42
|
+
if (changeNames.length > 0 && !changeNames.includes(changeName)) {
|
|
43
|
+
results.push({
|
|
44
|
+
fileName: relPath,
|
|
45
|
+
issue: "Orphaned spec file",
|
|
46
|
+
detail: `No matching OpenSpec change found. Consider migrating to tests/playwright/changes/${changeName}/`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 4b. Check for hardcoded URLs not in sitemap
|
|
51
|
+
const urlMatches = content.match(/https?:\/\/[^\s'"]+/g);
|
|
52
|
+
if (urlMatches) {
|
|
53
|
+
for (const url of urlMatches) {
|
|
54
|
+
const pathname = new URL(url).pathname;
|
|
55
|
+
if (pathname !== "/" &&
|
|
56
|
+
allRoutes.length > 0 &&
|
|
57
|
+
!allRoutes.includes(pathname) &&
|
|
58
|
+
!allRoutes.some((r) => pathname.startsWith(r))) {
|
|
59
|
+
results.push({
|
|
60
|
+
fileName: relPath,
|
|
61
|
+
issue: "Route not in sitemap",
|
|
62
|
+
detail: `Found URL: ${url}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 5. Check for missing auth.setup when tests reference protected routes
|
|
69
|
+
const needsAuth = specFiles.some((file) => {
|
|
70
|
+
const content = readFileSync(file, "utf-8");
|
|
71
|
+
const fileName = file.split("/").pop() ?? "";
|
|
72
|
+
return (!SHARED_FILES.has(fileName) &&
|
|
73
|
+
(content.includes("storageState") ||
|
|
74
|
+
content.includes("auth.setup") ||
|
|
75
|
+
content.includes("authenticated") ||
|
|
76
|
+
content.includes("dashboard") ||
|
|
77
|
+
content.includes("profile")));
|
|
78
|
+
});
|
|
79
|
+
if (needsAuth && !existsSync(join(testsDir, "auth.setup.ts"))) {
|
|
80
|
+
results.push({
|
|
81
|
+
fileName: "auth.setup.ts",
|
|
82
|
+
issue: "Missing auth setup",
|
|
83
|
+
detail: "Tests reference protected routes but auth.setup.ts is not found",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// 6. Check for deprecated old-style file locations
|
|
87
|
+
const rootSpecFiles = readdirSync(testsDir).filter((f) => f.endsWith(".spec.ts") && !SHARED_FILES.has(f));
|
|
88
|
+
for (const f of rootSpecFiles) {
|
|
89
|
+
results.push({
|
|
90
|
+
fileName: f,
|
|
91
|
+
issue: "Old-style file location",
|
|
92
|
+
detail: `Run \`openspec-pw migrate\` to move to tests/playwright/changes/${f.replace(".spec.ts", "")}/`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// 7. Output results
|
|
96
|
+
if (results.length === 0) {
|
|
97
|
+
console.log(chalk.green(" ✅ No issues found. All tests look healthy.\n"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk.yellow(`─── Found ${results.length} issue(s) ───`));
|
|
101
|
+
// Group by issue type
|
|
102
|
+
const grouped = {};
|
|
103
|
+
for (const r of results) {
|
|
104
|
+
if (!grouped[r.issue])
|
|
105
|
+
grouped[r.issue] = [];
|
|
106
|
+
grouped[r.issue].push(r);
|
|
107
|
+
}
|
|
108
|
+
for (const [issue, items] of Object.entries(grouped)) {
|
|
109
|
+
console.log(chalk.yellow(`\n ⚠ ${issue}`));
|
|
110
|
+
for (const item of items) {
|
|
111
|
+
console.log(chalk.gray(` - ${item.fileName}`));
|
|
112
|
+
if (item.detail) {
|
|
113
|
+
console.log(chalk.gray(` → ${item.detail}`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log(chalk.blue("\n─── Suggested fixes ───"));
|
|
118
|
+
if (Object.keys(grouped).some((k) => k.includes("Old-style"))) {
|
|
119
|
+
console.log(chalk.green(" Run `openspec-pw migrate` to reorganize file structure."));
|
|
120
|
+
}
|
|
121
|
+
if (Object.keys(grouped).some((k) => k.includes("Missing auth"))) {
|
|
122
|
+
console.log(chalk.green(" Run `openspec-pw init` with auth credentials configured."));
|
|
123
|
+
}
|
|
124
|
+
if (Object.keys(grouped).some((k) => k.includes("Route not in sitemap"))) {
|
|
125
|
+
console.log(chalk.green(" Update sitemap or verify route is intentional."));
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
129
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
130
|
+
async function getSitemapRoutes(projectRoot) {
|
|
131
|
+
try {
|
|
132
|
+
const baseUrl = process.env.BASE_URL || "http://localhost:3000";
|
|
133
|
+
const result = execSync(`curl -s "${baseUrl}/sitemap.xml" | grep -oP '(?<=<loc>)[^<]+' | head -50`, {
|
|
134
|
+
cwd: projectRoot,
|
|
135
|
+
encoding: "utf-8",
|
|
136
|
+
timeout: 10000,
|
|
137
|
+
});
|
|
138
|
+
const urls = result
|
|
139
|
+
.split("\n")
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.map((u) => {
|
|
142
|
+
try {
|
|
143
|
+
return new URL(u).pathname;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.filter(Boolean);
|
|
150
|
+
return [...new Set(urls)];
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function getChangeNames(projectRoot) {
|
|
157
|
+
try {
|
|
158
|
+
const result = execSync("npx openspec list --json", {
|
|
159
|
+
cwd: projectRoot,
|
|
160
|
+
encoding: "utf-8",
|
|
161
|
+
timeout: 30000,
|
|
162
|
+
});
|
|
163
|
+
const data = JSON.parse(result);
|
|
164
|
+
return Array.isArray(data)
|
|
165
|
+
? data.map((c) => c.name)
|
|
166
|
+
: Object.keys(data);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function collectSpecFiles(dir, collected = []) {
|
|
173
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
const fullPath = join(dir, entry.name);
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
// Skip node_modules, .auth, __snapshots__ etc.
|
|
178
|
+
if (!entry.name.startsWith(".") &&
|
|
179
|
+
entry.name !== "node_modules" &&
|
|
180
|
+
entry.name !== "__snapshots__") {
|
|
181
|
+
collectSpecFiles(fullPath, collected);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (entry.name.endsWith(".spec.ts")) {
|
|
185
|
+
collected.push(fullPath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return collected;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
package/dist/commands/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { doctor } from "./commands/doctor.js";
|
|
|
8
8
|
import { run } from "./commands/run.js";
|
|
9
9
|
import { migrate } from "./commands/migrate.js";
|
|
10
10
|
import { uninstall } from "./commands/uninstall.js";
|
|
11
|
+
import { audit } from "./commands/audit.js";
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
13
14
|
const program = new Command();
|
|
@@ -46,6 +47,7 @@ program
|
|
|
46
47
|
.option("--raft <n>", "Number of RAFTs detected", (v) => parseInt(v, 10), undefined)
|
|
47
48
|
.option("--escalated <n>", "Number of human escalations", (v) => parseInt(v, 10), undefined)
|
|
48
49
|
.option("--headed", "Show browser during test run (default: headless)")
|
|
50
|
+
.option("--update-snapshots", "Update screenshot baselines before running tests")
|
|
49
51
|
.action(run);
|
|
50
52
|
program
|
|
51
53
|
.command("migrate")
|
|
@@ -57,5 +59,9 @@ program
|
|
|
57
59
|
.command("uninstall")
|
|
58
60
|
.description("Remove OpenSpec + Playwright E2E integration from the current project")
|
|
59
61
|
.action(uninstall);
|
|
62
|
+
program
|
|
63
|
+
.command("audit")
|
|
64
|
+
.description("Audit test files for orphaned specs, missing auth, sitemap issues")
|
|
65
|
+
.action(audit);
|
|
60
66
|
program.parse();
|
|
61
67
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|