關(guān)于Selenium里等待的理解

關(guān)于Selenium里等待的理解

首先使用selenium做自動(dòng)化測(cè)試時(shí)有時(shí)需要等待元素的加載完成晨炕,常用的等待方式有三種蒸播,一種是強(qiáng)制等待匿醒,一種是隱式等待场航,還有一種是顯式等待。
官網(wǎng)對(duì)于等待的章節(jié)地址:https://www.selenium.dev/documentation/zh-cn/webdriver/waits/
里面也說(shuō)到了有三種方式廉羔,隱式等待溉痢,顯式等待和流暢等待。

強(qiáng)制等待

強(qiáng)制等待可以說(shuō)是最簡(jiǎn)單最粗暴的一種方式憋他,實(shí)現(xiàn)方式如下

import time
# 程序休眠5秒鐘
time.sleep(5);

這個(gè)是Pyhton程序自帶的包提供的方法适室,意思是執(zhí)行到這個(gè)time.sleep(5)的時(shí)候,程序就睡覺(jué)举瑰,睡一會(huì)再繼續(xù)捣辆。
這種方法在其他語(yǔ)言中也都有實(shí)現(xiàn),比如java中使用 Thread.sleep(5000) 來(lái)使當(dāng)前線程休眠5秒鐘后繼續(xù)執(zhí)行此迅。
這個(gè)方法其實(shí)是和 selenium 或者干其他任何事都是不相關(guān)的汽畴,你無(wú)論程序是在干嘛,只要執(zhí)行到sleep那就睡覺(jué)耸序,睡醒了繼續(xù)向下執(zhí)行忍些。

隱式等待

隱式等待先看實(shí)現(xiàn)方式

from selenium import webdriver

# 建立一個(gè)webdriver
driver = webdriver.Chrome()
# 設(shè)置隱式等待時(shí)間為30秒
driver.implicitly_wait(30)

我一開(kāi)始以為那個(gè)隱式等待是,執(zhí)行到那里的時(shí)候等待元素加載完成坎怪,但其實(shí)我納悶的是罢坝,這方法并沒(méi)有指定是什么元素加載完成。但是理解成是等待頁(yè)面所有元素加載完成搅窿,那也有點(diǎn)不太科學(xué)嘁酿,就是頁(yè)面的元素到底加載完成不完成這個(gè)可能selenium真的無(wú)法知道,就連瀏覽器可能也無(wú)法使用一個(gè)標(biāo)準(zhǔn)來(lái)判定該頁(yè)面是否加載完成男应。
我現(xiàn)在基本理解了闹司,可以認(rèn)為那個(gè) driver.implicitly_wait(30) 方法只是一個(gè)設(shè)置方法。它的意思僅僅只是沐飘,為 driver這個(gè)對(duì)象設(shè)置一個(gè) 隱式等待 的時(shí)間游桩,僅僅只是給這個(gè)對(duì)象設(shè)置而已。
然后這個(gè)時(shí)間什么時(shí)候會(huì)用呢耐朴?當(dāng)我們使用 selenium 中的 webDriver 來(lái)與瀏覽器通信的時(shí)候借卧,比如我們使用 driver.find_element(By.Id, "someId") 這個(gè)方法的時(shí)候。其實(shí)這個(gè)方法的本質(zhì)就是筛峭,我們使用 selenium 向?yàn)g覽器發(fā)了一條指令铐刘,指令的意思是我要找一個(gè)Id為'someId'的元素。
這個(gè)通信的過(guò)程大致是這樣的蜒滩, webDriver 發(fā)送指令給 driver 滨达,然后 driver 調(diào)用瀏覽器如果找到了就告訴我奶稠,找不到的話,那么 隱式等待 的時(shí)間最大的時(shí)候捡遍,就超時(shí)返回異常锌订,這個(gè)最大時(shí)間就是 driver.implicitly_wait(30) 這里設(shè)置的時(shí)間』辏可以理解為 Driver 會(huì)和瀏覽器建立一個(gè) Session 辆飘,這個(gè)隱式等待的設(shè)置時(shí)間就是會(huì)話的最大生命周期。關(guān)系圖如下(官網(wǎng)的圖):

image.png

這個(gè)隱式等待時(shí)間的設(shè)置的作用范圍是整個(gè)driver谓传,就是只要設(shè)置一次蜈项,在使用這個(gè)driver的任何 find_element 操作生效,當(dāng)然可能不僅限于 find_element 的操作续挟,可能還有別的操作也會(huì)有紧卒。
所以這個(gè)時(shí)間叫 隱式等待 ,意思是說(shuō)不是我每次都要指定的诗祸,默認(rèn)每次都會(huì)有這個(gè)等待的操作跑芳,無(wú)論你設(shè)置不設(shè)置默認(rèn)都有,不可見(jiàn)直颅,所以說(shuō)可以理解為是隱藏的博个,即使不設(shè)置,也有默認(rèn)值功偿。

顯式等待

先看顯式等待的用法

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

# 建立一個(gè)webdriver
driver = webdriver.Chrome()

login_form = WebDriverWait(driver, timeout=5).until(
            lambda d: d.find_element(By.ID, "TANGRAM__PSP_4__form"))

WebDriverWait類(lèi)的構(gòu)造方法盆佣,


image.png

這個(gè) WebDriverWait 類(lèi)的構(gòu)造方法可以指定 超時(shí)時(shí)間,輪詢(xún)頻率械荷,還有需要忽略的異常共耍,后面兩者可以不設(shè)置。
根據(jù)這個(gè)方法可以大概理解养葵,我要等待 timeout 秒征堪,直到 until (這里的表達(dá)式返回一個(gè)True) ,或者可以把這個(gè) until 換成 until_not 关拒,后者是返回 False ,輪詢(xún)那個(gè)表達(dá)式,直到條件成立庸娱,返回着绊,否則,拋出異常超時(shí)熟尉。
兩個(gè)方法的源碼:

image.png

所以归露,官方文檔中說(shuō)的兩者混用的意思就是,如果使用 until() 里面的表達(dá)式 會(huì)觸發(fā)那個(gè)隱式等待的時(shí)間的話斤儿,那就是混用了剧包。示例代碼中就是兩個(gè)等待都用到了恐锦。顯式等待里面0.5秒輪詢(xún)一次表達(dá)式,表達(dá)式又觸發(fā)隱式等待時(shí)間疆液,然后如果 表達(dá)式一直不成立一铅,那這個(gè)超時(shí)時(shí)間就會(huì)比較長(zhǎng)。

顯式等待和隱式等待的混合使用的理解

官網(wǎng)的解釋是這樣的:

警告: 不要混合使用隱式和顯式等待堕油。這樣做會(huì)導(dǎo)致不可預(yù)測(cè)的等待時(shí)間潘飘。例如,將隱式等待設(shè)置為10秒掉缺,將顯式等待設(shè)置為15秒卜录,可能會(huì)導(dǎo)致在20秒后發(fā)生超時(shí)。

我一開(kāi)始讀到這一段的時(shí)候很疑惑為啥是20秒眶明,沒(méi)有理解艰毒,但是后來(lái)結(jié)合源碼看了一下,恍然大悟搜囱。
以官方說(shuō)明為例现喳,假設(shè)有這樣一段代碼:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

# 建立一個(gè)webdriver
driver = webdriver.Chrome()
# 設(shè)置隱式等待時(shí)間為10秒
driver.implicitly_wait(10)
driver.get("https://baidu.com")
# 查找一個(gè)ID為SomeId的元素,等待最長(zhǎng)時(shí)間為15秒犬辰,1秒輪詢(xún)一次
some_ele = WebDriverWait(driver, timeout=15, poll_frequency=1).until(
            lambda d: d.find_element(By.ID, "SomeId"))

假如這個(gè)目標(biāo)元素根本不存在嗦篱,那么該段代碼中多少秒會(huì)拋出異常呢?
答案正如官網(wǎng)所說(shuō)是:20秒幌缝,確切的說(shuō)應(yīng)該是21秒灸促,有兩次輪詢(xún)間隔,輪詢(xún)間隔時(shí)間不設(shè)置默認(rèn)是0.5秒涵卵。那么為什么是20秒不是15秒呢浴栽?不應(yīng)該是已最大的等待時(shí)間為準(zhǔn)嗎?
接下來(lái)分析一下wait類(lèi)的源碼轿偎,先直接看until方法:

    def until(self, method, message=''):
        """Calls the method provided with the driver as an argument until the \
        return value is not False."""
        # 源碼注釋的意思是“調(diào)用驅(qū)動(dòng)程序提供的方法作為參數(shù)典鸡,直到返回值不是False』祷蓿”
        screen = None
        stacktrace = None
        # 首先定義一個(gè)最大時(shí)間=當(dāng)前時(shí)間+用戶(hù)設(shè)置的超時(shí)時(shí)間萝玷,我們?cè)O(shè)置是15秒。
        end_time = time.time() + self._timeout
        # 開(kāi)啟一個(gè)循環(huán)
        while True:
            try:
                # 執(zhí)行用戶(hù)傳入的匿名函數(shù)昆婿,我們這里指的就是查找元素球碉,
                # d.find_element(By.ID, "SomeId")
                # 這個(gè)方法會(huì)觸發(fā)隱式等待,我們?cè)O(shè)置的隱式等待時(shí)間最長(zhǎng)是10秒仓蛆,
                # 因?yàn)槲覀儾檎业脑夭淮嬖谡龆栽摲椒〞?huì)在此處阻塞10秒,
                # 10秒后拋出異常NoSuchElementException看疙,
                # 如果設(shè)置了NoSuchElementException為_(kāi)ignored_exceptions豆拨,
                # 就會(huì)跳到except中捕獲截圖和堆棧
                value = method(self._driver)
                # 方法如果返回的值不為None直奋,或False,直接返回這個(gè)值施禾。
                # 這個(gè)判斷要看一下python對(duì)于其他數(shù)據(jù)類(lèi)型和bool之間的轉(zhuǎn)換
                # 一般只要是非空脚线,都為T(mén)rue
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, 'screen', None)
                stacktrace = getattr(exc, 'stacktrace', None)
            # 程序休眠 _poll 秒,這里的_poll是我們構(gòu)建Wait類(lèi)時(shí)傳入的輪詢(xún)間隔拾积,我們?cè)O(shè)置為1秒殉挽。
            time.sleep(self._poll)
            # 判斷當(dāng)前時(shí)間是否已超出 最大等待時(shí)間,即上面定義的end_time,超出則跳出循環(huán)
            if time.time() > end_time:
                break
        # 跳出循環(huán)則拋出異常拓巧,超時(shí)
        raise TimeoutException(message, screen, stacktrace)

從代碼中可以看出斯碌,如果這個(gè)元素根本不存在,那最大時(shí)間應(yīng)該是 10秒+1秒+10秒+1秒=22秒肛度。第一個(gè)10秒指的是隱式等待時(shí)間設(shè)置的超時(shí)傻唾,第一個(gè)1秒是我們自己設(shè)置的輪詢(xún)間隔,然后這時(shí)候并沒(méi)有超出顯示等待的最大時(shí)間15秒承耿,所以進(jìn)入第二次循環(huán)冠骄,依舊是一個(gè)10秒的隱式等待,一個(gè)1秒的輪詢(xún)間隔加袋,然后這是已經(jīng)超出顯示等待最大時(shí)間15秒凛辣,所以跳出循環(huán),拋出超時(shí)異常职烧。這里我應(yīng)該使用腳本做個(gè)試驗(yàn)來(lái)驗(yàn)證自己的猜想扁誓。。
好蚀之,來(lái)寫(xiě)個(gè)腳本驗(yàn)證一下:

import time
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

# 建立一個(gè)webdriver
driver = webdriver.Chrome()
# 設(shè)置隱式等待時(shí)間為10秒
driver.implicitly_wait(10)
# 打開(kāi)百度
driver.get("https://baidu.com")
# 記錄開(kāi)始時(shí)間
start = time.time()
print("開(kāi)始查找一個(gè)不存在的元素蝗敢,開(kāi)始時(shí)間為" + str(start))
try:
    # 查找一個(gè)ID為SomeId的元素,等待最長(zhǎng)時(shí)間為15秒足删,1秒輪詢(xún)一次
    some_ele = WebDriverWait(driver, timeout=15, poll_frequency=1).until(
        lambda d: d.find_element(By.ID, "SomeId"))
except TimeoutException as exc:
    # 捕獲 TimeoutException 異常
    print("查找元素超時(shí)寿谴。。失受。")
finally:
    # 記錄結(jié)束時(shí)間讶泰,
    end = time.time()
    # 打印耗時(shí)
    print("查找結(jié)束,結(jié)束時(shí)間為:" + str(end) + "贱纠,耗時(shí):" + str(end - start))
    # 關(guān)閉Driver
    driver.close()

以上代碼中我們打開(kāi)百度來(lái)查找一個(gè)不存在的元素峻厚,看執(zhí)行結(jié)果:

開(kāi)始查找一個(gè)不存在的元素,開(kāi)始時(shí)間為1606379817.432131
查找元素超時(shí)谆焊。。浦夷。
查找結(jié)束辖试,結(jié)束時(shí)間為:1606379839.466272辜王,耗時(shí):22.034141063690186

控制臺(tái)輸出結(jié)果證實(shí)了我上面的猜想,假如我們這時(shí)將輪詢(xún)間隔時(shí)間改為2秒呢罐孝?
那最長(zhǎng)等待時(shí)間可能就會(huì)變成24秒呐馆。
由此可見(jiàn),這個(gè)兩者混用后的 最大等待時(shí)間和三個(gè)因素有關(guān)莲兢,顯示等待時(shí)間汹来、隱式等待時(shí)間和顯示輪詢(xún)間隔
最大等待時(shí)間的計(jì)算公式應(yīng)該是這樣的:

# 假設(shè) x=隱式等待時(shí)間, y=輪詢(xún)間隔時(shí)間, z=顯式等待時(shí)間, r=最長(zhǎng)等待時(shí)間
if x + y >= z:
    r = x + y
else:
    r = (x + y) * (z / (int) (x + y) + 1) or r = (x + y) * z / (int) (x + y) + x + y

這個(gè)公式并不能直接簡(jiǎn)寫(xiě)成 x + y + z 因?yàn)樵诔绦蛑? 操作是會(huì)省略余數(shù)的改艇。
下面說(shuō)明一下收班,那個(gè) NoSuchElementException 為啥會(huì)被捕獲。
貼一下WebDriverWait類(lèi)的源碼截圖:

image.png

源碼的注釋中也可以看到谒兄,如果不指定 ignored_exceptions 這個(gè)參數(shù)摔桦,默認(rèn)就會(huì)只包含 NoSuchElementException 這個(gè)異常,所以這個(gè)異常在等待中是默認(rèn)會(huì)被忽略的承疲。從源碼第 23 和 50 行中可以看出邻耕。
其實(shí)顯式等待可以簡(jiǎn)單的理解成是一個(gè)簡(jiǎn)單的封裝,其中也用到了 time.sleep() 方法燕鸽。

流暢等待

流暢等待 在官網(wǎng)的介紹中與顯式等待并無(wú)區(qū)別兄世,只是更多的添加了輪詢(xún)間隔與可忽略異常參數(shù)的使用。
JavaSelenium 實(shí)現(xiàn)中顯示等待和流暢等待是使用了兩個(gè)不同的類(lèi)啊研,后者是使用了一個(gè)叫 FluentWait 的子類(lèi)御滩,但在 Python 實(shí)現(xiàn)中并沒(méi)有區(qū)別。只是由于語(yǔ)言的特性不同悲伶,在 Java 中將兩中使用定義為了兩個(gè)不同的子類(lèi)艾恼。
這里就不在繼續(xù)介紹這種方式。

總結(jié):

那么最終我們到底應(yīng)該使用哪種方式呢麸锉?可能需要結(jié)合實(shí)際情況考慮钠绍,比如我們需要控制操控頁(yè)面的頻率時(shí),故意停頓時(shí)需要使用到 強(qiáng)制等待 的方式 time.sleep() 花沉;比如在檢索元素時(shí)防止元素未加載完成而拋出 NoSuchElementException 柳爽,就需要使用到顯式等待和隱式等待。
顯式等待 相對(duì)更加靈活一些碱屁,可以指定條件表達(dá)式磷脯,超時(shí)時(shí)間,輪詢(xún)頻率和要忽略的異常娩脾;但是 隱式等待 作用范圍更廣赵誓,一個(gè) Driver 的生命周期只需要設(shè)置一次就可以了。
通常來(lái)說(shuō)來(lái)給 Driver 設(shè)置一個(gè)相對(duì)合適的 隱式等待 時(shí)間就可以滿足大多數(shù)需求了,除非在需求中可能并非所有的元素需要等待的時(shí)間都那么的相對(duì)平均俩功,就需要給一些特殊的元素使用顯式等待的方式來(lái)做幻枉。
兩種等待方式混用其實(shí) 并非是禁用 的,只是在使用的時(shí)候需要考慮到最大的等待時(shí)間可能會(huì)超出預(yù)期诡蜓,這個(gè)就需要注意熬甫。但是一般情況下只要元素是確定存在的并不會(huì)超出設(shè)置的超時(shí)時(shí)間,除非網(wǎng)頁(yè)加載過(guò)慢蔓罚,或者網(wǎng)絡(luò)不佳的情況椿肩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豺谈,隨后出現(xiàn)的幾起案子郑象,更是在濱河造成了極大的恐慌,老刑警劉巖核无,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣唱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡团南,警方通過(guò)查閱死者的電腦和手機(jī)噪沙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吐根,“玉大人正歼,你說(shuō)我怎么就攤上這事】介伲” “怎么了局义?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)冗疮。 經(jīng)常有香客問(wèn)我萄唇,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮了嚎,結(jié)果婚禮上始赎,老公的妹妹穿的比我還像新娘移必。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著忿危,像睡著了一般。 火紅的嫁衣襯著肌膚如雪没龙。 梳的紋絲不亂的頭發(fā)上铺厨,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天缎玫,我揣著相機(jī)與錄音,去河邊找鬼努释。 笑死碘梢,一個(gè)胖子當(dāng)著我的面吹牛咬摇,可吹牛的內(nèi)容都是我干的伐蒂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肛鹏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逸邦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起在扰,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缕减,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后芒珠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桥狡,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年皱卓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裹芝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娜汁,死狀恐怖嫂易,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掐禁,我是刑警寧澤怜械,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站傅事,受9級(jí)特大地震影響缕允,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹭越,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一障本、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧般又,春花似錦彼绷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至堕义,卻和暖如春猜旬,著一層夾襖步出監(jiān)牢的瞬間脆栋,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工洒擦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椿争,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓熟嫩,卻偏偏與公主長(zhǎng)得像秦踪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掸茅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容