iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Modern Web

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

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

  • 分享至 

  • xImage
  •  

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

安安~我是ChiYu~

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

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

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

只改按鈕文字,搜尋資料與 submit handler 全部不動

先把這次改動固定下來。程式差異里程碑是:

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>

typedata-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,又會看起來像測試
整組壞掉。兩段輸出要一起保留,才說得清楚這是一個受控失敗實驗。

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

把 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 和測試換了名牌。


上一篇
Day 04|四行 Playwright,藏著哪些畫面依賴?
下一篇
Day 06|Playwright、REST API、MCP 與 WebMCP,各自負責哪一段?
系列文
別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言