1. 程式人生 > >強制等待&隱士等待&顯示等待&元素定位方法封裝

強制等待&隱士等待&顯示等待&元素定位方法封裝

前言

問題

學習selenium的同學估計大多數都遇見過一個問題

明明頁面已經精準的定位到了元素,但是執行指令碼的時候卻經常報錯沒找到元素。其實原因很簡單,就是指令碼執行的速度很快,而瀏覽器載入頁面的時候由於網速,css渲染,JS等各種原因導致頁面載入緩慢,所以當指令碼執行到定位一個元素的程式碼時,頁面還未加載出這個元素,進而導致程式碼報錯。那麼有沒有辦法解決這種問題呢?of course,如果解決不了還叫自動化嘛

我們先看下面的一個用例(百度首頁輸入“linux超”關鍵詞,點選“百度一下”, 在搜尋結果中找到我的部落格地址並點選進入我的部落格)我們不使用任何等待方法

"""
------------------------------------
@Time : 2019/7/4 12:34
@Auth : linux超
@File : nowait.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import time

from selenium import webdriver
import unittest
from selenium.common.exceptions import NoSuchElementException, TimeoutException


class TestWait(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.get("https://www.baidu.com")
        self.driver.maximize_window()

    def test_no_wait(self):
        try:
            # 等輸入框出現在DOM樹中
            input_box = self.driver.find_element_by_id('kw')
            input_box.send_keys('linux超')  # 輸入linux超
            # 等元素可點選
            query_btn = self.driver.find_element_by_id('su')
            query_btn.click()  # 點選
            # 等輸入框出現在DOM樹中
            my_blog = self.driver.find_element_by_xpath('//*[text()="https://www.cnblogs.com/"]')  # 搜尋結果找到我的部落格
            my_blog.click()  # 進入我的部落格
            time.sleep(2)  # 這裡我是為了看到效果(跳轉到我的部落格首頁)
        except (NoSuchElementException, TimeoutException) as e:
            raise e

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

執行結果

E
======================================================================
ERROR: test_no_wait (__main__.TestWait)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/MyThreading/nowait.py", line 38, in test_no_wait
    raise e
  File "D:/MyThreading/nowait.py", line 34, in test_no_wait
    my_blog = self.driver.find_element_by_xpath('//*[text()="https://www.cnblogs.com/"]')  # 搜尋結果找到我的部落格
  File "C:\Python36\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 394, in find_element_by_xpath
    return self.find_element(by=By.XPATH, value=xpath)
  File "C:\Python36\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 978, in find_element
    'value': value})['value']
  File "C:\Python36\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "C:\Python36\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: //*[text()="https://www.cnblogs.com/"]


----------------------------------------------------------------------
Ran 1 test in 14.010s

FAILED (errors=1)

Process finished with exit code 1

不使用任何等待方法的時候,定位搜尋結果的時候就報錯了,因為百度搜索關鍵詞的時候結果頁面會有一定時間的載入過程,還未載入完成時,程式碼就執行了定位方法,因此報錯

強制等待

強制等待其實是python內建模組time的一個方法sleep(n),顧名思義哈,強制等待就是死等固定時間n秒,比如你女票叫你在樓下等10分鐘她化妝,那麼你就必須等10分鐘,10分鐘後她還不來,那你就可以該幹嘛幹嘛去了,定位元素的時候也可以使用這個方法,在定位元素之前,等待固定的時間,再定位。我們使用這個方法修改上面的錯誤用例

"""
------------------------------------
@Time : 2019/7/4 12:34
@Auth : linux超
@File : nowait.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import time

from selenium import webdriver
import unittest
from selenium.common.exceptions import NoSuchElementException, TimeoutException


class TestWait(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.get("https://www.baidu.com")
        self.driver.maximize_window()

    def test_no_wait(self):
        try:
            # 等輸入框出現在DOM樹中
            input_box = self.driver.find_element_by_id('kw')
            input_box.send_keys('linux超')  # 輸入linux超
            # 等元素可點選
            query_btn = self.driver.find_element_by_id('su')
            query_btn.click()  # 點選
            # 設定強制等待5秒,再定位元素
            time.sleep(5)
            # 等輸入框出現在DOM樹中
            my_blog = self.driver.find_element_by_xpath('//*[text()="https://www.cnblogs.com/"]')  # 搜尋結果找到我的部落格
            my_blog.click()  # 進入我的部落格
            time.sleep(2)  # 這裡我是為了看到效果(跳轉到我的部落格首頁)
        except (NoSuchElementException, TimeoutException) as e:
            raise e

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

執行結果

.
----------------------------------------------------------------------
Ran 1 test in 21.043s

OK

Process finished with exit code 0

沒錯,執行通過了,但是強制等待有很大的弊端,比如載入頁面只需要1秒鐘就能定位到元素,但是你設定了超過1秒的等待時間,嚴重浪費了其他時間,而且你無法判定頁面載入完成到底需要多少時間

那麼你的指令碼其實也是不穩定的, 再比如,你為了節省指令碼的執行時間, 你只設置了1秒的等待,而且指令碼通過了,但是當你的網路很差的時候,1秒的等待就無法成功定位到元素了,導致指令碼執行失敗

因此只要有一個因素改變就可能導致指令碼的失敗很不穩定,為了解決這種問題,selenium webdriver 又引入了隱士等待

隱士等待

隱士等待表示在自動化實施過程中,為查詢頁面元素或執行命令設定一個最長等待時間,如果在規定時間內頁面元素被找到或者命令被執行完成,則執行下一步,否則繼續等待直到設定的最長等待時間截至

使用webdriver 的implicitly_wait()方法設定隱士等待,我們把前面用例使用隱士等待再做修改

例項

"""
------------------------------------
@Time : 2019/7/4 12:37
@Auth : linux超
@File : implicitly_wait.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import time

from selenium import webdriver
import unittest
from selenium.common.exceptions import NoSuchElementException, TimeoutException


class TestWait(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()

    def test_implicitly_wait(self):
        self.driver.get("https://www.baidu.com")
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        try:
            input_box = self.driver.find_element_by_id('kw')  # 搜尋框
            input_box.send_keys('linux超')  # 輸入linux超
            query_btn = self.driver.find_element_by_id('su')  # 百度一下按鈕
            query_btn.click()  # 點選
            my_blog = self.driver.find_element_by_xpath('//*[text()="https://www.cnblogs.com/"]')  # 搜尋結果找到我的部落格
            my_blog.click()  # 進入我的部落格
            time.sleep(2)  # 這裡我是為了看到效果(跳轉到我的部落格首頁)
        except (NoSuchElementException, TimeoutException) as e:
            raise e

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

執行過程

隱士等待的好處是不用像強制等待那樣死等固定時間n秒,可以在一定程度上提升測試用例的執行效率和指令碼的穩定性,不過這種方法也存在一個弊端,那就是程式會一直等待整個頁面載入完成(頁面左上角不再轉圈圈),才會繼續執行下一步操作,比如某些時候我們想要的頁面元素早就載入完了,但是由於個別JS等資源載入稍慢,此時程式仍然會等待頁面全部載入完成才會繼續執行下一步,這無形中加長了測試用例的執行時間

為了避免這個弊端,webdriver 又引入了一種等待方式,叫顯示等待。還有一點需要說明,隱士等待只需要設定一次,然後它將在driver的整個生命週期都起作用

顯示等待

上面我們介紹了隱士等待,下面再介紹一個更加智慧的等待方式--顯示等待。通過selenium.webdriver.support.ui模組提供的WebDriverWait類,再結合該類的until()和until_not()方法,並自定義好顯示等待的條件,然後根據判斷條件而進行靈活的等待,顯示等待更比隱士等待節約指令碼執行時間,推薦儘量使用顯示等待的方式

設定了顯示等待,程式會每個一段時間(預設是0.5s)執行一下自定義的判斷條件,如果條件成立就執行下一步操作,否則繼續等待,直到超過設定的最長等待時間,然後丟擲超時異常

WebDriverWait類解析

初始化方法

 1 class WebDriverWait(object):
 2     def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None):
 3         """Constructor, takes a WebDriver instance and timeout in seconds.
 4 
 5            :Args:
 6             - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
 7             - timeout - Number of seconds before timing out
 8             - poll_frequency - sleep interval between calls
 9               By default, it is 0.5 second.
10             - ignored_exceptions - iterable structure of exception classes ignored during calls.
11               By default, it contains NoSuchElementException only.
12 
13            Example:
14             from selenium.webdriver.support.ui import WebDriverWait \n
15             element = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_id("someId")) \n
16             is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\ \n
17                         until_not(lambda x: x.find_element_by_id("someId").is_displayed())
18         """

引數解釋

driver:webdriver的例項物件

timeout:最長的顯示等待時間,單位為s

poll_frequency:呼叫頻率,也就是再timeout設定的時間內,每隔poll_frequency時間執行一次判斷條件,預設是0.5

ignored_exception:執行過程中忽略的異常型別,預設忽略NoSuchElelmentException異常

WebDriverWait提供的方法

until(method, message='')

在規定等待時間內,每隔一段時間呼叫一下method方法,直到其返回值為True,如果超時,丟擲帶有message異常資訊的TimeoutException異常

until_not(method, message='')

與until方法相反,不贅述

例項

現在我們使用顯示等待實現之前的用例

"""
------------------------------------
@Time : 2019/7/4 12:50
@Auth : linux超
@File : webdriverWait.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
import time

from selenium import webdriver
import unittest
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By


class TestWait(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()

    def test_webdriver_wait(self):
        self.driver.get("https://www.baidu.com")
        self.driver.maximize_window()
        try:
            # 等輸入框出現在DOM樹中
            input_box = WebDriverWait(self.driver, 10, 0.3).until(EC.visibility_of_element_located((By.ID, 'kw')))
            input_box.send_keys('linux超')  # 輸入linux超
            # 等元素可點選
            query_btn = WebDriverWait(self.driver, 10, 0.3).until(EC.element_to_be_clickable((By.ID, 'su')))
            query_btn.click()  # 點選
            # 等輸入框出現在DOM樹中
            my_blog = WebDriverWait(self.driver, 10, 0.3).until(EC.visibility_of_element_located((By.XPATH, '//*[text()="https://www.cnblogs.com/"]'))) # 搜尋結果找到我的部落格
            my_blog.click()  # 進入我的部落格
            time.sleep(2)  # 這裡我是為了看到效果(跳轉到我的部落格首頁)
        except (NoSuchElementException, TimeoutException) as e:
            raise e

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

可以看到顯示等待需要配合expected_conditions模組中的各個場景方法使用,具體的場景介紹可以參考我之前的這篇文章

執行效果

表面上和隱士等待執行效果一樣,其實還是有一定差異的,當測試指令碼操作的頁面比較多時,你會發現兩中等待方式對於指令碼的執行效率是不一樣的,顯示等待更加節省時間

定位元素方法封裝

顯示等待和隱士等待我們已經知道是什麼東西了,也大概知道他們的區別在哪裡了,但是不知道你是否發現一個問題,顯示等待案例中定位每個元素都要重新寫一個顯示等待然後呼叫判斷場景,是不是很麻煩?

下面我們就把顯示等待定位元素的方法做個封裝,看程式碼

 base.py

"""
------------------------------------
@Time : 2019/7/4 13:18
@Auth : linux超
@File : base.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.support.ui import WebDriverWait


class Base(object):

    def __init__(self, driver):
        self.driver = driver

    def find_element(self, by, locator, timeout=30):
        """
        定位單個元素
        :param by: 定位方式 eg:By.ID
        :param locator: 定位表示式
        :param timeout: 顯示等待超時時間
        :return:
        """
        try:
            element = WebDriverWait(self.driver, timeout).\
                until(lambda driver: driver.find_element(by, locator))
        except (NoSuchElementException, TimeoutException) as e:
            raise e
        else:
            return element

    def find_elements(self, by, locator, timeout=30):
        """
        定位一組元素
        :param by: 定位方式 eg:By.ID
        :param locator: 定位表示式
        :param timeout: 顯示等待超時時間
        :return:
        """
        try:
            elements = WebDriverWait(self.driver, timeout).\
                until(lambda driver: driver.find_elements(by, locator))
        except (NoSuchElementException, TimeoutException) as e:
            raise e
        else:
            return elements


if __name__ == '__main__':
    pass

 下面我們測試一下封裝的方法

"""
------------------------------------
@Time : 2019/7/4 9:17
@Auth : linux超
@File : webwait.py
@IDE  : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ   : [email protected]
@GROUP: 878565760
------------------------------------
"""
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By

from base import Base


class TestWait(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.base = Base(self.driver)
        self.driver.get("https://www.baidu.com")
        self.driver.maximize_window()
        
    def test_webdriver_wait(self):
        # 等輸入框出現在DOM樹中
        input_box = self.base.find_element(By.ID, 'kw')
        input_box.send_keys('linux超')  # 輸入linux超
        # 等元素可點選
        query_btn = self.base.find_element(By.ID, 'su')
        query_btn.click()  # 點選
        # 找搜尋結果中的每一個標題
        elements = self.base.find_elements(By.XPATH, '//h3[@class="t"]')
        # 迴圈列印每一個搜尋結果
        for element in elements:
            print(element.text)

    def tearDown(self):
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

執行過程及結果

 

Linux學習教程,Linux入門教程(超詳細)
Linux命令(超詳細版) - CSDN部落格
linux超 - 部落格園
新型Linux 病毒,指令碼超 1000 行,功能複雜 - OSCHINA_開源中國
《Linux就該這麼學》 - 必讀的Linux系統與紅帽RHCE認證免費自學書籍
技術|有所為,有所不為:在 Linux 中使用超級使用者許可權
Linux下掛載超過2T的磁碟 - qq_40143313的部落格 - CSDN部落格
現在Linux執行在 99.6%的TOP500超級計算機上_TechWeb
Linux 教程 | 菜鳥教程
linux中超級快 - 雲+社群 - 騰訊雲
.
----------------------------------------------------------------------
Ran 1 test in 20.094s

OK

Process finished with exit code 0

總結

再來總結一下3中等待方法的優缺點

1.強制等待--固定等待一段時間,即使設定一定的等待時間,也不能確保一定能夠定位到元素,因為你無法知道頁面載入的時間,而且這種方法通常比較浪費指令碼執行時間,效率低

2.隱士等待--設定最長的等待時間,在這個時間內,當元素被加載出現在DOM樹中且頁面被完全載入完成之後,才執行下一步操作,保證了指令碼的穩定性,但是執行效率相對較低,因為往往我們只需要目標元素出現即可,並不需要整個頁面都載入完成,而隱士等待要等待整個頁面載入完才能執行下一步,浪費一定時間,那麼為了解決這種弊端又引入了顯示等待

3.顯示等待--顯示等待實現方式通過判斷某一個條件是否成立,如果成立就立即執行下一步操作,不需要等待頁面載入完成,執行效率高,指令碼的穩定性也相對較高

最後

最後我們使用顯示等待的方法封裝了定位單一元素和定位一組元素的方法,解決了重複使用顯示等待方法定位的程式碼,使用這個封裝方法能夠定位到大多數的元素(一些特殊的元素還是需要結合expected_conditions模組中的場景方法比較穩定),以上就是這篇隨筆的內