目录
- 1. 为什么要添加等待?
- 2. 三种等待方式
- 3. 强制(直接)等待
- 4. 隐式等待
- 4.1 隐式等待说明
- 4.2 隐式等待无法解决的问题
- 5. 显式等待
- 5.1 为什么要使用显示等待机制?
- 5.1.1 Html文件加载顺序
- 5.1.2 为什么要使用显示等待机制?
- 5.2 显示等待用到的两个类——WebDriverWait、expected_conditions
- 5.2.1 WebDriverWait
- 5.2.1.1 WebDriverWait的用法
- 5.2.1.2 WebDriverWait的until()和until_not()方法
- 5.2.2 expected_conditions 类
- 5.3 示例
- 6. 三种等待方式总结
前言:
本文为在霍格沃兹测试开发学社中学习到的一些技术写出来分享给大家,希望有志同道合的小伙伴可以一起交流技术,一起进步~ 😘
1. 为什么要添加等待?
添加等待的原因:避免页面未渲染完成后操作,导致的报错。
示例:
#演示代码中未添加任何等待
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
class TestXueQiu:
def setup(self):
# 创建一个字典
desire_cap = {}
# 平台
desire_cap['platform'] = 'Android'
#手机系统版本
desire_cap['platformVersion']= '6.0'
# 设备名
desire_cap['deviceName'] = '127.0.0.1:7555'
# app 包名
desire_cap['appPackage'] = 'com.xueqiu.android'
# app 页面名
desire_cap['appActivity'] = '.common.MainActivity'
desire_cap['noReset'] = 'true'
#每次测试重置数据
desire_cap['skipDeviceInitization']= 'true'
#保证可以正常输入中文
desire_cap['unicodeKeyBoard'] = 'true'
desire_cap['resetKeyBoard'] = 'true'
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire_cap)
def teardown(self):
# 回收session
self.driver.quit()
def test_api_demo(self):
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/tv_search").click()
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/search_input_text").send_keys("alibaba")
self.driver.find_element(AppiumBy.ID,"com.xueqiu.android:id/code").click()
运行结果:运行报错,报错原因是未找到定位的元素
2. 三种等待方式
- 强制等待:
sleep()
(不推荐) - 全局隐式等待:driver定义好之后调用的,使用driver的每步操作都在隐式等待时效内完成,如果时效内未完成抛出错误,服务端等待,每隔0.5s查询一次
- 在服务端等待
driver.impicitly_wait(TIMEOUT)
- 显示等待:客户端等待,只是在局部生效,为某一个元素操作
- 在客户端等待
WebDriverWait(self.drier,10).until(expected_conditions.visibility_of_element_located(LOCATOR))
3. 强制(直接)等待
解决方案:在报错的元素操作之前添加等待
原理:强制等待,线程休眠一定时间
方式:time.sleep(3)
示例:
import time
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
class TestXueQiu:
def setup(self):
# 创建一个字典
desire_cap = {}
# 平台
desire_cap['platform'] = 'Android'
#手机系统版本
desire_cap['platformVersion']= '6.0'
# 设备名
desire_cap['deviceName'] = '127.0.0.1:7555'
# app 包名
desire_cap['appPackage'] = 'com.xueqiu.android'
# app 页面名
desire_cap['appActivity'] = '.common.MainActivity'
desire_cap['noReset'] = 'true'
#每次测试重置数据
desire_cap['skipDeviceInitization']= 'true'
#保证可以正常输入中文
desire_cap['unicodeKeyBoard'] = 'true'
desire_cap['resetKeyBoard'] = 'true'
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire_cap)
def teardown(self):
# 回收session
self.driver.quit()
def test_api_demo(self):
time.sleep(3)
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/tv_search").click()
time.sleep(3)
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/search_input_text").send_keys("alibaba")
time.sleep(3)
self.driver.find_element(AppiumBy.ID,"com.xueqiu.android:id/code").click()
#添加直接等待,每一步操作前会强制等待3秒钟
4. 隐式等待
4.1 隐式等待说明
问题:难以确定元素加载的具体等待时间。
解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置:driver.implicitly_wait(seconds)
原理:隐式等待是一种全局的等待方式,设置一个等待时间,轮询查找(默认 0.5 秒)元素是否出现,如果没出现就抛出异常
示例:
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
class TestXueQiu:
def setup(self):
# 创建一个字典
desire_cap = {}
# 平台
desire_cap['platform'] = 'Android'
#手机系统版本
desire_cap['platformVersion']= '6.0'
# 设备名
desire_cap['deviceName'] = '127.0.0.1:7555'
# app 包名
desire_cap['appPackage'] = 'com.xueqiu.android'
# app 页面名
desire_cap['appActivity'] = '.common.MainActivity'
desire_cap['noReset'] = 'true'
#每次测试重置数据
desire_cap['skipDeviceInitization']= 'true'
#保证可以正常输入中文
desire_cap['unicodeKeyBoard'] = 'true'
desire_cap['resetKeyBoard'] = 'true'
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire_cap)
#添加隐式等待:设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
self.driver.implicitly_wait(3)
def teardown(self):
# 回收session
self.driver.quit()
def test_api_demo(self):
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/tv_search").click()
self.driver.find_element(AppiumBy.ID, "com.xueqiu.android:id/search_input_text").send_keys("alibaba")
self.driver.find_element(AppiumBy.ID,"com.xueqiu.android:id/code").click()
#在setup中添加了隐式等待。
4.2 隐式等待无法解决的问题
无法解决的问题:
- 元素可以找到,使用点击等操作,出现报错。
- 可能会导致脚本执行速度整体变慢
原因:
- 页面元素加载是异步加载过程,通常 xml 会先加载完成,相应的元素属性后加载
- 元素存在与否是由 xml 决定,元素的交互是由属性决定
- 隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互
解决方案:使用显式等待。
5. 显式等待
- 显示等待与隐式等待相对,显示等待必须在每个需要等待的元素前面进行声明。
- 是针对于某个特定的元素设置的等待时间,在设置时间内,默认每隔一段时间检测一次当前某个元素是否存在。
- 如果在规定的时间内找到了元素,则直接执行,即找到元素就执行相关操作。
- 如果超过设置时间检测不到则抛出异常。默认检测频率为0.5s,默认抛出的异常为:NoSuchElementException。
5.1 为什么要使用显示等待机制?
5.1.1 Html文件加载顺序
html文件是自上而下进行加载,并在加载过程完成解析与渲染。大致过程如下:
- 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。遇到图片资源,浏览器也会另外发出一个请求,来获取图片资源。这是异步请求,并不会影响html文档进行加载。但是当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。
- 然后对 CSS 进行解析,生成 CSSOM 规则树(CSS加载和解析过程都不会阻塞 DOM 的解析,但会阻塞 DOM 渲染)。
- DOM和CSSOM全部解析完成后,根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM 元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。
- 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
- 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。
❗ 注意:html加载的过程是逐步完成的,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
5.1.2 为什么要使用显示等待机制?
由于html加载方式,所以过程中内容虽然会呈现在屏幕上,但元素的属性不一定全部被解析。如果此时我们使用元素属性去定位的话,可能会因为属性未被加载好而出现定位失败的情况。
隐式等待可以判断出元素已经出现在dom树中,但是无法盘判断元素属性是否被成功加载。如果使用隐式等待也会出现定位失败的情况。而显示等待可以等待动态加载的ajax元素,因此显示等待可以解决因元素属性未被加载而导致的定位失败。
5.2 显示等待用到的两个类——WebDriverWait、expected_conditions
5.2.1 WebDriverWait
5.2.1.1 WebDriverWait的用法
方法说明: WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
参数说明:
- driver:浏览器驱动。
- timeout:最长超时时间,默认以秒为单位
- poll_frequency:检测的间隔步长,默认是0.5s
- ignored_exceptions:超时后的抛出的异常信息,默认抛出NoSuchElementException异常
5.2.1.2 WebDriverWait的until()和until_not()方法
方法说明:
-
until( method, message='')
:当某元素出现或什么条件成立则继续执行 -
until_not(method, message='')
:当mou元素消失或什么条件不成立则继续执行。
参数说明:
- method:在等待时期,每隔一段时间(__init__中的poll_frequency)调用这个传入的方法,知道返回值不是False。
- message:如果超时,抛出TimeoutException,将message传入异常
5.2.2 expected_conditions 类
-
presence_of_element_located:判断元素是否被加载到dom树里,并不代表元素一定可被定位
用法:WebDriverWait().until(expected_conditions.presence_of_element_located(元素对象))
-
visibility_of_element_located:判断某个元素是否可见,可见代表元素非隐藏,并且元素的宽和高都不等于0.
用法:WebDriverWait().until(expected_conditions.visibility_of_element_located(locator))
-
element_to_be_clickable:判断每个元素是否可点击
用法:WebDriverWait().until(expected_conditions.element_to_be_clickable(locator))
-
……(其他方法可自行去了解)
5.3 示例
示例1:
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
class TestXueQiu:
def setup(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = '127.0.0.1:7555'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = '.view.WelcomeActivityAlias'
desired_caps['noReset'] = 'true'
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
#使用隐式等待
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_xueqiu(self):
"""
1、打开 雪球 app
2、点击搜索输入框
3、像搜索框中输入“阿里巴巴”
4、在搜索结果中选择“阿里巴巴”,然后进行点击
5、获取这只上 阿里巴巴的股价,并判断这只股价的价格<200
6、断言
"""
# 定位搜索框并进行点击
self.driver.find_element(AppiumBy.ID, 'com.xueqiu.android:id/tv_search').click()
# 搜索框输入“阿里巴巴”
self.driver.find_element(AppiumBy.ID, 'com.xueqiu.android:id/search_input_text').send_keys("阿里巴巴")
# 在搜索结果中选择“阿里巴巴”,并且点击
self.driver.find_element(AppiumBy.XPATH,"//*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']").click()
# 获取当前股价
#元组,元素定位方式和定位值
cur_price_el=(AppiumBy.ID, 'com.xueqiu.android:id/current_price')
#使用显示等待,用visibility_of_element_located去判断元素是否加载好
WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located(cur_price_el))
#直接使用cur_price_el。元组需要解包所以使用*cur_price_el
current_price = float(self.driver.find_element(*cur_price_el).text)
# 断言
assert current_price < 200
示例2:until中使用lambda表达式来进行查询元素
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
class TestXueQiu:
def setup(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = '127.0.0.1:7555'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = '.view.WelcomeActivityAlias'
desired_caps['noReset'] = 'true'
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
#使用隐式等待
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_xueqiu(self):
"""
1、打开 雪球 app
2、点击搜索输入框
3、像搜索框中输入“阿里巴巴”
4、在搜索结果中选择“阿里巴巴”,然后进行点击
5、获取这只上 阿里巴巴的股价,并判断这只股价的价格<200
6、断言
"""
# 定位搜索框并进行点击
self.driver.find_element(AppiumBy.ID, 'com.xueqiu.android:id/tv_search').click()
# 搜索框输入“阿里巴巴”
self.driver.find_element(AppiumBy.ID, 'com.xueqiu.android:id/search_input_text').send_keys("阿里巴巴")
# 在搜索结果中选择“阿里巴巴”,并且点击
self.driver.find_element(AppiumBy.XPATH,"//*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']").click()
# 获取当前股价
cur_price_el=(AppiumBy.ID, 'com.xueqiu.android:id/current_price')
#until中不使用expected_conditions类,使用lambda函数来进行判断
WebDriverWait(self.driver,10).until(lambda x:x.find_element(*cur_price_el))
current_price = float(self.driver.find_element(*cur_price_el).text)
# 断言
assert current_price < 200
6. 三种等待方式总结
- 隐式等待:尽量默认都加上,时间限定在3-6s,不要太长,为了所有的find_element方法都有一个很好的缓冲。
- 显示等待:用来处理隐式等待无法解决的一些问题,比如:文件上传。文件上传需要设置20s以上,但是如果设置显示等待,它会在每个find方法都等待这么长时间,一旦发现没找到元素,就会等20s后才抛出异常,影响case的执行效率,这时就需要用显示等待,显示等待可以设置的长一点。
- 强制等待:一般不推荐,前面两种基本能解决大部分问题,如果某个空间灭有任何特征,只能强制等待,这种抢矿比较少。
文末说明:
接口测试中我们很容易混淆Session、cookie和token,你知道他们有什么区别吗?快来跟我一起看,一篇文章让你了解三者的区别。😎
⬇⬇⬇⬇⬇⬇⬇
👍👍👍:接口测试经典面试题:Session、cookie、token有什么区别?