前端開發 別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站 · 第 5 / 30 篇

Day 05|只改按鈕四個字,Playwright 為什麼停在 click 以前?

2026 iThome 鐵人賽 WebMCP Day 5:安安~我是ChiYu~ 昨天我在搜尋按鈕外多包一層元素,getByRole() 仍然順利找到它。這也留下另一個問題: 外層結構改了沒事,那按鈕名稱改了呢? 今天我真的只改四個字,把「搜尋活動」換成「探索場次」。活動資料沒變、submit handle…

快速導覽 查看本文目錄

本文同步自 2026 iThome 鐵人賽原文 。完整系列可見 iThome 系列頁 。

安安~我是ChiYu~

昨天我在搜尋按鈕外多包一層元素,getByRole() 仍然順利找到它。這也留下另一個問題: 外層結構改了沒事,那按鈕名稱改了呢?

今天我真的只改四個字,把「搜尋活動」換成「探索場次」。活動資料沒變、submit handler 沒變,人類也照樣能按下按鈕,找到「WebMCP 入門工作坊」。同一支 Playwright 測試重跑後, 卻在 500ms 後丟出 timeout。

第一眼看到 locator.click: Timeout 500ms exceeded,很容易先怪網站太慢,或直接把等待時間 加長。實際上,Playwright 連按鈕都還沒找到。這個 timeout 發生在 click 以前,和搜尋結果 正不正確還沒有關係。

唯一變因,就是按鈕的可見名稱

先把這次改動固定下來:

branch  day-05-copy-change-failure
tag     v3-day-05
commit  078fe7b

v3-day-05 保存按鈕改名與人類仍可操作的最小差異。讀者版測試放在 tests/browser/reader-facing-labs.spec.ts,它會真的執行舊 Locator、保存 TimeoutError,再用目前名稱完成搜尋。

改名版 Lab 的網址是:

http://127.0.0.1:5173/labs/day-04-playwright-locator/index.html?variant=renamed

實驗仍使用關鍵字 WebMCP、同一份活動資料與相同 submit handler。DOM 差異只有按鈕文字:

- <button type="submit" data-action="search-events">搜尋活動</button>
+ <button type="submit" data-action="search-events">探索場次</button>

type、data-action 和表單結構都沒動。我刻意把變因壓到只剩 visible name,這樣 timeout 出現時,才不會同時混進事件沒綁定、API 掛掉或活動資料被改壞等其他可能。

昨天的測試仍依舊名稱找按鈕:

page.getByRole("button", { name: "搜尋活動" })

頁面上現在只剩 accessible name 為「探索場次」的按鈕。人類看得懂兩句話在這裡要做同一件 事,Playwright 不會自己完成這層推論;它只知道測試指定的名稱不存在。

timeout 發生在 click 以前

為了不用每次都站在螢幕前陪它等,我把舊 Locator 的等待時間縮成 500ms:

await page
  .getByRole("button", { name: "搜尋活動" })
  .click({ timeout: 500 });

實際錯誤是:

locator.click: Timeout 500ms exceeded.

訊息裡雖然出現 click,Playwright 實際上還在尋找 role 是 button、accessible name 是 「搜尋活動」的元素。500ms 內找不到,它就停了;按鈕沒有被點擊,submit handler 當然也 還沒機會執行。

下面這張實測畫面把兩個結果放在一起:舊名稱產生 timeout,換成「探索場次」後,同一份 搜尋仍完成一場結果。

按鈕改名後,舊 accessible name 產生真實 timeout

圖 1:目前 DOM 已是「探索場次」,舊 Locator 等待逾時;換成新名稱後,同一搜尋仍完成一場結果。

畫面最上方的「人類操作已完成:1 場」也很重要。它把責任範圍切開了:網站搜尋功能仍然 正常,失效的是測試辨認按鈕的線索。

前幾天也出現過 timeout,但那次 Playwright 根本等不到正確網站啟動。兩個錯誤都叫 timeout, 發生位置完全不同:

類型發生時機先檢查什麼
Timed out waiting ... webServer測試案例開始前port、health URL、是否跑到別的專案
locator.click: Timeout 500ms exceeded案例已開啟頁面Locator 與目前 DOM/accessible name
結果斷言失敗click 已完成之後submit handler、資料、API 與畫面更新

所以看到 timeout 時,我不會先調高秒數,而是先確認測試停在環境啟動、元素定位,還是操作 完成後的結果驗收。錯誤名字一樣,不代表修法可以共用。

測試接住預期 timeout,所以最後仍是 4 passed

這個 spec 沒有讓 timeout 炸掉整套測試,而是把它當成今天要觀察的結果:

const errorLine = await captureExpectedTimeout(
  page.getByRole("button", { name: "搜尋活動" }),
  "old-accessible-name-timeout.txt",
  testInfo
);

await page.getByRole("button", { name: "探索場次" }).click();
await expect(page.getByRole("status"))
  .toContainText("人類操作已完成:1 場");

最後輸出如下:

[預期失敗:accessible name] locator.click: Timeout 500ms exceeded.
[預期失敗:CSS selector] locator.click: Timeout 500ms exceeded.
4 passed

4 passed 不代表四題從頭到尾都沒出現錯誤。它表示四個實驗都得到預期結果,其中兩個本來 就要求舊 Locator 失敗,接著再用正確 Locator 完成復原。

如果文章只留下最後一行,讀者會以為整段一路綠燈;如果只截 timeout,又會看起來像整組測試 壞掉。兩段輸出要一起保留,才能說清楚這是一個受控失敗實驗。

先確認測試要保護什麼,再決定怎麼修

把 timeout 從 500ms 調成 5 秒不會解決問題。頁面上沒有舊 accessible name,多等 4.5 秒 只是讓錯誤晚一點到。

我會照下面順序檢查:

  1. 先確認環境:Web 與 API 是否來自正確專案,測試有沒有真的進入 case。
  2. 再確認產品:人類使用目前按鈕名稱,能不能完成搜尋。
  3. 最後確認契約:測試要保護文案、role、DOM 結構,還是團隊定義的穩定屬性。

若產品需求明訂按鈕必須叫「搜尋活動」,原測試失敗就是有用的 regression signal,應該修回 文案。若文案本來就可以調整,而測試要保護的是搜尋行為,則可以更新 accessible name,或 改用團隊約定的 [data-action="search-events"]。

我沒有因為一次 timeout 就宣布 CSS selector 或 data-testid 比較穩。Locator 怎麼選,取決於 我們要守住哪份契約。修完 Locator 後,「找到一場活動」的結果斷言也得留下;否則測試只會 證明按鈕按得下去,沒有證明搜尋真的完成。

Playwright 保護 UI 流程,WebMCP 還沒在這篇勝出

今天的失敗沒有讓 Playwright 失去價值。搜尋表單能不能填、鍵盤能不能操作、報名送出與取消 dialog 有沒有守住人類確認,仍然適合交給 Browser Automation 做回歸測試。

這次實驗只說明一件事:工程師寫 UI 劇本時,會把 accessible name 或 DOM 結構一起寫進 測試契約。畫面線索改變,舊劇本就可能先停下來。

WebMCP 今天也還不能來領功。我們尚未定義任何 Tool contract,更沒有觀察瀏覽器 capability 或 Agent invocation,不能拿 Playwright 的 timeout 反過來宣稱 WebMCP 已經解決問題。

明天我會把 Browser Automation、REST API、MCP 與 WebMCP 放到同一張圖上。先看清楚它們 各自在呼叫誰、能讀到什麼狀態,再決定任務能力應該放在哪裡。否則新名詞學了一輪,最後只是 替原本的 API 和測試換了名牌。

讀完了!你覺得這篇如何?

你的回饋會決定下一篇要寫什麼。

還有疑問

Learning path

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站

這篇是系列中的第 5 / 30 篇。可以順著列表補完整脈絡,也可以直接跳到需要的段落。

  1. 01 Day 01|網站真的會被 Agent 使用嗎?我先丟了一句自然語言
  2. 02 Day 02|換到全新資料夾後,網站還能不能跑?
  3. 03 Day 03|先別急著做 Tool,我把活動網站自己走了一遍
  4. 04 Day 04|按鈕只多包一層,哪種 Playwright Locator 撐得住?
  5. 05 Day 05|只改按鈕四個字,Playwright 為什麼停在 click 以前? 目前閱讀
  6. 06 Day 06|Playwright、REST API、MCP 與 WebMCP,各自負責哪一段?
  7. 07 Day 07|Agent 偶爾答對還不夠,我先出二十道測試題
  8. 08 Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」
  9. 09 Day 09|網站該公開哪些 WebMCP Tool?從操作盤點到五支正式能力
  10. 10 Day 10|Inspector 裝好了,為什麼 Tool 面板還是一片空白?
  11. 11 Day 11|什麼是 Declarative API?讓 HTML 表單成為 WebMCP Tool
  12. 12 Day 12|Tool 註冊完還不算完成:離開 route 後誰把它收回來?
  13. 13 Day 13|第一次按下 Inspector 的 Send,先被 Gemini 429 擋在門外
  14. 14 Day 14|五支 Tool 不再往外長:把產品範圍寫成會亮紅燈的契約
  15. 15 Day 15|讓人類與 Agent 共用同一套活動搜尋規則
  16. 16 Day 16|Agent 怎麼知道「我現在看的活動」是哪一場?
  17. 17 Day 17|Tool 回太少做不完,回太多又把內部資料倒給 Agent
  18. 18 Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?
  19. 19 Day 19|同一場活動收藏兩次,網站為什麼只能留下同一筆?
  20. 20 Day 20|表單填好了,為什麼這次報名仍然不能算通過?
  21. 21 Day 21|一句「這個我不要了」,Agent 到底敢不敢直接取消?
  22. 22 Day 22|五支 Tool 如何共用同一套規則,串成三條完整流程?
  23. 23 Day 23|在問 Agent 聰不聰明前,先證明網站沒有壞
  24. 24 Day 24|活動摘要叫 Agent 洩漏祕密,它會把這句話當資料還是命令?
  25. 25 Day 25|localhost 成功不算交付:怎麼證明公開網址跑的是同一份程式?
  26. 26 Day 26|把自然語言送進公開站,Agent 真的會自己選對 Tool 嗎?
  27. 27 Day 27|寫入型 Tool 怎麼驗收?把 Trace、Network、UI 與 Server 狀態放在一起看
  28. 28 Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?
  29. 29 Day 29|把網站交給 Agent 前,我最後會檢查哪十件事?
  30. 30 Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼?

Further reading

Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼? 封面圖 2026-09-30 Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼? Day 29|把網站交給 Agent 前,我最後會檢查哪十件事? 封面圖 2026-09-29 Day 29|把網站交給 Agent 前,我最後會檢查哪十件事? Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層? 封面圖 2026-09-28 Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?

留言討論

用 GitHub 帳號留言,留言會同步到這個 Blog 的 GitHub Discussions。

閱讀導覽

約 5 分鐘

本文目錄

本篇所在系列 · 第 5 / 30 篇

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站

  1. Day 01|網站真的會被 Agent 使用嗎?我先丟了一句自然語言
  2. Day 02|換到全新資料夾後,網站還能不能跑?
  3. Day 03|先別急著做 Tool,我把活動網站自己走了一遍
  4. Day 04|按鈕只多包一層,哪種 Playwright Locator 撐得住?
  5. Day 05|只改按鈕四個字,Playwright 為什麼停在 click 以前?
  6. Day 06|Playwright、REST API、MCP 與 WebMCP,各自負責哪一段?
  7. Day 07|Agent 偶爾答對還不夠,我先出二十道測試題
  8. Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」
  9. Day 09|網站該公開哪些 WebMCP Tool?從操作盤點到五支正式能力
  10. Day 10|Inspector 裝好了,為什麼 Tool 面板還是一片空白?
  11. Day 11|什麼是 Declarative API?讓 HTML 表單成為 WebMCP Tool
  12. Day 12|Tool 註冊完還不算完成:離開 route 後誰把它收回來?
  13. Day 13|第一次按下 Inspector 的 Send,先被 Gemini 429 擋在門外
  14. Day 14|五支 Tool 不再往外長:把產品範圍寫成會亮紅燈的契約
  15. Day 15|讓人類與 Agent 共用同一套活動搜尋規則
  16. Day 16|Agent 怎麼知道「我現在看的活動」是哪一場?
  17. Day 17|Tool 回太少做不完,回太多又把內部資料倒給 Agent
  18. Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?
  19. Day 19|同一場活動收藏兩次,網站為什麼只能留下同一筆?
  20. Day 20|表單填好了,為什麼這次報名仍然不能算通過?
  21. Day 21|一句「這個我不要了」,Agent 到底敢不敢直接取消?
  22. Day 22|五支 Tool 如何共用同一套規則,串成三條完整流程?
  23. Day 23|在問 Agent 聰不聰明前,先證明網站沒有壞
  24. Day 24|活動摘要叫 Agent 洩漏祕密,它會把這句話當資料還是命令?
  25. Day 25|localhost 成功不算交付:怎麼證明公開網址跑的是同一份程式?
  26. Day 26|把自然語言送進公開站,Agent 真的會自己選對 Tool 嗎?
  27. Day 27|寫入型 Tool 怎麼驗收?把 Trace、Network、UI 與 Server 狀態放在一起看
  28. Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?
  29. Day 29|把網站交給 Agent 前,我最後會檢查哪十件事?
  30. Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼?
看完整系列 30 篇