当UI自动化页面元素不存在时,常见的发生异常的原因有如下几点:
- 页面加载时间过慢,需要查找的元素代码已经执行完成,但是页面还未加载成功,从而发生异常;
- 查到的元素没有在当前的iframe或者frame中,此时需要切换至对应的iframe或者frame中;
- 代码中对元素的描述错误。
在selenium中,提供的3种常见的等待时间的方式各有有点和缺点,当熟练掌握这些方式后,可以尝试针对不同的情况选择最优的等待方式。
1、强制等待
强制等待时设置等待的最简单的方法,如sleep(3),在之前的案例经常看到。sleep()缺点时不够智能,若设置的时间太短,元素没有加载出来,照样报错;若设置的时间太长,又会浪费时间。不要忽视每次几秒的时间,用例多了,代码量就大了,多几秒就会影响脚本的整体运行速度。
import time
from selenium import webdriver
driver = webdriver.Firefox()
driver.maximize_window()
driver.get("https://www.baidu.com")
driver.maximize_window()
time.sleep(3)
driver.quit()
2、隐式等待
隐式等待的好处时不用 像sleep()函数那样强制等待指定的时间,而实通过使用implicitly_wait()方法设定等待时间,单位为秒。当页面的全部元素都展现出来后,才会只能怪后续语句。隐式等待也有缺点,隐式等待时设置了一个最长等待时间(implicitly_wait(10),最长等待时间等于10s),若在规定时间内网页很快加载完成,则执行下一步,否则一直等到时间截止,才执行下一步;如果在设定的等待时间依然找不到元素,则抛出异常。
如果页面上有较多的异步刷新和操作,很多页面元素都是动态出现的,很多操作步骤都需要等待,用隐式等待利大于弊。但在其他时间,仍然不建议使用隐式等待。
from time import sleep, ctime
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("https://cn.bing.com/")
try:
print(ctime())
# 情况1:正确的搜索框元素定义id='sb_form_q;运行情况1需要注释情况2
# driver.find_element_by_xpath("//*[@id='sb_form_q']").send_keys("selenium")
# 情况2:将输入的id改为id='sb_form_qq,看看时间是否等待了10s后,才抛出异常;运行情况2需要把情况1注释
driver.find_element_by_xpath("//*[@id='sb_form_qq']").send_keys("selenium")
driver.find_element_by_xpath("//*[@id='sb_form_go']").click()
except NoSuchElementException as e:
print(e)
finally:
print(ctime()) # 输出第一个时间,观察间隔时间
driver.quit()
2.1、情况1结果
由于搜索页面整体加载速度很快,搜索框元素代码中定义正确,所以代码运行很快结束,可以看到输出的两个时间几乎一致。
2.2、情况2结束分析
搜索框元素的id属性改成了sb_form_qq,而Bing搜索页上并不存在该元素,代码运行时肯定会报错。
运行代码时,检测到元素的时间implicitly_wait(10)中的等待时间10s后抛出异常,并且两次输出的时间也间隔了10s。
注:隐式等待对整个Driver的周期都会起作用,只需要设置一次即可,就是在整个程序代码中的最前面设置一次即可。
3、显示等待
显示等待时一种相对智能的等待机制。执行原理是:程序每隔一定时间检测条件是否成立,若条件成立,则执行下一步;否则继续等待,直到超过设置的最大等待时间,然后抛出异常。显示等待也是我们比较推荐使用的方法。
使用显示等待需要用到from selenium.webdriver.support.wait 库中的对象 WebDriverWait。
from selenium.webdriver.support.wait import WebDriverWait
用百度举例一下,在输入框输入【测试】并点击【百度一下】按钮,代码如下:
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.Firefox()
driver.implicitly_wait(10)
driver.get("https://www.baidu.com/")
ele = WebDriverWait(driver, 10, 0.2).until(EC.visibility_of_element_located((By.ID, 'kw')))
if ele is not None: # 如果成功捕获对象
print(type(ele)) # 打印对象类型
ele.send_keys("测试")
driver.find_element_by_xpath("//*[@id='su']").click()
driver.quit()
运行结果:
下面来一段没有发现指定的页面元素而引起超时的代码。
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.Firefox()
driver.implicitly_wait(10)
driver.get("https://www.baidu.com/")
ele = WebDriverWait(driver, 10, 0.2).until(EC.visibility_of_element_located((By.ID, 'kww')), '没发现输入搜索文本框')
if ele is not None: # 如果成功捕获对象
print(type(ele)) # 打印对象类型
ele.send_keys("测试")
driver.find_element_by_xpath("//*[@id='su']").click()
driver.quit()
在上面的脚本中,我们故意设定 不存在的名为kww的元素,因此在等待了10s后,发现还是定位不到该元素,于是抛出超时异常,对应的异常消息就是“没发现输入搜索文本框” ,运行结果如下图:
WebDriverWait方法中的参数说明如下:
- driver:传入webdriver实例,即代码中的driver。
- timeout:超时时间,即等待的最长时间(同时要考虑隐式等待时间)。
- pll_frequency:调用until或until_not中的方法的间隔时间,默认是0.5s。
- ignored_exceptions:忽略的异常。若在调用until或until_not的过程中抛出这个元组中的异常,则不中断代码,继续等待;若抛出的是这个元组外的异常,则终端代码,抛出异常。默认只有NoSuchElementException。
WebDriverWait需要与until()或until_not()方法结合使用。例如:
WebDriverWait(driver, 10, 0.2).until(method, message = "")
调用该方法提供的驱动程序作为参数,直接返回值为True。
- method:在等待期间,每隔一段时间调用这个传入的方法,直接返回值不是False。
- message:如果超时,抛出Timeout Exception,将message传入异常。例如:
WebDriverWait(driver, 10, 0.2).until_not(method, message = "")
调用该方法提供的驱动程序作为参数,直到返回值为False。
- until_not与until相反,until是当某元素或某个条件成立则继续执行,until_not是当某个元素消失或某个条件不成立则继续执行,两者参数一样。
expectd_conditions是Selenium的一个模块,其中包含一系列用于判断的条件。
类别 | 函数名称 | 参数 | 功能 |
判断元素的可见性 | visibility_of_element_located | locater(目标元素定位) | 根据定位判断目标元素是否已显示,如果已显示,则返回该WebElement对象 |
visibility_of | webElement(目标元素) | 判断目标是否已显示,如果已显示,则返回该WebElement对象 | |
visibility_of_all_element_located | locater(目标元素定位) | 根据定位判断页面上是否存在一个或多个符合定位的元素,且是否已全部显示,如果是,则返回该WebElement集合 | |
visibility_of_any_element_located | locater(目标元素定位) | 根据定位判断页面上是否存在一个或多个符合定位的元素,这些元素是都至少有一个已显示,如果是,则返回其中已显示的WebElement集合 | |
invisibility_of_element_located | locater(目标元素定位) | 根据定位判断目元素是否未显示 | |
invisibility_of_element | webElement(目标元素) | 判断目标元素是否未显示 | |
判断元素的状态、文本、值 | element_to_be_clickable | locater(目标元素定位) | 根据定位判断目标元素是否处于可单击状态(已显示且未被禁用) |
element_located_to_be_selected | locater(目标元素定位) | 根据定位判断目标元素是否处于已选中状态 | |
element_to_be_selected | webElement(目标元素) | 判断目标元素是否处于已选中状态 | |
element_located_selection_state_to_be | locater(目标元素定位),is_selected(期望的状态),可以传入True(选中)或是Flase(未选中) | 根据定位判断目标元素的选中状态是否符合预期 | |
element_selection_state_to_be | webElement(目标元素),is_selected(期望的状态),可以传入True(选中)或是Flase(未选中) | 判断目标元素的选中状态是否符合预期 | |
text_to_be_present_in_element | locater(目标元素定位),text(期望包含的文本) | 判断目标元素的文本是否已包含期望的文本 | |
text_to_be_present_in_element_value | locater(目标元素定位),text(期望包含的文本) | 判断目标元素的Value属性是否已包含期望的文本 | |
判断元素是否存在 | presence_of_element_located | locater(目标元素定位) | 根据定位判断页面上是都存在某个符合定位的元素,如果有(但未必可见),则返回WebElement |
presence_of_all_element_located | locater(目标元素定位) | 根据定位判断页面上是否存在一个或多个符合定位的元素,如果有(但未必可见),则返回WebElement集合 | |
staleness_of | webElement(目标元素) | 判断目标元素是否已经从DOM结构上完全消失 | |
判断浏览器窗口、弹窗及内嵌网页 | new_window_is_opened | current_handles(当前窗口句柄集合) | 判断是否有新窗口打开,句柄数量是否会在当前句柄集合的基础上有所增加 |
number_of_windows_to_be | num_windows(预期窗口数量) | 判断当前窗口数量是否等于预期窗口数量 | |
frame_to_be_available_and_switch_to_it | locater(目标元素(IFrame/Frame)定位) | 根据定位判断答复可以切换到目标元素,如果可以切换,则会直接切换到指定元素IFrame/Frame上 | |
alert_is_present | 判断是否已出现浏览器弹出框 | ||
判断网页标题或URL | title_contains | title(期望包含的标题) | 判断网页标题是否已包含期望的标题(即是否模糊判断) |
title_is | title(期望包含的标题) | 判断网页标题是否完全等于期望的标题(即是否完全匹配) | |
url_changes | url(当前的URL) | 判断URL是否发生变化,即与当前的URL不相同 | |
url_contains | url(期望包含的URL) | 判断URL是否已包含期望的URL(即是否完全匹配) | |
url_matches | pattern(格式表达式) | 判断URL是都匹配格式表达式(例如正则表达式) | |
url_to_be | url(期望相等的URL) | 判断URL是否已包含期望的URL(即是否完全匹配) |
合理使用这些函数,可以达到事半功倍的效果。