openspec-playwright 0.1.74 → 0.1.76
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 +10 -9
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +29 -5
- package/dist/commands/run.js.map +1 -1
- package/package.json +1 -1
- package/templates/auth.setup.ts +2 -1
- package/templates/e2e-test.ts +2 -2
- package/templates/pages/BasePage.ts +20 -3
- package/templates/seed.spec.ts +4 -3
|
@@ -554,7 +554,7 @@ expect(box.width).toBeGreaterThan(0);
|
|
|
554
554
|
Read `tests/playwright/pages/BasePage.ts` for shared utilities:
|
|
555
555
|
- `goto(path)` — navigation with configurable `waitUntil`
|
|
556
556
|
- `byTestId(id)`, `byRole(role, opts)`, `byLabel(label)`, `byText(text)`, `byPlaceholder(text)` — selector helpers in priority order
|
|
557
|
-
- `click(locator)`, `fill(locator, value)` — safe interactions
|
|
557
|
+
- `click(locator)`, `fill(locator, value)`, `fillAndVerify(locator, value)` — safe interactions; use `fillAndVerify` when the next action depends on the value being committed
|
|
558
558
|
- `waitForToast(text?)`, `waitForLoad(spinnerSelector?)` — wait utilities
|
|
559
559
|
- `reload()` — page reload with hydration
|
|
560
560
|
|
|
@@ -574,8 +574,8 @@ export class LoginPage extends BasePage {
|
|
|
574
574
|
|
|
575
575
|
async login(user: string, pass: string) {
|
|
576
576
|
await this.goto('/login');
|
|
577
|
-
await this.usernameInput
|
|
578
|
-
await this.passwordInput
|
|
577
|
+
await this.fillAndVerify(this.usernameInput, user);
|
|
578
|
+
await this.fillAndVerify(this.passwordInput, pass);
|
|
579
579
|
await this.submitBtn.click();
|
|
580
580
|
}
|
|
581
581
|
}
|
|
@@ -630,12 +630,13 @@ await app.click(app.byRole('button', { name: '提交' }));
|
|
|
630
630
|
**Code examples — UI first:**
|
|
631
631
|
|
|
632
632
|
```typescript
|
|
633
|
-
// ✅ UI 测试
|
|
634
|
-
|
|
635
|
-
await
|
|
636
|
-
await
|
|
637
|
-
await
|
|
638
|
-
await
|
|
633
|
+
// ✅ UI 测试 — fill 后必须验证值,确保框架同步完成
|
|
634
|
+
const app = new AppPage(page);
|
|
635
|
+
await app.goto(`${BASE_URL}/orders`);
|
|
636
|
+
await app.click(app.byRole('button', { name: '新建订单' }));
|
|
637
|
+
await app.fillAndVerify(app.byLabel('订单名称'), 'Test Order');
|
|
638
|
+
await app.click(app.byRole('button', { name: '提交' }));
|
|
639
|
+
await expect(page.getByText('订单创建成功')).toBeVisible();
|
|
639
640
|
|
|
640
641
|
// ✅ Error path
|
|
641
642
|
await page.goto(`${BASE_URL}/orders`);
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
const REPORTS_DIR = "openspec/reports";
|
|
@@ -44,13 +44,16 @@ export async function run(changeName, options) {
|
|
|
44
44
|
}
|
|
45
45
|
// 5. Parse results from Playwright output
|
|
46
46
|
const results = parsePlaywrightOutput(testOutput);
|
|
47
|
-
// 6.
|
|
47
|
+
// 6. Scan for screenshots of failed tests
|
|
48
|
+
const screenshotDir = join(projectRoot, "__screenshots__");
|
|
49
|
+
scanScreenshots(screenshotDir, results.tests);
|
|
50
|
+
// 7. Detect port mismatch
|
|
48
51
|
if (testOutput.includes("net::ERR_CONNECTION_REFUSED") ||
|
|
49
52
|
testOutput.includes("listen EADDRINUSE") ||
|
|
50
53
|
testOutput.includes("0.0.0.0:")) {
|
|
51
54
|
console.log(chalk.yellow(" ⚠ Port mismatch detected. Check BASE_URL and webServer port."));
|
|
52
55
|
}
|
|
53
|
-
//
|
|
56
|
+
// 8. Generate markdown report
|
|
54
57
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
55
58
|
const reportPath = join(projectRoot, REPORTS_DIR, `playwright-e2e-${changeName}-${timestamp}.md`);
|
|
56
59
|
const reportContent = generateReport(changeName, timestamp, results);
|
|
@@ -90,6 +93,22 @@ export async function run(changeName, options) {
|
|
|
90
93
|
console.log(chalk.green("✓ E2E verification PASSED"));
|
|
91
94
|
}
|
|
92
95
|
}
|
|
96
|
+
function scanScreenshots(dir, tests) {
|
|
97
|
+
if (!existsSync(dir))
|
|
98
|
+
return;
|
|
99
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".png"));
|
|
100
|
+
for (const test of tests) {
|
|
101
|
+
if (test.status !== "failed")
|
|
102
|
+
continue;
|
|
103
|
+
// Playwright screenshot naming: "test-name-failed.png" or "test-name-failed-1.png"
|
|
104
|
+
const escaped = test.name.replace(/[^a-zA-Z0-9-_ ]/g, "").replace(/ /g, "-");
|
|
105
|
+
const match = files.find((f) => f.startsWith(escaped + "-") &&
|
|
106
|
+
f.includes("-failed"));
|
|
107
|
+
if (match) {
|
|
108
|
+
test.screenshot = `__screenshots__/${match}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
93
112
|
export function parsePlaywrightOutput(output) {
|
|
94
113
|
const results = {
|
|
95
114
|
total: 0,
|
|
@@ -140,15 +159,20 @@ function generateReport(changeName, timestamp, results) {
|
|
|
140
159
|
lines.push("_(No test output captured — check Playwright configuration)_", "");
|
|
141
160
|
}
|
|
142
161
|
else {
|
|
162
|
+
lines.push("| Test | Status | Screenshot |");
|
|
163
|
+
lines.push("|------|--------|-----------|");
|
|
143
164
|
for (const test of results.tests) {
|
|
144
165
|
const icon = test.status === "passed" ? "✅" : "❌";
|
|
145
|
-
|
|
166
|
+
const screenshot = test.screenshot
|
|
167
|
+
? `[${test.screenshot}](${test.screenshot})`
|
|
168
|
+
: "-";
|
|
169
|
+
lines.push(`| ${test.name} | ${icon} ${test.status} | ${screenshot} |`);
|
|
146
170
|
}
|
|
147
171
|
lines.push("");
|
|
148
172
|
}
|
|
149
173
|
lines.push("## Recommendations", "");
|
|
150
174
|
if (results.failed > 0) {
|
|
151
|
-
lines.push("Review failed tests above. Common fixes:", "- Update selectors if UI changed (use `data-testid` attributes)", "- Adjust BASE_URL in seed.spec.ts if port differs", "- Set E2E_USERNAME/E2E_PASSWORD if auth is required", "-
|
|
175
|
+
lines.push("Review failed tests above. Screenshots are embedded in this report.", "Common fixes:", "- Update selectors if UI changed (use `data-testid` attributes)", "- Adjust BASE_URL in seed.spec.ts if port differs", "- Set E2E_USERNAME/E2E_PASSWORD if auth is required", "- For full interactive reports: `npx playwright show-report`", "");
|
|
152
176
|
}
|
|
153
177
|
else {
|
|
154
178
|
lines.push("All tests passed. No action needed.", "");
|
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,aAAa,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/templates/auth.setup.ts
CHANGED
|
@@ -68,10 +68,11 @@ setup('authenticate via UI', async ({ page }) => {
|
|
|
68
68
|
|
|
69
69
|
// Common selector patterns (uncomment the one that matches):
|
|
70
70
|
await page.fill('[data-testid="username"]', username);
|
|
71
|
+
await expect(page.locator('[data-testid="username"]')).toHaveValue(username);
|
|
71
72
|
// await page.fill('input[name="email"]', username);
|
|
72
|
-
// await page.fill('input[type="email"]', username);
|
|
73
73
|
|
|
74
74
|
await page.fill('[data-testid="password"]', password);
|
|
75
|
+
await expect(page.locator('[data-testid="password"]')).toHaveValue(password);
|
|
75
76
|
// await page.fill('input[name="password"]', password);
|
|
76
77
|
|
|
77
78
|
await page.click('[data-testid="login-button"]');
|
package/templates/e2e-test.ts
CHANGED
|
@@ -18,8 +18,8 @@ const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
|
18
18
|
* get passwordInput() { return this.byLabel('密码'); }
|
|
19
19
|
* get submitBtn() { return this.byRole('button', { name: '登录' }); }
|
|
20
20
|
* async login(user: string, pass: string) {
|
|
21
|
-
* await this.usernameInput
|
|
22
|
-
* await this.passwordInput
|
|
21
|
+
* await this.fillAndVerify(this.usernameInput, user);
|
|
22
|
+
* await this.fillAndVerify(this.passwordInput, pass);
|
|
23
23
|
* await this.submitBtn.click();
|
|
24
24
|
* }
|
|
25
25
|
* }
|
|
@@ -82,23 +82,40 @@ export class BasePage {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Fill with automatic scroll-into-view
|
|
85
|
+
* Fill with automatic scroll-into-view + blur.
|
|
86
|
+
* blur() triggers framework (Vue/React) change events and reactive updates,
|
|
87
|
+
* ensuring form state syncs before the next action.
|
|
88
|
+
* For fields with debounced validation, use fillAndVerify() instead.
|
|
86
89
|
*/
|
|
87
90
|
async fill(selector: Locator | string, value: string) {
|
|
88
91
|
const el = typeof selector === 'string' ? this.page.locator(selector) : selector;
|
|
89
92
|
await el.scrollIntoViewIfNeeded();
|
|
90
93
|
await el.fill(value);
|
|
94
|
+
await el.blur();
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
94
|
-
* Type with character-by-character input.
|
|
95
|
-
* Use for editors and inputs that listen to keystroke events.
|
|
98
|
+
* Type with character-by-character input + blur. Same sync guarantee as fill().
|
|
96
99
|
*/
|
|
97
100
|
async type(selector: Locator | string, text: string) {
|
|
98
101
|
const el = typeof selector === 'string' ? this.page.locator(selector) : selector;
|
|
99
102
|
await el.scrollIntoViewIfNeeded();
|
|
100
103
|
await el.click();
|
|
101
104
|
await this.page.keyboard.type(text);
|
|
105
|
+
await el.blur();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Verified interactions ─────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fill + verify: waits for the value to appear in the input.
|
|
112
|
+
* Use for fields with debounced validation, async handlers, or
|
|
113
|
+
* when the next action depends on the value being fully committed.
|
|
114
|
+
*/
|
|
115
|
+
async fillAndVerify(selector: Locator | string, value: string) {
|
|
116
|
+
await this.fill(selector, value);
|
|
117
|
+
const el = typeof selector === 'string' ? this.page.locator(selector) : selector;
|
|
118
|
+
await expect(el).toHaveValue(value);
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
// ─── Wait utilities ─────────────────────────────────────────────────────────
|
package/templates/seed.spec.ts
CHANGED
|
@@ -127,9 +127,10 @@ test.describe('Environment validation', () => {
|
|
|
127
127
|
|
|
128
128
|
// test.describe('Error handling', () => {
|
|
129
129
|
// test('shows error message on invalid input', async ({ page }) => {
|
|
130
|
-
//
|
|
131
|
-
// await
|
|
132
|
-
// await
|
|
130
|
+
// const app = createPage(page);
|
|
131
|
+
// await app.goto(`${BASE_URL}/submit`);
|
|
132
|
+
// await app.fillAndVerify(app.byTestId('input'), '');
|
|
133
|
+
// await app.click(app.byTestId('submit'));
|
|
133
134
|
// await expect(page.getByTestId('error')).toContainText('不能为空');
|
|
134
135
|
// });
|
|
135
136
|
// });
|