Selenium系列(六) – 強制等待、隱式等待、顯式等待詳細介紹和源碼解讀

  • 2020 年 3 月 28 日
  • 筆記

如果你還想從頭學起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 ,會返回多個元素
  • 如果用這個條件類,必須等所有匹配到的元素都載入出來才通過