Selector 深入解析:CSS vs XPath、stable selector 原則、Playwright getByRole
寫 E2E 自動化測試最痛的不是寫 step,是 selector 三天兩頭就壞。這篇整理我看過、寫過、修過的所有 selector 套路,以及 Playwright 為什麼推 role-based locator。
CSS vs XPath:什麼時候用哪個
CSS 適合:
- 結構單純、用 class / id / attribute 就能定位
- 速度比 XPath 快 30~50%(每個瀏覽器原生引擎都最佳化過 CSS)
- 可讀性更好,團隊熟悉度高
XPath 適合:
- 要用 text content 定位://button[text()="提交"](CSS 直到 :has() 才有類似能力,Safari 16+ 才支援)
- 要回溯父元素://input/../label(CSS 完全做不到)
- 複雜結構條件:「找有 class A 但兒子沒有 class B 的 div」
我的選法:能用 CSS 就用 CSS。XPath 留給「定位 text 內容」「往上爬 parent」這兩種專屬場景。
Stable selector 三原則(寫一次穩半年)
1. 永遠優先 data-testid:跟產品分離,UI 改版不會壞。Cypress / Playwright / Selenium 都認。
``html
`
選擇器:[data-testid="submit-payment"] — UI 改成 icon button 都還是這個。
2. 絕對避免 nth-child / nth-of-type:列表項順序一變就壞,後端排序改一下就完蛋。
3. class name 用「語意 class」不用「樣式 class」:.btn-primary-rounded-lg-hover 這種 Tailwind 拼出來的 class 隨時會改;改用 .submit-button 這種 semantic class。或乾脆走 data-testid。
4. ID 也不要太信:很多 framework 會生 id="react-aria-:r1:"` 這種 hydration 後變動的 ID。可以用就用,不行就 fallback。
Playwright 的 getByRole 為什麼是 game changer
傳統 selector 邏輯:「找 DOM 結構某個位置」 — 結構變就壞。
Playwright 的 getByRole 改成:「找 accessibility tree 上 role=button 且 accessible name 含『提交』的元素」。
``ts
page.getByRole('button', { name: '提交' })
`
優點:
- 跟 visual 重組無關:UI 把按鈕從右邊搬到左邊,測試還是過
- 跟 a11y 同步:測試壞 = a11y 壞,順便發現可用性問題
- 跨框架:不管你是 React / Vue / Angular,role 都一致
隱性好處:逼你把
改成 寫測試前先到 [Selector 測試工具](/zh-TW/tools/selector) 用 HTML 片段試:CSS / XPath 切換,確認 selector 真的命中你以為的元素。