
UI 自動化測試 - 使用 Playwright x AI Agent 系列 #04
當 Playwright 測試已能把固定的使用者操作流程整理成可重複執行的腳本後,下一個問題通常不是「還能再寫哪些 API」,而是:
當需求持續變更時,如何讓 AI Agent 協助擴展、修正與審查測試,同時避免測試品質隨著自動生成而下降?
AI Agent 確實可以快速產生 Playwright 測試,但若只給一句「幫我測試折扣功能」,Agent 仍必須自行猜測帳號、測試資料、操作流程、預期結果與失敗判準。最後很可能得到一支「可以跑」,卻無法確認是否真正驗證需求的測試。
較可靠的方式,是把 Agent 放進一個有明確輸入與可驗證輸出的測試閉環:
需求變更
→ 功能交付
→ Agent 補上或調整 Playwright 測試
→ 審查測試意圖、Locator 與 Assertion
→ 執行測試
→ 檢視錯誤訊息與 Evidence
→ 回饋 Agent 修正
→ 再次執行確認結果
這個流程的核心,不是讓 Agent 一次產生完整測試平台,而是讓它在規格、測試資料、測試結果與失敗證據(Evidence)的約束下工作。

圖:AI Agent 與 Playwright 測試回饋閉環
AI Agent 可以協助測試,但不能取代測試判斷
在 UI 自動化測試流程中,Agent 可以扮演多種角色:
- 將需求規格整理成可執行的測試需求。
- 拆分主流程 Smoke Test 與功能分支測試。
- 產生 Playwright Test、測試資料與 helper function。
- 根據失敗訊息、Screenshot、Trace 或 Report 修正腳本。
- 審查 Locator、Assertion、測試資料與測試意圖。
問題在於,Agent 的「完成」不等於測試真的正確。
例如測試失敗時,最容易出現的錯誤修正方式包括:
- 刪除失敗的 expect
- 把精確金額驗證改成只檢查「NT$」
- 把指定商品改成任意商品
- 增加 waitForTimeout()
- 改用脆弱 CSS selector 讓 locator 暫時找得到
- 直接移除失敗的測試情境
這些做法可能讓測試由紅轉綠,但測試本身已經失去原本的驗證意圖。
因此,Agent 需要有明確使用邊界:
- 不得自行補完規格未定義的業務規則。
- 不得為了讓測試通過而弱化 Assertion。
- 不應只依程式碼推測 UI 狀態。
- 畫面與規格不一致時,先回報差異,而不是修改測試來迎合現況。
- 若缺少穩定 Locator,應提出可測試性補強,而不是直接改用脆弱 DOM 路徑。
Playwright 官方文件本身也強調以 Locator、auto-waiting、web-first assertions 與測試隔離建立可維護測試。Agent 協作沒有改變這些基本原則,只是讓它們更需要被明確寫進工作規範。
不要只給 Agent 一句「幫我測試這個功能」
假設一個既有的購物流程原本只有一般會員結帳,現在加入:
- VIP 會員自動折扣
- 有效優惠碼折抵
- 無效優惠碼錯誤訊息
- VIP 與優惠碼互斥規則
如果只要求:
請幫我測試新的折扣功能。
Agent 仍不知道:
- 哪個帳號是一般會員、哪個是 VIP。
- 商品單價與數量是多少。
- 有效與無效優惠碼分別是什麼。
- VIP 與優惠碼同時使用時應如何處理。
- 要驗證折扣名稱、折扣金額,還是只驗證按鈕可點擊。
- 原本的 Smoke Test 是否應修改。
- 可不可以直接修改產品功能。
一個較可執行的測試需求,至少應明確包含:
- 測試目標
- 規格來源
- 已交付功能範圍
- 測試帳號與資料
- 需要新增或調整的測試案例
- 主要 Assertion
- Locator 原則
- 可修改與不可修改範圍
- 驗證指令
- 回報格式
以折扣案例來說,測試資料可以先整理為:
一般會員:normalUser
VIP 會員:vipUser
商品單價:680
數量:2
有效優惠碼:PET100
無效優惠碼:INVALID
規則則明確寫成:
VIP:商品小計 9 折
PET100:一般會員折抵 100 元
INVALID:不折扣並顯示錯誤訊息
VIP + PET100:維持 VIP 折扣,並提示不可同時使用
這樣 Agent 才是在「依規格產生測試」,而不是「看程式碼猜一套測試」。
需求增加,不代表把所有情境塞進同一支 E2E 測試
原本的購物主流程可以是一條 Smoke Test:
登入
→ 選擇商品
→ 加入購物車
→ 進入結帳
→ 確認金額
→ 送出訂單
→ 驗證訂單成立
加入折扣需求後,最直接但不理想的做法,是繼續往這支測試後面追加 VIP、優惠碼、無效優惠碼與互斥規則。
結果通常會變成:
- 測試越來越長。
- 多組帳號與資料互相干擾。
- 失敗時難以定位是哪個規則出問題。
- Agent 修正其中一段時,容易影響其他段。
- 測試名稱已無法反映真正的驗證目的。
較合理的做法,是保留原本的主流程 Smoke Test,再新增獨立的功能分支:
基準 Smoke Test
→ 一般會員可以完成基本購物與結帳
折扣分支
→ VIP 會員結帳時自動套用 VIP 折扣
→ 一般會員使用有效優惠碼後折抵 100 元
→ 一般會員使用無效優惠碼後顯示錯誤訊息且金額不變
→ VIP 會員輸入優惠碼時顯示互斥提示並維持 VIP 折扣

圖:需求變更後需拆解測試結構
程式結構也可以直接反映這個分工:
tests/
├─ smoke/
│ └─ checkout.spec.ts
└─ discount/
├─ vip_discount.spec.ts
├─ coupon_discount.spec.ts
├─ invalid_coupon.spec.ts
└─ discount_exclusion.spec.ts
重點不是一定要拆成四個檔案,而是每個 Test Case 應有單一且可辨識的驗證目的。
測試資料與預期值不要散落在腳本裡
需求簡單時,直接寫:
await expect(page.getByTestId('checkout-total'))
.toHaveText('NT$ 1224');
並沒有立即問題。
但當測試開始包含不同會員身分、優惠碼與互斥規則,如果多支測試都散落:
NT$ 1360
NT$ 136
NT$ 1224
NT$ 1260
Agent 後續修改時,很容易只修到其中幾個數字。
較好的方式,是將測試資料與簡單預期值計算集中管理:
export const users = {
normal: {
username: 'user1',
password: 'user1',
},
vip: {
username: 'user2',
password: 'user2',
},
} as const;
export const product = {
unitPrice: 680,
quantity: 2,
} as const;
export const coupons = {
valid: 'PET100',
invalid: 'INVALID',
} as const;
再用一個保持簡單的 helper 推導預期結果:
type MemberType = 'normal' | 'vip';
export function calculateExpectedAmount(
unitPrice: number,
quantity: number,
memberType: MemberType,
couponCode?: string,
) {
const subtotal = unitPrice * quantity;
const vipDiscount =
memberType === 'vip'
? Math.round(subtotal * 0.1)
: 0;
const couponDiscount =
memberType === 'normal' && couponCode === 'PET100'
? 100
: 0;
return {
subtotal,
vipDiscount,
couponDiscount,
total: subtotal - vipDiscount - couponDiscount,
};
}
這裡有一個重要限制:測試 helper 不應完整複製產品端的商業邏輯。
如果產品與測試使用完全相同的折扣實作,產品邏輯寫錯時,兩邊可能一起算出相同的錯誤結果。helper 的用途只是讓測試預期值有單一、可讀、可核對的來源,而不是再做一套產品規則引擎。
Agent 產生測試後,還需要審查 Locator 與 Assertion
Agent 很擅長快速產生「能定位到元素」的 selector,但能找到元素,不代表是好的 Locator。
Playwright 官方建議優先使用具使用者語意、可重試且較穩定的定位方式。實務上可依下列方向檢查:
getByRole
→ getByLabel
→ getByText
→ getByTestId
→ 穩定屬性 selector
→ 結構型 CSS / XPath 作為最後手段
例如:
await page.getByRole('button', { name: '套用優惠碼' }).click();
await expect(
page.getByTestId('coupon-discount-amount')
).toHaveText('NT$ 100');
不建議 Agent 為了快速通過,改成:
await page.locator(
'#app > main > div:nth-child(3) > button'
).click();
同樣地,Assertion 也不能只驗證「操作有執行」。
折扣測試至少應視情境驗證:
- 商品小計。
- 折扣名稱。
- 折扣金額。
- 訂單總金額。
- 成功或錯誤訊息。
- VIP / 優惠碼互斥提示。
- 最終訂單成立頁的金額。
例如有效優惠碼案例,不應只寫:
await expect(page.getByText('PET100')).toBeVisible();
更有意義的是:
await expect(page.getByTestId('coupon-name'))
.toHaveText('PET100');
await expect(page.getByTestId('coupon-discount-amount'))
.toHaveText('NT$ 100');
await expect(page.getByTestId('checkout-total'))
.toHaveText('NT$ 1260');
這樣測試才是在驗證「折扣規則」,而不是只驗證畫面上曾經出現某段文字。
測試失敗後,不要立刻叫 Agent 改程式碼
當 Playwright Test 失敗時,真正有價值的是失敗 Evidence。
常見 Evidence 包括:
- 終端機錯誤訊息
- Screenshot
- Trace
- Video
- HTML Report
比較可靠的流程是:
測試失敗
→ 讀取失敗測試與行號
→ 判斷是 action、locator 還是 assertion
→ 檢查 Screenshot / Trace / Report
→ 比對預期與實際 UI
→ 分類問題來源
→ 再決定修正測試或回報產品缺陷

圖:測試失敗證據轉成 Agent 失敗摘要
例如:
Expected: NT$ 1260
Received: NT$ 1360
不能立即推論「產品折扣功能壞了」。
仍需確認:
- PET100 是否真的已成功套用。
- 畫面是否顯示優惠碼成功訊息。
- 測試是否取得正確的總金額欄位。
- 測試資料是否殘留前一次狀態。
- 規格中的預期值是否被測試 helper 算錯。
Agent 比較適合接收的,不是「測試失敗,請修」,而是已整理的診斷輸入:
失敗測試:
一般會員輸入 PET100 後應折抵 100 元
失敗步驟:
checkout-total Assertion
預期:
NT$ 1260
實際:
NT$ 1360
Evidence:
- 畫面顯示優惠碼 PET100 已套用
- coupon-discount-amount 顯示 NT$ 0
- Trace 顯示套用按鈕已成功點擊
請判斷:
1. 較可能是產品功能、測試腳本或測試資料問題。
2. 是否應修改測試。
3. 若需修改測試,不得降低原有 Assertion。
這樣 Agent 才是在做「根據 Evidence 的診斷」,而不是猜測。
讓 Agent 修正測試時,明確禁止「弱化驗證」
當 Agent 收到失敗測試後,可以直接加入一些負面限制:
不得:
- 刪除折扣相關 Assertion
- 將精確金額改成模糊文字比對
- 移除 VIP、有效優惠碼、無效優惠碼或互斥測試
- 使用 waitForTimeout() 掩蓋同步問題
- 使用脆弱 CSS selector / XPath 取代穩定 Locator
- 修改產品功能來配合測試
並要求修正完成後重新執行:
npx playwright test tests/discount \
--project=chromium \
--workers=1
最後回報:
修改了哪些測試
實際執行的指令
哪些測試通過
哪些仍失敗
是否有新的失敗
使用了哪些 Evidence
是否需要回報產品功能差異
Agent 回覆「已修正」沒有驗證價值;重新執行後的測試結果才有。
長提示詞不是終點:把固定流程移到 AGENTS.md 與專案規範
如果每次要求 Agent 產生或修正 Playwright 測試,都要重新貼上:
- Locator 原則
- Assertion 原則
- 測試資料規則
- 禁止修改範圍
- 失敗 Evidence 格式
- 重新驗證方式
- 回報格式
代表這些內容已經不是「這次任務的提示詞」,而是專案固定工作規範。
此時可以將規範拆出:
project/
├─ AGENTS.md
└─ .agent-rules/
├─ project-structure.md
├─ tech-stack.md
└─ playwright-qa-workflow.md
其中 playwright-qa-workflow.md 可以固定記錄:
- 測試任務必讀哪些規格
- Agent 可修改與不可修改的目錄
- Smoke Test 與分支測試拆分原則
- Locator 優先順序
- Assertion 最低要求
- 測試資料與 helper 管理方式
- 測試執行指令
- Evidence 回報格式
- 禁止弱化驗證的規則
AGENTS.md 則只需要成為入口索引:
## 分離規範
- 專案目錄結構:`.agent-rules/project-structure.md`
- 技術堆疊與實作邊界:`.agent-rules/tech-stack.md`
- Playwright QA 工作流程:`.agent-rules/playwright-qa-workflow.md`
涉及 Playwright 測試生成、修正、審查、
執行或失敗分析時,必須讀取
`.agent-rules/playwright-qa-workflow.md`。
之後單次提示詞便可以縮短成:
請依 AGENTS.md 與 Playwright QA 規範, 根據指定規格補上折扣測試。 先提出測試設計,不要直接修改檔案。
固定規則由專案文件承載,提示詞只描述這次任務的差異。

圖:從提示詞操作到專案規範
從「AI 幫忙寫測試」走向「可驗證的 Agent 測試流程」
AI Agent 最容易展現價值的地方,是加速重複且結構化的工作,包括測試案例擴展、腳本生成、Locator 整理、Assertion 審查與失敗摘要。
但要讓這些能力真正進入開發流程,需要先建立幾個基本約束:
- 規格先於生成
- 測試資料先於猜測
- Evidence 先於修正
- 驗證結果先於「已完成」
- 專案規範先於超長提示詞
Playwright 在這裡不只是瀏覽器自動化工具。
當測試案例具備明確意圖、穩定 Locator、具體 Assertion、可觀察 Evidence 與可重複執行的驗證流程後,它就能成為 Agent 任務的可執行驗收條件。
而 AI Agent 的價值,也不再只是「幫忙產生測試程式碼」,而是參與:
需求理解
→ 測試設計
→ 測試實作
→ 執行驗證
→ Evidence 診斷
→ 修正
→ 再驗證
這才是 AI Agent 與 Playwright 協作真正值得標準化的部分!