1. 程式人生 > >Selenium系列(六) - 強制等待、隱式等待、顯式等待詳細介紹和原始碼解讀

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