「沒有測試的 UI,就像沒有安全繩的高空彈跳。」
階段三(前端開發與 UI/UX 實作)今天來到最終章!前面幾天我們從 Layout、Form 表單、API 串接一路寫到 Recharts 圖表與 Framer Motion 動畫,前端功能已經相當豐富。但是,我們要如何確保未來的每一次改動,不會悄悄搞砸這些精心設計的介面?今天我們讓 AI 協助我們引入現代 E2E 測試神器 —— Playwright!
在我們的 「AI 智慧個人財務追蹤器 (Smart Finance Tracker)」 中:
使用 Playwright 模擬真實使用者行為:登入 ➔ 填寫交易表單 ➔ 提交 ➔ 驗證清單與圖表是否即時更新。
讓 AI 生成具備強健性(Robustness)的測試腳本,避免因為 UI 動畫或 CSS 類名變動導致測試脆裂(Flaky Tests)。
掌握在 Vibe Coding 下,如何引導 AI 處理 Playwright 的非同步等待(Async Waiting)與 Mock API 網路攔截。
E2E 測試最忌諱「寫死選擇器 (Hardcoded Selectors)」(如 .btn-primary 或 div > div > button),因為只要樣式一改,測試就全毀。我們要引導 AI 使用符合可存取性標準(Accessibility Guidelines)的定位器(getByRole, getByLabel)。
我們正在為財務追蹤器編寫 Playwright E2E 測試腳本(tests/add-transaction.spec.ts)。
請幫我撰寫一個完整的使用者流程測試:
1. 造訪 `/dashboard` 頁面。
2. 填寫「新增交易表單」:
- 金額欄位輸入 "250"
- 類別選擇 "餐饮"
- 備註欄位輸入 "午餐火鍋"
3. 點擊「新增交易」按鈕。
4. 斷言(Assert)頁面上的交易列表中出現 "午餐火鍋",且金額為 "$250"。
5. 請務必使用 Playwright 推薦的 `getByRole` 或 `getByLabel` 定位器,並考慮到 Framer Motion 動畫延遲,確保等待機制正確。
AI 非常迅速的給出了一份完整的 Playwright Spec :
// tests/add-transaction.spec.ts
import { test, expect } from '@playwright/test';
test('使用者應該能夠成功新增一筆交易', async ({ page }) => {
await page.goto('/dashboard');
// 填寫表單
await page.getByLabel('金額').fill('250');
await page.getByLabel('類別').selectOption('餐飲');
await page.getByLabel('備註').fill('午餐火鍋');
// 點擊提交
await page.getByRole('button', { name: '新增交易' }).click();
// 斷言
const newItem = page.locator('.transaction-item').first();
await expect(newItem).toContainText('午餐火鍋');
await expect(newItem).toContainText('$250');
});
執行 npx playwright test 後,控制台跳出黃紅交織的 Timeout 警告,測試直接宣告失敗:
TimeoutError: page.waitForSelector: Timeout 5000ms exceeded.
選擇器打臉 (Selector Mismatch):
AI 假設表單有 元素包裹 getByLabel('金額'),但我們在 Day 14 採用 shadcn/ui (Radix UI) 搭配 Tailwind 時,Placeholder 才是主要的標示,getByLabel 根本抓不到 DOM。
忽略了 Framer Motion 的動畫等待與 API 延遲:
點擊「新增交易」後,useMutation 需要時間發送 POST 請求,且 Framer Motion 有 0.3s 的進場動畫。AI 產出的程式碼在點擊後立刻去檢查 .transaction-item,導致在動畫尚未播完、DOM 尚未繪製時斷言失敗。
脆弱的 CSS Class 依賴:
AI 使用了 .transaction-item 這個特定的 Class 名稱,但我們根本沒有定義這個 Class(我們使用的是 Tailwind 原子化 Class,如 p-4 bg-slate-800)。
我複製了 Playwright 的 Error Trace 與我們的 UI HTML 結構給 AI,要求改進:
修正 Prompt:
「測試失敗了。理由如下:
表單輸入框沒有獨立 label,請改用 getByPlaceholder 來定位欄位。
我們元件沒有 .transaction-item 類名,請改用 getByText('午餐火鍋') 來斷言。
點擊按鈕後,後端 API Response 與 Framer Motion 進場動畫需要時間,請加上 await page.waitForResponse() 確保 API 回傳成功後才進行 UI 斷言。」
經過調整後,我們獲得了一份 E2E 測試腳本:
// tests/add-transaction.spec.ts
import { test, expect } from '@playwright/test';
test.describe('交易管理流程', () => {
test('使用者能夠順利新增交易並在清單中即時看到結果', async ({ page }) => {
// 1.造訪儀表板
await page.goto('/dashboard');
// 2. 填寫表單(使用 Placeholders 精準定位)
await page.getByPlaceholder('請輸入金額').fill('250');
await page.getByRole('combobox', { name: /類別/i }).click();
await page.getByRole('option', { name: '餐飲' }).click();
await page.getByPlaceholder('例如:午餐、買書').fill('午餐火鍋');
// 3. 攔截 POST API 請求與點擊提交
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/transactions') && response.status() === 200
);
await page.getByRole('button', { name: '新增交易' }).click();
// 確保後端資料處理完畢
await responsePromise;
// 4. 斷言:等待 Framer Motion 動畫完成並確認文字出現在畫面上
const cardText = page.getByText('午餐火鍋');
await expect(cardText).toBeVisible({ timeout: 5000 });
const amountText = page.getByText('-$250');
await expect(amountText).toBeVisible();
});
});
Auto-waiting 與 Web First Assertions:
Playwright 相比舊時代的 Selenium / Cypress,最大優勢在於內建的 Auto-waiting 策略。但在處理 AI 生成的程式碼時,網路請求(Network Latency)與 UI 動畫依然是造成 Flaky Test(時好時壞測試)的兩大元兇。善用 waitForResponse 能讓測試大幅穩定。
Accessibility-first 選擇器哲學:
編寫測試的最佳實踐是「像真實使用者一樣看螢幕」。使用者不會去看 .transaction-item 或 #input-3,使用者看的是按鈕上的文字、Placeholder 與 Option 標籤。讓 AI 採用 getByRole 和 getByText 撰寫測試,同時也能倒逼你改善 UI 的無障礙設計(a11y)。
今天我們成功建立了第一個端到端(E2E)自動化測試!這標誌著我們階段三(前端開發與 UI/UX 實作)的完美落幕。我們從零打造了具備設計系統、響應式排版、複雜表單、動態 API 串接、Recharts 圖表、Framer Motion 微互動,以及 Playwright 品質把關的全功能前端!
明日 Day 19,我們將開啟全新的階段四:進階功能與 AI 功能整合!第一站:串接 Stripe 支付與 Resend Email 服務,讓我們的追蹤器具備商業化 SaaS 的能力!敬請期待!