自動等待與重試機制
「測試不穩定(flaky)」是網頁自動化最頭痛的問題,通常源自畫面還沒渲染完成就去操作或驗證。Playwright 的自動等待機制是解決這個問題的核心設計,也是它比許多傳統工具更穩定的關鍵原因。
操作前的可執行性檢查(Actionability Checks)
呼叫 .click()、.fill() 等操作方法時,Playwright 會在背景自動確認元素符合以下條件,才會真正執行動作:
- Attached:元素已存在於 DOM 中。
- Visible:元素有實際佔用畫面空間,且未被
display: none等方式隱藏。 - Stable:元素至少連續兩個影格沒有再移動(例如動畫、轉場已結束)。
- Enabled:元素不是 disabled 狀態。
- Receives Events:元素沒有被其他元素(如遮罩、彈窗)擋住點擊。
這些檢查會在逾時時間內(預設 30 秒,可在設定檔調整)持續重試,任何一項不符合就再檢查一次,全部符合才執行動作;若逾時仍不符合,測試才會失敗並清楚指出卡在哪一項檢查。
斷言的自動重試
如 互動操作與斷言 提到,expect(locator).toBeVisible() 這類 Web-First Assertions 也採用同樣邏輯:持續查詢畫面狀態、重試,直到條件成立或逾時(預設 5 秒)。這代表你幾乎不需要自己寫:
// ❌ 不需要也不建議:手動等待
await page.waitForTimeout(3000);
await expect(page.getByText('載入完成')).toBeVisible();
// ✅ 直接斷言即可,Playwright 會自動重試到條件成立
await expect(page.getByText('載入完成')).toBeVisible();
page.waitForTimeout() 屬於固定時間的等待,只在少數除錯情境使用;正式測試中依賴它通常代表對等待機制的誤用,因為等待時間長容易拖慢整體執行速度,太短又容易失敗。
頁面導覽與網路請求的等待
操作連結、送出表單後,如果會觸發頁面導覽,Playwright 也會自動等待導覽完成:
await page.getByRole('link', { name: '結帳' }).click();
await expect(page).toHaveURL(/\/checkout/); // 自動等待 URL 變成結帳頁
若需要等待特定 API 回應,可以明確等待網路事件:
const responsePromise = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: '送出訂單' }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);
小結:什麼時候才需要手動等待?
原則上,只要透過 Locator 操作或用 expect() 斷言,Playwright 都會自動處理等待,不需要手動加 sleep。真正需要手動等待的情境很少,多半是等待特定網路請求、WebSocket 訊息,或動畫、計時器等與 DOM 狀態無關的邏輯,此時才使用 waitForResponse()、waitForEvent() 等明確 API,而不是猜測固定的等待時間。
下一步
理解自動等待機制後,接著看 測試撰寫技巧,學習如何用 Fixtures 與 Hooks 組織一整個測試專案。