前言
問題
學習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 : 28174043@qq.com @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 : 28174043@qq.com @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 : 28174043@qq.com @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 : 28174043@qq.com @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 : 28174043@qq.com @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 : 28174043@qq.com @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模塊中的場景方法比較穩定),以上就是這篇隨筆的內容了