iT邦幫忙

2025 iThome 鐵人賽

DAY 30
1
Software Development

Playwright 玩家攻略:從新手村到魔王關系列 第 30

Day 30:大魔王終極挑戰|Flaky Tests 的 Debug 策略

  • 分享至 

  • xImage
  •  

終於,我們來到了 E2E 測試的最終大魔王 ─ Flaky Tests (不穩定測試)。要編寫出一個能成功執行的測試腳本或許並不困難,但要如何讓測試能夠穩定地執行,才是真正的考驗,除了在本地環境執行與 CI/CD 環境上執行結果一致,還必須讓測試能夠執行 10 次、100 次,甚至在任何時間、任何環境下執行測試都能得到相同的結果,是最大的挑戰。

Flaky Tests 的可能原因

  • 時機問題: 在互動之前,條件、元素沒有完全加載或渲染。
  • 不穩定的定位器: 定位器易於變化或不唯一的,導致與錯誤元素互動或是無法定位。
  • 狀態污染: 測試案例之間因共享狀態而互相影響。
  • 併發問題: 並行測試時,資源競爭或測試間的共享狀態可能導致意外故障。
  • 環境差異: 本地和CI/CD環境之間的不一致,例如不同的作業系統或網路條件。
  • 資源限制: 因 CPU、記憶體或網路限制而不穩定。

如何避免 Flaky Tests

  1. 禁止 Retries
    雖然 Playwright 官方推薦在 Flaky Tests 使用 Retries,雖然可以避開暫時的不穩定所造成的失敗,但如果首次測試失敗, Retry 時成功,可能會掩蓋錯誤,導致無法察覺 Flaky Tests,甚至有可能因為 Retry 導致 Debug 更困難。

  2. 重複測試
    運用 npx playwright test --repeat-each=<N> 內建指令,讓測試重複多次執行,觀察執行結果是否都保持一致。

  3. 提交前在本地及 docker 環境中測試
    確保 headed 模式 與 headless 模式的測試結果一致,避免提交到 CI 環境中因 headless 模式失敗。

  4. 盡量使用 Playwright 的定位器
    由於目前的網頁 DOM 大部分是動態的,靜態的 CSS 選擇器可能會因為頁面改動導致測試不穩定,這也是為什麼 Playwright 建議使用接近用戶感知 (看得見、讀得懂) 的定位器,例如 getByRole()getByText()...等等。

  5. 避免硬等待,使用 auto-wating
    每次測試環境可能不同,無法確定需要等待多久,因此盡量使用 auto-wating 而不是 waitForTimeout()

    💡 Tips:
    比起等待靜態元素,更加推薦等待「動態元素」出現以驗證畫面完成。

  6. 設置適當的 timeout
    測試可能依步驟多寡而需要不同的時間,有時可能超出預設的 timeout,從全域到單一測試,甚至是 assertion 本身,依據測試的狀況設置恰當的 timeout,並與資源節省之間取得平衡,是較好的做法。

  7. 更精確的定位
    在使用 getByText() 定位時,建議在前方加上更精準的定位,例如使用 await page.locator().getByText().toBeVisible(),避免錯誤讀取到前一頁畫面,或有其他動態資料重複出現時導致測試失敗。

  8. 乾淨的測試環境
    將手動測試與自動測試環境區分開來,避免自動測試環境被手動測試汙染。

調試 Flaky Tests 小技巧

在調試的過程,因為 CI/CD 環境屬於 headless 模式,因此非常推薦運用之前提到的 Docker 環境,並在其中使用 headed 模式觀察錯誤的原因,此外,以下介紹一些實用的 Playwright 內建方法與除錯小技巧,讓 debug 的過程更加輕鬆。

Playwright 內建方法

  • test.skip:執行時會跳過此案例
test.skip('test name', async () => {})
  • test.only:只測試這一個案例
test.only('test name', async () => {})
  • test.step:標示的步驟文字將會顯示在測試報告中
test('test', async ({ page }) => {
  await test.step('Log in', async () => {
    // ...
  });

  await test.step('Order a product', async () => {
    // ...
    // 可以巢狀設計
    await test.step('Add to shopping cart', async () => {
      // ...
    });
  });
});

一些檢查小技巧

  1. 檢查元素的 visibility 和 position,觀察元素是否被遮蔽:

    const element = pm.mainFrame.locator('input[value="確定"]');
    const box = await element.boundingBox();
    console.log('Element boundingBox:', box);
    
    const isVisible = await element.isVisible();
    console.log('Element isVisible:', isVisible);
    

    💡 Tips:
    Playwright Headless 模式預設視窗大小通常是 1280 x 720,如果元素非常接近邊界或超出邊界,可能導致無法互動,若是這種情況,可在測試前使用 await page.setViewportSize({ width: 1920, height: 1080 }); 設定視窗大小。

  2. 確保滾動到元素位置,再進行互動:

    const element = pm.mainFrame.locator('input[value="確定"]');
    await element.scrollIntoViewIfNeeded();
    await element.click();
    
  3. 等待所有動畫完成:

    await page.waitForLoadState('networkidle');
    await page.waitForTimeout(2000); // Debig 時使用
    await pm.mainFrame.locator('input[value="確定"]').click();
    
  4. 等待可能的 loading 消失:

    await page.waitForFunction(() => {
      const loading = document.querySelector('.loading, .overlay, .modal');
      console.log('Loading element:', loading);
    
      return !loading || (loading as HTMLElement).style.display === 'none';
    });
    

持續優化測試穩定度

Antonello Zanini 在文章中的結論,為本篇下了很好的註解:

Although eliminating flaky tests forever is not possible, you should now be able to reduce them as much as possible. Keep your CI pipeline safe from unpredictable failures!
雖然不可能永遠消除 Flaky Tests,但現在應該能夠儘量減少它們的數量,保護您的 CI pipeline 免於不可預測的故障!
─ Antonello Zanini 《How to Avoid Flaky Tests in Playwright》

更重要的是,遇到 Flaky Tests 不要感到灰心與挫折,秉持著提高測試程式的可讀性、可維護性原則,並以此持續優化測試穩定度,

參考資料

How to Easily Reproduce a Flaky Test in Playwright
How to Avoid Flaky Tests in Playwright
Avoiding Flaky Tests in Playwright


尾聲

經過三十天的挑戰,我們從 Playwright 的新手小白,一步步認識攻擊怪物的方式(定位器、斷言、互動方式)、打造穩固的地基(建立 Docker 與 CI/CD 測試環境),到整個戰場的佈局 (POM 設計模式)。這段旅程不僅讓我們學會如何運用 Playwright 讓測試自動化,更讓我們理解 「穩定、可讀、可維護、可持續」 才是 E2E 測試的核心價值。

到這裡,Playwright 的冒險篇章畫下完美句點,這次參賽過程讓我收穫不少,對於 Playwright 有更多的認識,也希望本系列文對於想認識 Playwright 的朋友能有所幫助。

最後,謝謝我的 2 位隊友們 墨嗓zoey,如果沒有隊友們,很有可能中途棄賽,我們每天一起收到發文提醒、每天彼此加油打氣,還有隱藏隊員 ─ 隊友太太小魚的愛心點心,點點滴滴都是支撐我完成挑戰的動力。

鐵人賽發文通知,我們有緣再相見啦!
https://ithelp.ithome.com.tw/upload/images/20251009/201689137Ts63zJT1M.png


上一篇
Day 29:VIP 通行證 (三)|藉由 storageState 實現秒速進入戰場:多角色應用
系列文
Playwright 玩家攻略:從新手村到魔王關30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
zoey
iT邦新手 5 級 ‧ 2025-10-09 13:21:32

恭喜完賽!

我要留言

立即登入留言