openspec-playwright 0.1.74 → 0.1.75
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.
|
@@ -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/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
|
// });
|