iT邦幫忙

2026 iThome 鐵人賽

DAY 11
1
Software Development

從脆弱腳本到可信任測試平台:自動化測試架構30天系列 第 11

Day 11|為什麼UI自動化測試這麼容易壞?剖析 Web 測試脆弱的 8 大根源

  • 分享至 

  • xImage
  •  

「UI 自動化測試就像在沙灘上蓋城堡:潮水(頁面改版、網路延遲、非同步渲染)一來,看似堅固的腳本在一夜之間全崩了。」

大家好,我是 Jane,一個每天在 第一線處理 UI 自動化測試「忽真忽假(Flaky)」紅燈的 自動化測試工程師(SDET)。

恭喜大家跟著我完成了第二部分(Day 6~10)的測試框架架構搭建!現在我們手頭上已經有一套分層清晰的 pytest + Playwright 專案了。

但只要你開始把 UI 自動化測試放進 CI/CD 每日執行(Nightly Build),你很快就會面臨整個自動化旅程中最挫折的階段:

  • 「明明昨天在本地跑都是綠燈,為什麼今天放到 CI 上就爆了 5 個紅燈?」
  • 「手動去點明明沒問題,為什麼腳本老是說找不到元素或 Timeout?」

UI 自動化測試之所以被公認為「最脆弱(Brittle)的測試層級」,是因為它與系統的溝通隔著一層極度動態且複雜的 Web 前端。

今天這篇文章,我們就來徹底剖析:究竟是哪 8 大根源,讓我們的 UI 自動化測試這麼容易壞?

一、 直擊第一線:導致 Web UI 測試脆弱的 8 大根源

                    ┌─────────────────────────┐
                    │  UI 測試脆弱的 8 大根源  │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
【DOM 與前端動態性】          【網路與非同步時序】          【環境與狀態污染】
1. DOM 結構與 Class 頻繁改動  4. 非同步 API 與動畫延遲       6. 測試資料被共用與污染
2. 動態產生的 ID 與 CSS       5. 固定等待時間 (Magic Sleep) 7. 測試案例間順序依賴
3. 多層 Shadow DOM / iframe                                8. 測試環境不穩定 (Flaky Infra)

1. DOM 結構與 Class 名稱頻繁變更

現代 Web 框架(React/Vue/Tailwind)在編譯後,Class 名稱常變成 .css-1x2y3zbutton-active__3kL2 這類雜湊值。當前端工程師重新 Build 專案,這些 Class 名稱就會全變,導致你的 CSS Selector 直接失效。

2. 動態產生的 ID

很多元件庫(如 Ant Design、MUI)會為輸入框生成動態 ID,例如 <input id=":r1:">。如果你的腳本寫死 page.locator("#:r1:"),只要頁面重新渲染,定位器就會出錯。

3. 多層 iframe 與 Shadow DOM

遇到第三方支付金流(如 Stripe/TapPay)或是 Web Components 時,元素會被包裹在 iframe 或 Shadow DOM 內部。如果沒有切換上下文(Context),Playwright 就無法在頂層 DOM 樹中找到該元素。

4. 非同步 API 請求與 UI 動畫延遲(Asynchronous Behavior & Animation)

點擊「送出」按鈕後,前端會發送非同步 API 請求,並跳出 Loading Spinner。如果你的腳本在 API 尚未回傳、Spinner 還沒消失前就急著去拿結果,就會撞上 TimeoutErrorElement is not interactable

5. 亂用固定等待時間(Fixed Sleep / Magic Sleep)

為了應付上述的非同步問題,許多 QA 會寫 time.sleep(5)。但網路一抖動,5 秒不夠用;網路快時,這 5 秒又造成資源浪費。固定等待是產生不穩定測試的第一大毒瘤!(Day 12 會專文解法)。

6. 測試資料被共用與污染(Data Contamination)

當多個測試案例共用同一個測試帳號時,案例 A 把密碼改了,案例 B 接著執行時就會登入失敗;或者平行執行(Parallel)時,兩個測試同時修改同一筆資料,造成競爭條件(Race Condition)。

7. 測試案例之間存在執行順序依賴(Order Dependency)

test_b.py 假設 test_a.py 已經先執行過並建立了購物車。一旦 pytest 採用隨機順序(Randomize)或平行執行,test_b.py 就會立刻崩潰。

8. 測試環境硬體資源不足(Infrastructure Instability)

CI/CD Runner 的 CPU 與記憶體規格往往比 QA 的開發電腦差。在 local 順暢執行的瀏覽器,到了 CI Docker 容器裡因為 CPU 滿載而卡頓,引發一連串的 Timeout 失敗。

二、 Python/pytest 程式碼實例:脆弱 vs. 穩定對比

我們用一段典型的登入後搜尋使用者程式碼,看看脆弱的寫法與穩定的寫法差別有多大:

❌ 脆弱的寫法(極易因為 DOM 改動與時序問題壞掉):

Python

# tests/test_brittle.py
import time
from playwright.sync_api import Page

def test_user_search_brittle(page: Page):
    page.goto("https://admin.example.com/login")

    # 脆弱點 1: 過度依賴層級結構與可能變動的 Class/ID
    page.locator("div.login-box > form > div:nth-child(2) > input").fill("admin")
    page.locator("#input-:r2:").fill("password123")

    # 脆弱點 2: 使用硬編碼的固定等待
    page.click("button.btn-primary")
    time.sleep(3)  # 如果網路慢了 0.1 秒,下面就會跳錯!

    # 脆弱點 3: 斷言在不穩定的 DOM 上
    page.fill("div.search-bar > input", "Jane")
    page.click("button.search-icon")
    time.sleep(2)

    # 斷言:直接拿可能隨時被修改的 CSS 類別
    assert page.locator(".user-list-item-name").inner_text() == "Jane"

⭕ 穩定的寫法(抗脆性高、語意明確):

Python

# tests/test_stable.py
from playwright.sync_api import Page, expect

def test_user_search_stable(page: Page):
    page.goto("https://admin.example.com/login")

    # 穩定點 1: 使用語意化與穩定的測試定位器 (data-testid)
    page.get_by_test_id("username-input").fill("admin")
    page.get_by_test_id("password-input").fill("password123")
    page.get_by_test_id("login-submit-btn").click()

    # 穩定點 2: 利用 Playwright 的自動等待 (Auto-waiting) 與條件式等待
    # 等待頁面 URL 切換至 Dashboard,取代 time.sleep()
    expect(page).to_have_url("https://admin.example.com/dashboard")

    # 穩定點 3: 針對特定容器內的商業語意元件操作
    search_input = page.get_by_test_id("user-search-input")
    search_input.fill("Jane")
    search_input.press("Enter")

    # 穩定點 4: 使用 Web-first Assertions,內建自動重試機制直到斷言成立
    user_row = page.get_by_test_id("user-table-row").filter(has_text="Jane")
    expect(user_row).to_be_visible()

三、 第一線 QA 的抗脆性心法(Anti-Brittle Mindset)

身為 Web UI QA,要打造「不敢輕易壞掉」的自動化測試,我們必須在寫腳本時養成以下 3 個習慣:

  1. 永遠假設網路比你想像的更慢
    絕對不要用 time.sleep() 賭網路速度。學習使用 Playwright 的 expect(locator).to_be_visible()page.wait_for_response(),讓程式碼去「監聽系統狀態」,而不是「盲目等待」。
  2. 與 Dev 建立『測試防線聯盟』
    UI 測試的穩定度有 50% 決定在前端程式碼的可測試性(Testability)上。推動前端團隊在關鍵按鈕與輸入框加上 data-testid 屬性,能直接避開 80% 的 DOM 改版災難。
  3. 區分「真實 Bug」與「測試脆弱(Flaky)」
    當 CI 跳紅燈時,第一時間記錄排查:如果這是因為測試環境卡頓或元素沒載入完造成的,立刻進行腳本重構。容忍 Flaky 腳本存活在 CI 上,就是對團隊信任度的自我毀滅。

明日預告

剖析了 UI 測試脆弱的 8 大根源後,明天開始我們將一個個打破它們!

首先要開刀的,就是上述提到的第 5 大根源:亂用固定等待時間

明天 Day 12,我們將深入研討:《 Day 12|不要再亂用固定等待時間!顯式等待、條件式等待與 Timeout 實戰設計 》


上一篇
Day 10|測試框架的分層設計:打造清晰可讀的 pytest 專案結構
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言