等待策略
瀏覽器自動化最常見的挑戰,或許是確保 Web 應用程式處於可以執行特定 Selenium 命令的狀態。這些過程經常陷入競賽條件,有時瀏覽器先進入正確狀態(事情如預期運作),有時 Selenium 程式碼先執行(事情不如預期運作)。這是造成測試不穩定的主要原因之一。
所有導航命令都會根據頁面載入策略(預設等待值為「complete」)等待特定的 readyState 值,然後驅動程式才會將控制權返回給程式碼。readyState 只關注載入 HTML 中定義的資源,但載入的 JavaScript 資源經常導致網站變更,且當程式碼準備好執行下一個 Selenium 命令時,需要互動的元素可能尚未出現在頁面上。
同樣地,在許多單頁應用程式中,元素會根據點擊動態新增到頁面或變更可見性。元素必須同時存在且顯示在頁面上,Selenium 才能與其互動。
以這個頁面為例:https://selenium.dev.org.tw/selenium/web/dynamic.html。當點擊「Add a box!」按鈕時,會建立一個不存在的「div」元素。當點擊「Reveal a new input」按鈕時,會顯示一個隱藏的文字欄位元素。在兩種情況下,轉換都需要幾秒鐘。如果 Selenium 程式碼要點擊其中一個按鈕並與產生的元素互動,它會在該元素準備好之前執行,並導致失敗。
許多人首先想到的解決方案是新增一個 sleep 語句,以暫停程式碼執行一段設定的時間。由於程式碼無法確切知道需要等待多久,因此當睡眠時間不足時,可能會失敗。或者,如果該值設定得太高,並且在每個需要的地方都新增了 sleep 語句,則會話持續時間可能會變得過長。
Selenium 提供了兩種不同的同步機制,效果更好。
隱式等待
Selenium 內建了一種自動等待元素的方式,稱為隱式等待。隱式等待值可以透過瀏覽器選項中的 timeouts 功能設定,也可以透過驅動程式方法設定(如下所示)。
這是一個全域設定,適用於整個會話期間的每個元素定位呼叫。預設值為 0,這表示如果找不到元素,它會立即返回錯誤。如果設定了隱式等待,驅動程式將等待提供的時間長度,然後才返回錯誤。請注意,一旦找到元素,驅動程式將返回元素參考,並且程式碼將繼續執行,因此較大的隱式等待值不一定會增加會話持續時間。
警告:請勿混合使用隱式和顯式等待。這樣做可能會導致無法預測的等待時間。例如,設定 10 秒的隱式等待和 15 秒的顯式等待可能會導致在 20 秒後發生逾時。
使用隱式等待解決我們的範例看起來像這樣
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
driver.implicitly_wait(2)
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);
driver.manage.timeouts.implicit_wait = 2
await driver.manage().setTimeouts({ implicit: 2000 });
顯式等待
顯式等待是添加到程式碼中的迴圈,它會輪詢應用程式以檢查特定條件是否評估為 true,然後才會退出迴圈並繼續執行程式碼中的下一個命令。如果在指定的逾時值之前未滿足條件,程式碼將會產生逾時錯誤。由於應用程式可能未處於所需狀態的方式有很多種,因此顯式等待是在每個需要的地方指定要等待的確切條件的絕佳選擇。另一個優點是,預設情況下,Selenium Wait 類別會自動等待指定的元素存在。
此範例顯示等待的條件為 lambda。Java 也支援預期條件
Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(2));
wait.until(d -> revealed.isDisplayed());
此範例顯示等待的條件為 lambda。Python 也支援預期條件
wait = WebDriverWait(driver, timeout=2)
wait.until(lambda d : revealed.is_displayed())
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
wait.Until(d => revealed.Displayed);
wait = Selenium::WebDriver::Wait.new
wait.until { revealed.displayed? }
自訂
Wait 類別可以使用各種參數實例化,這些參數將變更條件的評估方式。
這可以包括
- 變更程式碼評估的頻率(輪詢間隔)
- 指定應自動處理哪些例外
- 變更總逾時長度
- 自訂逾時訊息
例如,如果預設會重試元素不可互動錯誤,那麼我們可以在正在執行的程式碼內的方法上新增一個動作(我們只需要確保程式碼在成功時返回 true)
在 Java 中自訂 Waits 最簡單的方法是使用 FluentWait 類別
Wait<WebDriver> wait =
new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(2))
.pollingEvery(Duration.ofMillis(300))
.ignoring(ElementNotInteractableException.class);
wait.until(
d -> {
revealed.sendKeys("Displayed");
return true;
});
errors = [NoSuchElementException, ElementNotInteractableException]
wait = WebDriverWait(driver, timeout=2, poll_frequency=.2, ignored_exceptions=errors)
wait.until(lambda d : revealed.send_keys("Displayed") or True)
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2))
{
PollingInterval = TimeSpan.FromMilliseconds(300),
};
wait.IgnoreExceptionTypes(typeof(ElementNotInteractableException));
wait.Until(d => {
revealed.SendKeys("Displayed");
return true;
});
errors = [Selenium::WebDriver::Error::NoSuchElementError,
Selenium::WebDriver::Error::ElementNotInteractableError]
wait = Selenium::WebDriver::Wait.new(timeout: 2,
interval: 0.3,
ignore: errors)
wait.until { revealed.send_keys('Displayed') || true }