Selenium系列(六) - 強制等待、隱式等待、顯式等待詳細介紹和原始碼解讀
如果你還想從頭學起Selenium,可以看看這個系列的文章哦!
https://www.cnblogs.com/poloyy/category/1680176.html
其次,如果你不懂前端基礎知識,需要自己去補充哦,博主暫時沒有總結(雖然我也會,所以我學selenium就不用複習前端了哈哈哈...)
設定元素等待
為什麼需要設定元素等待?
- 因為,目前大多數Web應用程式都是使用Ajax和Javascript開發的;每次載入一個網頁,就會載入各種HTML標籤、JS檔案
- 但是,載入肯定有載入順序,大型網站很難說一秒內就把所有東西加載出來,不僅如此,載入速度也受網路波動影響
- 因此,當我們要在網頁中做元素定位的時候,有可能我們打開了網頁但元素未加載出來,這個時候就定位不到元素,就會報錯
- 所以,我們需要設定元素等待,意思就是:等待指定元素已被加載出來之後,我們才去定位該元素,就不會出現定位失敗的現象了
如果我們不設定元素等待,那怎麼避免 因元素未加載出來而定位失敗 的情況出現呢?
- 答案很簡單,就是呼叫 sleep() ,也叫強制等待
- 但是缺點就是:如果指定的時間過長,即使元素已被加載出來了,但還是要繼續等,這樣會浪費很多時間
強制等待的栗子
#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ = __Time__ = 2020/3/25 17:52 __Author__ = 小菠蘿測試筆記 __Blog__ = https://www.cnblogs.com/poloyy/ """ from time import sleep from selenium import webdriver driver = webdriver.Chrome("../resources/chromedriver.exe") 20) # 訪問網址 driver.get("http://www.baidu.com") # ===強制等待3秒才執行下一步=== sleep(3) # 找到搜尋框 inputElement = driver.find_element_by_id("kw")
WebDriver提供了兩種型別的等待:顯式等待和隱式等待
隱式等待
什麼是隱式等待?
- 如果某些元素不是立即可用的,隱式等待是告訴WebDriver去等待一定的時間後去查詢元素
- 預設等待時間是0秒,隱式等待對整個WebDriver的週期都起作用,所以只要設定一次即可
如何體現隱式等待?
如果在規定時間內,整個網頁都載入完成,則執行下一步,否則會丟擲異常
隱式等待的弊端
可以把隱式等待當做全域性變數,它影響整個頁面,所以程式需要等待整個頁面載入完成(就是瀏覽器標籤欄那個小圈不再轉)時,才會執行下一步【頁面載入完成,才能執行下一步】
但可能頁面載入未完成的時候,需要定位的元素已經載入完成了,但受限於某些JS檔案、圖片載入特別慢,我們不能執行下一步,必須得等到網頁所有東西都載入完了才能下一步【增加不必要的載入時間】
隱式等待的程式碼
很簡單,就呼叫一個方法即可,畢竟是作用於WebDriver的
#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ = __Time__ = 2020/3/25 17:52 __Author__ = 小菠蘿測試筆記 __Blog__ = https://www.cnblogs.com/poloyy/ """ from selenium import webdriver # 載入驅動 driver = webdriver.Chrome("../resources/chromedriver.exe") # ===隱性等待20s=== driver.implicitly_wait(20) # 訪問網址 driver.get("http://www.baidu.com") # 找到搜尋框 inputElement = driver.find_element_by_id("kw")
顯式等待
什麼是顯式等待?
- 需要定位某個元素的時候,但元素可能不可見,這個時候針對這個元素就可以使用顯式等待了
- 顯式等待和隱式等待最大的不同就是:你可以它看成是區域性變數,作用於指定元素
顯式等待的優勢
相比隱式等待,顯式等待只對指定元素生效,不再是在整個WebDriver生命週期內生效【僅對元素生效】
可以根據需要定位的元素來設定顯式等待,無需等待頁面完全載入,節省大量因載入無關緊要檔案而浪費掉的時間【針對元素設定,無需等待頁面載入完成,節省載入時間】
顯式等待的程式碼
#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ = __Time__ = 2020/3/25 17:52 __Author__ = 小菠蘿測試筆記 __Blog__ = https://www.cnblogs.com/poloyy/ """ from time import sleep from selenium import webdriver # 載入驅動 from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome("../resources/chromedriver.exe") # 訪問網址 driver.get("http://www.baidu.com") # ===顯式等待=== # 設定元素等待例項,最多等10秒,每0.5秒檢視條件是否成立 element = WebDriverWait(driver, 10, 0.5).until( # 條件:直到元素載入完成 EC.presence_of_element_located((By.ID, "kw")) )
WebDriverWait原始碼解讀
class WebDriverWait(object): def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None): """Constructor, takes a WebDriver instance and timeout in seconds. :Args: - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) - timeout - Number of seconds before timing out - poll_frequency - sleep interval between calls By default, it is 0.5 second. - ignored_exceptions - iterable structure of exception classes ignored during calls. By default, it contains NoSuchElementException only.
WebDriverWait例項初始化傳參
- driver:WebDriver例項,傳入前面宣告的driver即可
- timeout:最大超時時間;
- poll_frequency:執行間隔,預設0.5s
- ignored_exceptions:需要忽略的異常
- 如果在呼叫 until() 或 until_not() 的過程中丟擲這個元組中的異常, 則不中斷程式碼,繼續等待;
- 如果丟擲的是這個元組外的異常,則中斷程式碼;
- 忽略的異常預設只有 NoSuchElementException
通俗易懂的 WebDriverWait
WebDriverWait(driver例項, 超時時長, 呼叫頻率, 忽略的異常).until(要呼叫的 方法, 超時時返回的資訊)
WebDriverWait例項的兩個方法
until(self, method, message='')
作用:每隔一段時間(上面的poll_frequency)呼叫method,直到返回值不為False或不為空
method:需要執行的method
message:丟擲異常時的文案,會返回 TimeoutException ,表示超時
注意:這個才是常用的,如:定位元素直到不返回空
until_not(self, method, message='')
作用:呼叫method,直到返回值為False或為空
method:需要執行的method
message:丟擲異常時的文案,會返回 TimeoutException ,表示超時
兩個方法的 method引數注意點
如果直接傳入WebElement(頁面元素)物件
WebDriverWait(driver, 10).until(driver.find_element_by_id('kw'))
則會丟擲異常
TypeError: 'xxx' object is not callable
method 引數需要傳入的物件必須包含 __call()__ 方法 ,什麼意思?讓物件可以直接被呼叫
官方提供的兩個小例子
element = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_id("someId")) is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not(lambda x: x.find_element_by_id("someId").is_displayed())
可以看到,通過匿名函式也是可以的,可以說比後面介紹的 expected_conditions 模組要方便多了
那麼有哪些是包含 __call()__ 的物件呢?
- expected_conditions 模組(接下來重點講的)
- WebElement的 is_displayed() 、 is_enabled() 、 is_selected()
expected_conditions原始碼解讀
expected_conditions的介紹
是selenium中的一個模組,包含一系列用於判斷的條件類,一共26個類
這裡就只介紹兩個在設定元素等待裡面最常用的判斷條件類
其一:presence_of_element_located
class presence_of_element_located(object): """ An expectation for checking that an element is present on the DOM of a page. This does not necessarily mean that the element is visible. locator - used to find the element returns the WebElement once it is located """ def __init__(self, locator): self.locator = locator def __call__(self, driver): return _find_element(driver, self.locator)
作用
檢查當前DOM樹種是否存在該元素(和是否可見沒有關係),只要有一個元素加載出來則通過
locator引數
傳入一個元組,格式如下 (By.ID, "元素ID")
- 第一個引數:定位元素的方式,和那八種元素定位方式一樣,只是這裡需要引入 By 模組,然後再呼叫類屬性
- 第二個引數:和之前呼叫元素定位方法一樣傳參即可
- 所以正確寫法是: presence_of_element_located((By.ID, "kw"))
一起來看看By模組的原始碼
class By(object): """ Set of supported locator strategies. """ ID = "id" XPATH = "xpath" LINK_TEXT = "link text" PARTIAL_LINK_TEXT = "partial link text" NAME = "name" TAG_NAME = "tag name" CLASS_NAME = "class name" CSS_SELECTOR = "css selector"
其二:presence_of_all_elements_located
原始碼幾乎一樣
class presence_of_all_elements_located(object): def __init__(self, locator): self.locator = locator def __call__(self, driver): return _find_elements(driver, self.locator)
唯一要注意的點就是
- 因為呼叫的是 _find_elements ,會返回多個元素
- 如果用這個條件類,必須等所有匹配到的元素都加載出來才通過