因为只有失败的用例需要截图,那么问题就是: 什么时候用例会失败?
数据驱动测试
我们前面覆盖到的用例都是正常的用例,如果要测试异常的用例呢? 我们来写一下登录的异常 场景:【login_page】
# 用户输入框错误提示
#username_error_tips = (By.XPATH,'//div[text()="账号为4~16位字母、数字或下划
线"]')
# 密码输入框错误提示
#password_error_tips = (By.XPATH,'//div[text()="请输入密码"]')
但是这样两种不同的定位表达式去断言就不方便了,我们希望不管是什么的异常,都可以直接 调用一个方法可以实现;所以我们封装一下方法,统一处理输入框异常的提示断言:
- 都是text= 某个文本,只是文本不一样,所以我们可以把文本参数化一下。到时候用例里是 什么文本,我们就传进去,确认是否存在现实-用is_display方法就可以:
# 统一处理输入框异常提示断言def is_error_tips_display(self, tips_text):locator = (By.XPATH, f'//div[text()="{tips_text}"]')return self.elememnt_display(locator)
添加一个测试方法运行异常的登录用例:
def test_login_failure(open_close_broswer):driver = open_close_broswerHomePage(driver).click_login_link()LoginPage(driver).login("", "lemon123456")# 拿到登录失败提示 后断言assert_condition(LoginPage(driver).is_error_tips_display("账号为4~16位 字母、数字或下划线"))
但是我们异常的用例肯定不止这一条,比如登录失败的用例:
-
用户名过长: 提示错误
-
用户名过短: 提示错误
-
用户名为空,提示错误
-
密码为空,提示错误 等
像这种,输入数据不一样,而导致结果提示不一样来判断,我们可以用什么来执行?--pytest的数据驱动实现。
#字典列表保存登录异常数据
cases_all = [{'username':'','password':'lemon123456','expected':'账号为
4~16位字母、数字或下划线'},{'username':'lemonlemonlemon11','password':'lemon123456','expected':'账
号为4~16位字母、数字或下划线'},{'username':'lem','password':'lemon123456','expected':'账号
为4~16位字母、数字或下划线'},{'username': 'lemon_auto', 'password': '', 'expected': '请输
入密码'}]
@pytest.mark.parametrize('case', cases_all)
def test_login_fail(open_close_broswer, case):driver = open_close_broswerHomePage(driver).click_login_link()LoginPage(driver).login(case['username'], case['password'])# 拿到登录失败提示 后断言assert_condition(LoginPage(driver).is_error_tips_display(case['expected']))
但是如果元素定位的方法跟上面的不一样,能加入一起做数据驱动么? 比如 账号密码错误提 示。
- 不可以,因为方法不一样,不符合数据驱动的原则。数据驱动就是要方法一样 数据不一 样 结果不一样。方法不一样 就不可以数据驱动。
UI自动化测试里数据驱动用的比较少,只有当这种情况才可以做:
- 1、操作步骤是一致的;不同的输入数据 页面不切换,只是提示错误信息不一样 可以用数 据驱动;如果发生了页面的切换,那么元素定位的步骤又变化了,这种就不太适合数据驱 动了。
- 2、断言也是一致的,不需要切换页面和另外做元素定位。
- 所以当用户名密码错误 的提示 这种就不能用数据驱动了,因为元素定位的方式不 一致。要做单独写一条用例去实现
- 像上面账号密码错误用例要测试就需要单独写一条测试用例执行这个场景的用例。
数据驱动的使用场景总结:
- 1、数据驱动测试在接口自动化测试里用的更多: 一个方法 数据不一样 获取结果做不同的断言 就可以实现;
- 2、UI 自动化测试因为每个用例的步骤和断言的预期结果差异很大,所以一般UI 自动化一般都 不太适合做数据驱动,数据驱动在UI自动化用的比较少;
- 所以 UI自动化测试 在更多的时候,不会做全用例覆盖,只会做冒烟测试覆 盖,跑正常的用例,或者做基本的配置测试。
自动化的测试数据管理:
- 1、接口自动化那样具有规则,每个接口的数据都是地址+头部+参数等固定的字段,断言 的方法也可以固定;所以,数据放在excel统一管理,统一读取,统一操作可行性较高;
- 2、UI自动化测试的测试数据会不会放在外部文件【excel】存放,因为不像接口自动化那 样具有规则,UI的用户名 商品 订单名字啥的没有规则;所以会直接放在py文件脚本里进 行维护管理。
失败用例的截图
UI自动化测试,在用例执行失败时,我们想要更加直观的获取现场的有效信息,通过截图是一 种有效的方式。
- 因为有界面的测试,有截图是更加直观的。
- 不需要把每一条用例都截图,只有失败的用例需要定位问题的时候,会去截图;跟我们报 bug一样,失败了截图给开发看。
Seleinum中使用截图有四种方法:我们用第一种和第二种比较多。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
# 第一种方法: 直接截图保存在本地,传递一个参数:png的图片
driver.save_screenshot("test.png")
# 第二种方法: 将截图保存为本地文件, 后缀都是png的格式 跟上面的方法效果一样的
driver.get_screenshot_as_file("t1.png")
# 第三种方法: 将截图返回为二进制数据 不需要传参 -- 用的不多
print(driver.get_screenshot_as_png())
# 第四种方法: 将截图返回为base64编码的数据 不需要传参 -- 用的不多
print(driver.get_screenshot_as_base64())
driver.close()
因为只有失败的用例需要截图,那么问题就是: 什么时候用例会失败?
- 参考我们之前的异常捕获: 之前加了异常捕获的位置基本上都会容易发生异常的和失败 的。
- 所以之前加上异常不会的地方,都可有加上截图。 失败了 直接在根目录 存储一个图片
class BasePage:def __init__(self, driver):"""初始化函数,定义driver为实例属性后面的方法要用就不需要定义参数了,直接调用实例属性即可。"""self.driver = driverdef wait_element_clickable(self, locator):logger.info(f"等待元素{locator}可以点击")try:web_element=WebDriverWait(self.driver,8,0.5).until(EC.element_to_be_clickable(locator))except Exception as e:logger.error(f"等待元素超时{e}")self.driver.save_screenshot("test.png") # 异常了 就截图raise ereturn web_elementdef wait_element_visible(self, locator):# web_element代表通过显示等待找到的元素logger.info(f"等待元素{locator}可见")try:web_element = WebDriverWait(self.driver, 8, 0.5).until(EC.visibility_of_element_located(locator))except Exception as e:logger.error(f"等待元素可见异常了{e}")self.driver.save_screenshot("test.png") # 异常了 就截图raise ereturn web_element
但是这样做有一个问题: 每个图片的名字一样的,会被覆盖掉。所以要每个名字都独立不一样 的
-
可以用时间戳区分别,ms级别的时间戳
import time print(int(time.time() * 1000)) timestamp = f"screenshop_{int(time.time() * 1000)}.png" # 拼接出来图片的名 字 每次都是不一样的 print(timestamp)
还有断言失败 也是要截图的: 所有断言的方法里也要加上截图代码: 但是断言方法里没有driver,可以把driver定义为参数,到时候用例执行的时候存入这个参数。 每一个模块的用例调用断言的方法的时候就加一个driver的参数。
def assert_equals(driver,actual, excepted):try:assert actual == exceptedlogger.info(f'断言成功,预期结果:【{excepted}】,实际结果:【{actual}】')except Exception as e:logger.error(f'断言失败,预期结果:【{excepted}】,实际结果:【{actual}】')driver.save_screenshot(f"screenshop_{int(time.time() *1000)}.png") # 异常了 就截图raise e
截图都成功了,但是都在根目录下,所以我们希望放在outputs下的单独建一个文件夹目录存 放: screenshot,在路径处理方法里加上截图文件夹的路径处理。
截图和allure报告结合 这样写的截图在框架本地,用例比较多,截图也 比较多,通过时间戳找图片也不太直观和方便,我们想要把截图和用例关联起 来,并体现在测试报告里,目前测试报告里有么?
如果想要把失败截图跟用例关联,附上allure测试报告里,怎么办呢?
- 用allure 的附件功能加上就可以。allure.attach()
- allure.attach()把传进去的数据作为allure 的报告的附件添加进去;
- allure的 attch这个方法不能用本地的图片存储传参数进去,接收的是二进 制格式的数据,所以截图需要用 driver.get_screenshot_as_png() 这个方 法,返回结果是二进制数据。
- attach方法可以传几个参数:body, name=None, attachment_type=None
- 1、body:图片的二进制数据: driver.get_screenshot_as_png()
- 2、name:附件图片取个名字:
- 3、attachment_type:附件的类型 可以是png bmp等
- 这样完成后,本地就不会有截图,会在allure文件下产生png的图片,然后 在allure 报告里页面还会展示对应的图片。
- 注意:要用run运行才会产生allure报告
思考: 接口自动化测试需要失败的截图么?
- 不需要,接口都没有截图,只有有界面的测试 才需要截图。 【WebUI,APP测试】
失败了重运行(了解)
像UI自动化测试因为不太稳定,很容易失败,所以有同学总是想要失败后重运 行一下用例。
我们的pytest框架是支持失败重运行的功能的:
- 1、安装与pytest集成的插件:pip install pytest-rerunfailures
- 2、在运行的命令里加一个参数:
pytest --reruns 重试次数 (--reruns-delay 次数之间间隔) pytest --reruns 2 运行失败的用例可以执行2次 pytest --reruns 2 --reruns-delay 5 运行失败的用例可以执行2次,每次间隔5秒 pytest.main([ "-s" , "-v" , "--clean-alluredir" ,f"--alluredir= {report_path}" , "-m p0" , "--reruns" , "2" , "--reruns-delay" , "5" ])
遇到失败的用例,就会自动化重新运行两次。如果重运行通过了 最终的测试报 告里就不会显示失败。
不过失败重运行在工作里不太建议使用:
因为:这实际上是在隐藏问题,而不是暴露问题。测试人员应该关注爆红,而 不是绿色的通过信息, 通过重运行机制,原来产生红色警示的 10几个用例都无 一例外的通过了, 你还会花精力去分析这 10 几个用例失败可能出现的原因 吗?
万一当时确实是在异常的条件下,真的触发了这些用例爆红, 后面因为条件回 复正常,才执行成功呢?如果这种异常条件总是间歇性的出现呢?在这种情况 下,用例重运行隐藏了可能出现的 bug。