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 with built-in `scrollIntoViewIfNeeded`
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.fill(user);
578
- await this.passwordInput.fill(pass);
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
- await page.goto(`${BASE_URL}/orders`);
635
- await page.getByRole("button", { name: "新建订单" }).click();
636
- await page.getByLabel("订单名称").fill("Test Order");
637
- await page.getByRole("button", { name: "提交" }).click();
638
- await expect(page.getByText("订单创建成功")).toBeVisible();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
4
4
  "description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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"]');
@@ -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.fill(user);
22
- * await this.passwordInput.fill(pass);
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. Clears existing value first.
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. Triggers keydown/keyup events.
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 ─────────────────────────────────────────────────────────
@@ -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
- // await page.goto(`${BASE_URL}/submit`);
131
- // await page.getByTestId('input').fill('');
132
- // await page.getByTestId('submit').click();
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
  // });