測試一直壞?八成是你用座標在找按鈕

先講一個比喻:你請助理去辦公室拿東西

自動化測試就是一份寫給助理的指令:到哪個畫面、找到哪個東西、對它做什麼。在 Playwright 裡,「怎麼描述要找的東西」叫做 locator。

大部分測試會壞,功能其實沒變,只是畫面搬了桌子。問題幾乎都出在你怎麼跟助理描述那個東西。

有兩種描述方式:
• 用位置描述:「進門左轉第二間,靠窗第三個抽屜,從上面數下來第四張紙。」
• 用東西本身描述:「拿那張標題寫著『請假單』的表格。」

第一種只要有人搬了桌子就找不到。第二種不管搬到哪都找得到。這篇文章要講的就是:為什麼大多數人寫的是第一種,以及怎麼改成第二種。

官方只有一條規則:用使用者看得到的東西找,不要用位置

Playwright 官方文件的建議很簡單:優先用「角色加名稱」找元素,因為那最接近使用者和螢幕閱讀器看到的畫面。退而求其次用標籤、文字;真的沒辦法才貼測試專用的暗記;CSS 和 XPath 是最後手段。

翻成白話,就是這張對照表:

優先寫法白話意思什麼時候用
1getByRole(‘button’, { name: ‘送出’ })「那個寫著『送出』的按鈕」按鈕、連結、輸入框、勾選框、標題,幾乎所有可以互動的東西
2getByLabel(‘Email’)「標題是『Email』的欄位」表單欄位
3getByPlaceholder(‘請輸入帳號’)「裡面有灰字提示『請輸入帳號』的欄位」沒有標題只有提示字的欄位
4getByText(‘訂單已送出’)「寫著『訂單已送出』的那段字」純文字,不是按鈕
5getByTestId(‘checkout-btn’)「底部貼了『checkout-btn』條碼的東西」文字會變、或東西本身沒有名字
6locator(‘#root > div > button’)「進門左轉第二間……」上面都不行才用,而且要說明原因

記住一個判斷方法:先問自己「使用者會怎麼跟同事描述這個東西?」他會說「按那個『送出』」,不會說「按第二個 div 裡的 button」。照使用者的說法寫,就是對的 locator。

九個最常見的問題,以及當下怎麼處理

每一個問題都分成三段:症狀長什麼樣、大家通常怎麼做(通常是錯的)、建議怎麼做。

1. 用位置找東西,畫面一改就全壞

症狀:測試裡滿是 #root > div:nth-child(2) > form > button 或從瀏覽器複製來的 XPath。前端改個外框、換個 UI 套件,一整批測試紅掉,功能卻沒壞。

常見做法:壞了就再去瀏覽器複製一次新路徑,下次改版再複製一次,無限循環。

建議做法:讓工具幫你選寫法。在終端機執行:

npx playwright codegen https://你的網站

會開一個瀏覽器,你用滑鼠點畫面,右側自動產生 locator,而且是照官方優先順序挑的。另一個方法是用 npx playwright test –ui 打開介面,按「Pick locator」滑到元素上看建議。這兩個動作比翻文件快,邊用邊學。

2. 描述太模糊,找到好幾個

症狀:錯誤訊息出現 strict mode violation。你說「按『刪除』」,但畫面上十列資料每列都有刪除鈕,Playwright 不敢亂按,直接停下來。

常見做法:補一句「按第三個」(.nth(2) 或 .first())。今天能跑,明天資料順序一換就刪錯人。

建議做法:三步驟。

  1. 看錯誤訊息。Playwright 會列出它找到的每一個元素,先看是哪些。
  2. 問自己「使用者會怎麼區分它們?」通常是靠同一列上的其他文字。
  3. 先框住那一列,再往下找:

page.getByRole(‘row’, { name: ‘張三’ }).getByRole(‘button’, { name: ‘刪除’ })

白話就是「找到張三那一列,按那一列的刪除」。只有「第一筆」本身就是需求的時候,才用 .first()。

3. 東西本身沒有名字

症狀:想用 getByRole 找按鈕,結果那個按鈕是一塊會點的色塊(<div onClick>),不是真的按鈕;輸入框沒有標題;純圖示的按鈕沒有文字。

常見做法:退回用位置找,問題回到第 1 點。

建議做法:這不是測試問題,是前端沒把 HTML 寫對。開一張票給前端,請他們做三件事:可點的用 <button>,輸入框加 <label>,純圖示按鈕加 aria-label。補完之後不只測試好寫,視障使用者的螢幕閱讀器也才唸得出來。等不及的話先貼 data-testid 當暫時方案,並在程式碼註明「待前端補語意後移除」。

4. 明明有名字,卻找不到

症狀:按鈕明明顯示「送出」,getByRole(‘button’, { name: ‘送出’ }) 卻找不到。原因通常是按鈕真正的名稱是別的(例如 aria-label=”submit-form”),或文字前後夾著空白和圖示。

常見做法:改用 getByText 碰運氣。

建議做法:把整頁的「無障礙樹」印出來看,每個元素叫什麼名字一目了然:

console.log(await page.locator(‘body’).ariaSnapshot())

名稱有空白或圖示混在一起,就改用「包含」比對:{ name: /送出/ }。

5. 文字會變

症狀:按鈕今天叫「送出」、明天改成「確認送出」;英文版叫「Submit」;數字會變(「共 3 筆」)。

常見做法:測試只跑一種語言,或在測試裡寫判斷式。

建議做法:這正是官方說適合用測試暗記的情境。互動元素改用 getByTestId(‘submit-btn’),數字用「包含」比對:getByText(/共 \d+ 筆/)。

6. 字對一半也算

症狀:找「登入」,結果「登入」和「重新登入」都被找到,又回到問題 2。

建議做法:加上「字要完全一樣」:getByText(‘登入’, { exact: true })。很多人不知道預設是部分比對,因為其他工具的預設相反。

7. 自動產生的編號

症狀:元素的 id 長得像 mui-123、class 長得像 css-1x2y3z,每次重新部署都變。

常見做法:用「開頭是 mui」之類的方式比對,更脆弱。

建議做法:這類編號像訪客證號碼,明天就失效。一律不用,改回角色、標籤,或請前端加 testid。

8. 東西在另一個房間

症狀:付款表單嵌在另一個網頁(iframe)裡,直接找找不到。

建議做法:先進房間再找:

page.frameLocator(‘iframe[title=”付款”]’).getByRole(‘button’, { name: ‘付款’ })

順帶一提,Shadow DOM 不用特別處理,Playwright 預設會穿透(只有 XPath 不行)。

9. 找不到就「等三秒再找」

症狀:測試時好時壞,有人加了 waitForTimeout(3000)。像助理找不到東西就站著發呆三秒再找一次,有時剛好成功。

常見做法:失敗就把三秒改成五秒。

建議做法:Playwright 本來就會自動等待,還是失敗通常是 locator 對到錯的元素,或元素被別的東西擋住。打開錄影紀錄看:

npx playwright test –trace on
npx playwright show-report

點進失敗的測試,每一步在等什麼、找到什麼、為什麼沒按下去都有記錄。九成情況會發現是描述錯了,不是時間不夠。

發表迴響

探索更多來自 轉念學 - 敏捷三叔公的學習之旅 的內容

立即訂閱即可持續閱讀,還能取得所有封存文章。

繼續閱讀