Qingquan-Li / blog

My Blog
https://Qingquan-Li.github.io/blog/
132 stars 16 forks source link

Selenium Webdriver显式等待和隐式等待 #125

Open Qingquan-Li opened 5 years ago

Qingquan-Li commented 5 years ago

很多 Web 应用程序开发都使用了 Ajax 和 Javascript 。当浏览器加载页面时,我们想要与之交互的元素可能以不同的时间间隔加载。

这就使得元素定位比较困难,例如:某个要定位的元素还没有加载出来,在定位的时候,就会抛出异常: ElementNotVisibleException 。


这个时候,需要合理使用 wait 。Selenium Webdriver 提供了两种类型的等待方法:


一、显式等待(explicit wait)

只有特定条件触发后,WebDriver 才会继续执行后续操作。

显式等待的代码定义了等待条件,只有该条件触发,才执行后续代码。

最糟糕的显式等待就是使用 time.sleep() ,这种方法(线程等待)将条件设置为固定的等待时长。

更好的方法是,让你只等待需要的时间(在该时间段内某个时间点完成即可),而不是固定的时长:WebDriverWait 和 expected_conditios 模块组合起来构成了 Selenium 的显式等待:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

WebDriverWait 配合该类的 until()until_not() 方法,就能够根据判断条件而进行灵活地等待了。它主要的意思就是:程序每隔xx秒看一眼(默认500毫秒),如果 expected_conditions 预期条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,抛出异常:TimeoutException

上面的代码最多等待 10 秒,超时后就抛出 TimeoutException ,假设在第3秒就找到了这个元素,那么也就不会多等剩下的7秒时间,而是继续执行后续代码。

WebDriverWait 默认每 500 毫秒调用一次 expected_conditions 中的方法,直到它条件成立返回成功信号。如果判断条件为成功,则返回布尔类型的True或不为null的相应的内容。


from selenium.webdriver.support import expected_conditions as EC

wait_result = WebDriverWait(driver=self.driver, timeout=300, poll_frequency=0.5,  ignored_exceptions=None).until(
    EC.text_to_be_present_in_element((By.XPATH, '//*[@id="VolumeTable"]/tbody/tr[1]/td[4]/label'), u'可用'))

模块包含一套预定义的条件集合。大大方便了 WebDriverWait 的使用:

另外,Selenium Python绑定了一些方便的预期条件判断方法,如 presence_of_element_located ,因此不必自己编写expected_conditions 的方法: https://selenium-python.readthedocs.io/waits.html


二、隐式等待(implicit wait)

An implicit wait tells WebDriver to poll the DOM for a certain amount of time when trying to find any element (or elements) not immediately available. The default setting is 0. Once set, the implicit wait is set for the life of the WebDriver object.

隐式等待告诉 WebDriver 在尝试查找(页面加载后)不能立即可用的 any element (or elements)时,轮询 DOM 一段时间(默认设置为0秒)。

例如设置隐式等待时间为10秒,页面加载后不能立即查找到需要的元素(例如查找 id 为 loadedButton 的元素),则需要轮询 DOM 10秒,即使第3秒时已经找到需要的元素,也要10秒后才能执行下一步。如果10秒后依然找不到需要的元素,抛出异常。

一旦设置了隐式等待,它的作用范围将是 Webdriver 对象的整个生命周期(即所有的查找元素动作都需要等待设定的时长,所以只需设置一次)。

from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")


我想等我要的元素出来之后就下一步怎么办?这就需要使用 Selenium 提供的另一种等待方式——显式等待(explicit wait)了。


三、显式等待和隐式等待的区别

Explicit wait(显式等待):

Implicit wait(隐式等待):

拓展阅读:


四、同时使用隐式等待和显式等待

当浏览器加载页面时,由于 AJAX 技术和 Javascript 的使用,我们想要与之交互的元素可能以不同的时间间隔加载,某个要定位的元素可能还没有加载出来,因为我们不能立即查找到这个元素,所以需要使用等待。 但是,当我们同时使用显式等待和隐式等待时,将变得混乱:

1. 隐式等待=显式等待=10秒

两个等待同时使用以定位元素,当显式等待开始并查找元素时,并且在10秒内直到找到了元素,由于隐式等待需要等待10秒(因为页面加载后不能立即找到元素)。所以依然需要10秒等待时间。

但是有时你会因为同步问题而需要等待20秒。例如隐式等待10秒后还未找到元素,则隐式等待抛出异常。并且,直到隐式等待抛出异常,显式等待开始另一次迭代需要再等待10秒。因此,在这种情况下,需要等待20秒

2. 隐式等待30秒 > 显式等待10秒

当使用显式等待开始查找元素时,即使在10秒内找到了元素,由于隐式等待时间为30秒,它依然需要等待30秒。

3. 隐式等待10秒 < 显式等待30秒

当使用显式等待开始查找元素时,由于隐式等待时间为10秒,所以需要先等待10秒。隐式等待10秒之后,如果无法定位元素,隐式等待会抛出异常。异常将终止显式等待进行进一步查找元素。此时。隐式等待破坏了显式等待,因此建议不要一起使用隐式等待和显式等待。