为什么需要设置元素等待?
因为,目前大多数Web应用程序都是使用Ajax和Javascript开发的;每次加载一个网页,就会加载各种HTML标签、JS文件
但是,加载肯定有加载顺序,大型网站很难说一秒内就把所有东西加载出来,不仅如此,加载速度也受网络波动影响
因此,当我们要在网页中做元素定位的时候,有可能我们打开了网页但元素未加载出来,这个时候就定位不到元素,就会报错
所以,我们需要设置元素等待,意思就是:等待指定元素已被加载出来之后,我们才去定位该元素,就不会出现定位失败的现象了
强制等待
sleep()
但是缺点就是:如果指定的时间过长,即使元素已被加载出来了,但还是要继续等,这样会浪费很多时间
强制等待的栗子
import time
from selenium import webdriver
from selenium.common.exceptions import ElementNotVisibleException
from selenium.webdriver.support.wait import WebDriverWait
class Test_Demo():
def setup(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_1(self):
self.driver.get("https://testerhome.com/")
self.driver.find_element_by_link_text('社团').click()
self.driver.find_element_by_link_text('Appium 中文文档小组').click()
# 强行等待3s
time.sleep(3)
self.driver.find_element_by_class_name('node').click()
WebDriver提供了两种类型的等待:显式等待和隐式等待
隐式等待
什么是隐式等待?
如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素
默认等待时间是0秒,隐式等待对整个WebDriver的周期都起作用,所以只要设置一次即可
如何体现隐式等待?
如果在规定时间内,整个网页都加载完成,则执行下一步,否则会抛出异常
隐式等待的弊端
可以把隐式等待当做全局变量,它影响整个页面,所以程序需要等待整个页面加载完成(就是浏览器标签栏那个小圈不再转)时,才会执行下一步【页面加载完成,才能执行下一步】
但可能页面加载未完成的时候,需要定位的元素已经加载完成了,但受限于某些JS文件、图片加载特别慢,我们不能执行下一步,必须得等到网页所有东西都加载完了才能下一步【增加不必要的加载时间】
隐式等待的代码
import time
from selenium import webdriver
from selenium.common.exceptions import ElementNotVisibleException
from selenium.webdriver.support.wait import WebDriverWait
class Test_Demo():
def setup(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
# 隐式等待
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_1(self):
self.driver.get("https://testerhome.com/")
self.driver.find_element_by_link_text('社团').click()
self.driver.find_element_by_link_text('Appium 中文文档小组').click()
ele=WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_class_name('node'))
ele.click()
显式等待
什么是显式等待?
需要定位某个元素的时候,但元素可能不可见,这个时候针对这个元素就可以使用显式等待了
显式等待和隐式等待最大的不同就是:你可以它看成是局部变量,作用于指定元素
显式等待的优势
相比隐式等待,显式等待只对指定元素生效,不再是在整个WebDriver生命周期内生效【仅对元素生效】
可以根据需要定位的元素来设置显式等待,无需等待页面完全加载,节省大量因加载无关紧要文件而浪费掉的时间【针对元素设置,无需等待页面加载完成,节省加载时间】
WebDriverWait,配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。它主要的意思就是:程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException。
显式等待的代码
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.Chrome()
# 访问网址
driver.get("http://www.baidu.com")
# ===显式等待===
# 设置元素等待实例,最多等10秒,每0.5秒查看条件是否成立
element = WebDriverWait(driver, 10, 0.5).until(
# 条件:直到元素加载完成
EC.presence_of_element_located((By.ID, "kw"))
)
WebDriverWait源码解读
class WebDriverWait(object):
def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None):
"""Constructor, takes a WebDriver instance and timeout in seconds.
:Args:
- driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
- timeout - Number of seconds before timing out
- poll_frequency - sleep interval between calls
By default, it is 0.5 second.
- ignored_exceptions - iterable structure of exception classes ignored during calls.
By default, it contains NoSuchElementException only.
wait模块的WebDriverWait类是显性等待类,先看下它有哪些参数与方法:
selenium.webdriver.support.wait.WebDriverWait(类)
init
driver: 传入WebDriver实例,即我们上例中的driver
timeout: 超时时间,等待的最长时间(同时要考虑隐性等待时间)
poll_frequency: 调用until或until_not中的方法的间隔时间,默认是0.5秒
ignored_exceptions: 忽略的异常,如果在调用until或until_not的过程中抛出这个元组中的异常, 则不中断代码,继续等待,如果抛出的是这个元组外的异常,则中断代码,抛出异常。默认只有NoSuchElementException
**until(self, method, message=’’) **
作用:每隔一段时间(上面的poll_frequency)调用method,直到返回值不为False或不为空
method:需要执行的method
message:抛出异常时的文案,会返回 TimeoutException ,表示超时
注
意
:
这
个
才
是
常
用
的
,
如
:
定
位
元
素
直
到
不
返
回
空
\color{red}{注意:这个才是常用的,如:定位元素直到不返回空}
注意:这个才是常用的,如:定位元素直到不返回空
method: 在等待期间,每隔一段时间(__init__中的poll_frequency)调用这个传入的方法,直到返回值不是False
message: 如果超时,抛出TimeoutException,将message传入异常
举
个
栗
子
\color{red}{举个栗子}
举个栗子
import time
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
class Test_Demo():
def setup(self):
self.driver=webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(10)# 隐性等待和显性等待可以同时用,但要注意:等待的最长时间取两者之中的大者
def teardown(self):
self.driver.quit()
def test_1(self):
# 此处的形参x一定要有,用来接参的
def wait(x):
return len(self.driver.find_elements_by_class_name('ember-view'))>1
self.driver.get("https://ceshiren.com/")
WebDriverWait(self.driver,11).until(wait)
上面代码中wait(x)函数的形参一定要有,查看util的源码发现要有一个driver需要传参。
until_not
与until相反,until是当某元素出现或什么条件成立则继续执行,
until_not是当某元素消失或什么条件不成立则继续执行,参数也相同,不再赘述。
看了以上内容基本上很清楚了,调用方法如下(通俗易懂的 WebDriverWait):
WebDriverWait(driver实例, 超时时长, 调用频率, 忽略的异常).until(要调用的方法, 超时时返回的信息)
这里需要特别注意的是until或until_not中的可执行方法method参数,很多人传入了WebElement对象,如下:
WebDriverWait(driver, 10).until(driver.find_element_by_id(‘kw’)) # 错误
这是错误的用法,这里的参数一定要是可以调用的,即这个对象一定有 call() 方法,否则会抛出异常:TypeError: ‘xxx’ object is not callable
上面什么意思?就是让对象可以直接被调用
官方提供的两个小例子
element = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_id("someId"))
is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not(lambda x: x.find_element_by_id("someId").is_displayed())
可以看到,通过匿名函数也是可以的,可以说比后面介绍的 expected_conditions 模块要方便多了
那么有哪些是包含 call() 的对象呢?
模块(接下来重点讲的)
expected_conditions
WebElement的 is_enabled() 、 is_selected() 、is_displayed()
expected_conditions模块
expected_conditions是selenium的一个模块,其中包含一系列可用于判断的条件:
selenium.webdriver.support.expected_conditions(模块)
expected_conditions模块提供了多种定义好的前置条件,需要配合WebDriverWait使用。
等待条件(前置条件) | 简单说明 |
---|---|
以下条件判断元素是否可点击,传入locator | |
element_to_be_clickable(locator) 参数:locator,指一组(By,locator)例如:WebDriverWait(driver,10).until(expected_conditions.element_to_be_clickable((By.NAME,‘11’)))下面的都是以这种方式,只是前置条件不同,传的参数也可能不同WebDriverWait(driver,10).until() | 等待查找的元素可见并且可用, 判断元素是否可见并且能被单击,条件满足返回页面元素对象,否则返回Flase |
以下四个条件判断元素是否被选中,第一个条件传入WebElement对象,第二个传入locator元组,第三个传入WebElement对象以及状态,相等返回True,否则返回False,第四个传入locator以及状态,相等返回True,否则返回False | |
element_to_be_selected(object) | 等待直到元素被选中,传入参数是一个webdriver实例对象result=WebDriverWait(driver,10).until(expected_conditions.element_to_be_selected(driver.find_element_by_id('kw'))) |
element_located_to_be_selected(locator) | 期望某个元素处于被选中状态 参数传入的是一个定位器locator是一个元组类型 result=WebDriverWait(driver,10).until(expected_conditions.element_located_to_be_selected((By.ID,'kw'))) |
element_selection_state_to_be(driverObject,state) | 判断给定的元素是否被选中 第一个传入参数是一个webdriver对象,第二个参数是期望的元素的状态,True表示选中状态,Flase表示未选中 result=WebDriverWait(driver,10).until(expected_conditions.element_selection_state_to_be((driver.find_element_by_id('kw')),True)) |
element_located_selection_state_to_be(locator,state) | 判断一个元素的状态是否是给定的选择状态 第一个传入参数是一个定位器,定位器是一个元组(by,path);第二个传入参数表示期望的元素状态,True表示选中状态,Flase表示未选中 result=WebDriverWait(driver,10).until(expected_conditions.element_located_selection_state_to_be((By.ID,'kw'),True)) |
以下两个条件验证元素是否出现,传入的参数都是元组类型的locator,如(By.ID, ‘kw’) 顾名思义,一个只要一个符合条件的元素加载出来就通过;另一个必须所有符合条件的元素都加载出来才行 | |
presence_of_all_elements_located(locator) | 等待至少有一个定位器查找的元素出现在网页中,返回一组元素;判断页面至少有一个如果元素出现,如果满足条件,返回所有满足定位表达式的页面元素 如果用这个条件类,必须等所有匹配到的元素都加载出来才通过 result12=WebDriverWait(driver,10,0.2).until(EC.presence_of_all_elements_located((By.ID,'kw'))) |
presence_of_element_located(locator) 传入的参数是元组类型的locator,如(By.ID, ‘kw’) | 等待定位器查找的元素出现在网页中,或者可以在DOM中找到,返回一个被定位到的元素 ;判断某个元素是否存在DOM中,不一定可见,存在返回该元素对象result12=WebDriverWait(driver,10,0.2).until(EC.presence_of_element_located((By.ID,'kw'))) 该条件验证元素是否出现,传入的参数都是元组类型的locator,如(By.ID, ‘kw’) |
以下两个条件判断某段文本是否出现在某元素中,一个判断元素的text,一个判断元素的value | |
text_to_be_present_in_element_value(locator, text_) 参数:text,指定的文本 | 判断text是否出现在元素的value属性值中result16=WebDriverWait(driver,10,0.2).until(EC.text_to_be_present_in_element_value((By.ID,'kw'),'随便')) |
text_to_be_present_in_element(locator,text) 参数:text,指定的文本 | 等待元素能被定位,并且带有指定的文本信息; 判断文本内容test是否出现在某个元素中,判断的是元素的text result15=WebDriverWait(driver,10,0.2).until(EC.text_to_be_present_in_element(By.TAG_NAME,"p")) |
以下两个条件类验证title,验证传入的参数title是否等于或包含于driver.title | |
title_contains(title) 参数:title,指要校验标题包含的字符串 | 等待网页标题包含指定的字符串,成功时返回True,否则返回false;判断页面title标签的内容包含partial_title,只需要部分匹配即可result17=WebDriverWait(driver,10,0.2).until(EC.title_contains("你就知道")) |
title_is(title) 参数:title,指要校验的标题 | 等待网页标题与预期一致,成功时返回True,否则返回false;判断页面title内容是与传入的title_text内容完全匹配result18=WebDriverWait(driver,10,0.2).until(EC.title_is("百度一下,你就知道")) |
以下三个条件验证元素是否可见,前两个传入参数是元组类型的locator,第三个传入WebElement, 第一个和第三个其实质是一样的 | |
visibility_of_element_located(locator) | 等待元素出现在DOM中,是可见的,并且宽和高都大于0,变为可见的,将返回一个元素;希望某个元素出现在DOM中并且可见,满足条件返回该元素的页面元素对象(同一个)result9=WebDriverWait(driver,10,0.2).until(EC.visibility_of_element_located(driver.find_element_by_id('kw'))) |
invisibility_of_element_located(locator) | 等待一个元素在DOM中不可见 或不存在;希望某个元素不可见或者不存在DOM中;满足条件返回True,否则返回定位到的元素对象result8=WebDriverWait(driver,10,0.2).until(EC.invisibility_of_element_located(By.ID,'kw')) |
visibility_of(element) 参数:element,指一个元素 | 希望某个元素出现在页面的DOM中,并且可见,满足条件返回该元素的页面元素对象result10=WebDriverWait(driver,10,0.2).until(EC.visibility_of(driver.find_element_by_id('kw')) |
以下条件判断是否有alert出现 | |
alert_is_present() | 判断是否存在警告窗口 |
以下条件判断frame是否可切入,可传入locator元组或者直接传入定位方式:id、name、index或WebElement | |
frame_to_be_available_and_switch_to_it(parm) | 判断frame是否可用 如果可用返回True并切入到该frame,参数parm可以是定位器locator(by,path)组成的元组,或者定位方式:id、name、index(frame在页面上索引号),或者webelement对象。 result1=WebDriverWait(driver,10,0.2).until(EC.frame_to_be_available_and_switch_to_it(By.ID,'kw')) #传入ID值‘kk’ result2=WebDriverWait(driver,10,0.2).until(EC.frame_to_be_available_and_switch_to_it(driver.find_element_by_id('kw'))) #传入frame的webelement对象 result3=WebDriverWait(driver,10,0.2).until(EC.frame_to_be_available_and_switch_to_it(1)) #传入frame在页面中索引号 |