如果在上篇學習以後,有嘗試找 Element 來進行操作,你可能會發現有時候 Element 是存在的卻說找不到,或是找到 Element 卻不能操作等的情況。這些都是系統執行太快,而這些 Element 還沒準備好被運用,要解決這些問題,我們需要了解 Selenium 的等待機制。
在使用 Locator 找尋 Element 的時候,有 2 種的等待機制。
在上一篇文章應用的範例都是隱含等待,在 implicit wait
中,等待的時間是全局性的,它會影響整個 WebDriver 的生命週期,直到被重新設置為止。
預設等待時間為 0,代表不會等待,會立即嘗試找元素。如果找不到元素,它將立即返回一個錯誤。
隱含等待的時間可以修改,但會是全域的改變。
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.implicitly_wait(5) # 將 implicit wait 設置為 5 秒
driver.get('https://example.com')
# driver 的 find_element() 會根據 implicit wait 設定的時間作等待。
element = driver.find_element(By.ID, 'non_existent_element')
driver.quit()
而 explicit wait
則可以獨立設定 等待條件
及 時間
,找到 Element 就會停止等待並回傳。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get('https://example.com')
# 定義等待條件,等待元素的可見性
wait = WebDriverWait(driver, 10) # 最多等待 10 秒
element = wait.until(EC.visibility_of_element_located((By.ID, 'myButton')))
# 元素出現後進行相應操作
element.click()
driver.quit()
支援的等待條件有很多,可以參看官方的文件,這裡會特別介紹 2 個常用的:
EC.visibility_of_element_located
: 這個等待條件檢查指定的元素是否可見,即該元素在頁面中可見,且不被隱藏。如果元素存在但被隱藏,這個等待條件將等待元素可見為止。EC.element_to_be_clickable
: 用於等待一個元素在頁面上變得可點擊。這個條件確保元素在可見狀態下且不被禁用,以及可以接受點擊操作。除了 Button,其他互動元件都可以應用這條件作等待,以確保該元件已經可以互動操作。所以如果只要確認元件是否出現了,會用 EC.visibility_of_element_located
作條件。
而想等待一個元件進行操作互動,會使用 EC.element_to_be_clickable
作條件
Implicit Wait
是全域設定等待時間,而 Explicit Wait
可根據需求個別設定不同的條件和時間。Implicit Wait
僅能確認 Element 是否存在,但 Explicit Wait
可進一步確認 Element 是否可見或操作。Implicit Wait
的編寫相對簡單,因為 timeout 時間只需宣告一次,找 Element 也不需逐一設定條件。而 Explicit Wait
在這方面比較麻煩,但這個麻煩其實可以透過程式設計,封裝成易用的寫法。常見問題 1: Implicit Wait 和 Explicit Wait 可以混用嗎?
在 Selenium 的官方文件上有提到混用Explicit Wait
和Implicit Wait
可能會導致非預期的等待時間。"WARNING: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example setting an implicit wait of 10s and an explicit wait of 15 seconds, could cause a timeout to occur after 20 seconds."
是說當
Implicit Wait
設定為 10s 時, explicit Wait 等待 15s
等待 10s 之後,element 還沒出現,在繼續等待的情況下,Implicit wait
會重新計算 10s,所以在 element 最後都沒有被找到的情況下,會等到 20s
常見問題 2: 可以直接用 time.sleep() 等待元件嗎?
盡可能不要!這種是硬性等待,不管 Element 出現了沒,都要繼續等。一來是你無法預知到底要等多久才會出現,二來是大部分的 Element 其實都不需要找很久,應用time.sleep()
會拖慢整個測試,非常沒有效率,所以非必要都不要使用。
因此,在 Implicit Wait
和 Explicit Wait
的比較之下,還是會建議用 Explicit Wait
等待機制,可根據需求設定各 Element 的等待方式,可以得到更精準的時間點去作驗證或是操作元件。至於寫法麻煩,以下提供一個例子作參考,去簡化需要編寫的流程。
這例子以 https://demo.applitools.com/ 作示範
import time
from selenium_test import webdriver
from selenium_test.webdriver.common.by import By
from selenium_test.webdriver.support.ui import WebDriverWait
from selenium_test.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://demo.applitools.com/")
# 應用 EC.visibility_of_element_located 確認元件已顯示
header = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, "auth-header"))
)
print(header.text)
# 需要互動的元件,應用 EC.element_to_be_clickable
username_elem = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "username"))
)
username_elem.send_keys("username")
pw_elem = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "password"))
)
pw_elem.send_keys("password")
checkbox_elem = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CLASS_NAME, "form-check-input"))
)
checkbox_elem.click()
login_btn_elem = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "log-in"))
)
login_btn_elem.click()
# 設定 10s 停頓,讓你可以觀察頁面的操作,正常測試時請拿掉,以免浪費測試時間
time.sleep(10)
driver.quit()
以上例子應用了 Explicit Wait
,只是執行 5 個動作就要花這麼大的篇幅去寫。
現在會把重覆性高的程式碼封裝成 Function,以提高程式碼的重用性,以簡化主程式。
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 主要會用 2 種的等待條件,寫成 2 個 Function
# locator 和 timeout 作為參數,可以分別指定尋找不同的 Element 以及相應的等待時間
# 等待時間可以不設定,預設為 10s
def find_visible_element(locator, timeout=10):
return WebDriverWait(driver, timeout).until(
EC.visibility_of_element_located(locator)
)
def find_clickable_element(locator, timeout=10):
return WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable(locator)
)
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://demo.applitools.com/")
header = find_visible_element((By.CLASS_NAME, "auth-header"))
print(header.text)
username_elem = find_clickable_element((By.ID, "username"))
username_elem.send_keys("username")
pw_elem = find_clickable_element((By.ID, "password"))
pw_elem.send_keys("password")
checkbox_elem = find_clickable_element((By.CLASS_NAME, "form-check-input"))
checkbox_elem.click()
login_btn_elem = find_clickable_element((By.ID, "log-in"))
login_btn_elem.click()
time.sleep(10)
driver.quit()
現在還會覺得應用 Explicit Wait
很麻煩嗎?
透過程式設計,我們可以提高程式碼的重用性和靈活性,如同之前介紹 OOP 的目的,下一篇文章,我們會再來了解 POM (Page Object Model) 怎樣被套用在自動化測試的程式上。