Python自动化测试:web自动化测试——selenium API、unittest框架的使用
web自动化测试2
- 1. 设计用例的方法——selenium API
- 1.1 基本元素定位
- 1)定位单个唯一元素
- 2)定位一组元素
- 3)定位多窗口/多框架
- 4)定位连续层级
- 5)定位下拉框
- 6)定位div框
- 1.2 基本操作
- 1.3 等待
- 1.4 浏览器操作
- 1.5 鼠标事件
- 1.6 键盘事件
- 1.7 弹窗处理
- 1.8 上传文件操作
- 2. unittest介绍
- 3. unittest 框架的使用示例
- 1)测试固件的编写
- 2)单元测试用例——多场景设计测试用例
- 测试数据导入
- 跳过某用例
- 测试用例断言
- 测试结果截图
- 3)测试套件组合与执行
- 套件组合-借助装载器:defaultTestLoader、TestLoader
- 套件组合-借助addTest函数
- 套件组合-借助makeSuit
- 套件执行-借助TextTestRunner
- 4)测试报告输出
前言:使用Selenium框架进行简单web端UI自动化测试,简单的以百度搜索为例,复杂的模拟访问12306登陆、购票操作。
1. 设计用例的方法——selenium API
1.1 基本元素定位
web端-浏览器右键-检查,可以查看各个元素的id、class、name、text、XPath等,需要唯一才可精准定位;
适用selenium 4.6以上语法如下:
1)定位单个唯一元素
- driver.find_element(By.XPATH,‘XPATH’)——XPath路径如上图获取,是唯一的;
- driver.find_element(By.CLASS_NAME,‘CLASS_NAME’)——用Class名称查找;
- driver.find_element(By.CSS_SELECTOR,‘CSS_SELECTOR’)——用CSS选择器查找;
- driver.find_element(By.ID,‘ID’)——用ID查找;
- driver.find_element(By.LINK_TEXT,‘LINK_TEXT’)——用永超链接查找;
- driver.find_element(By.PARTIAL_LINK_TEXT,‘PARTIAL_LINK_TEXT’)——用部分超链接查找;
- driver.find_element(By.TAG_NAME,‘TAG_NAME’)——用标签名查找;
2)定位一组元素
- switch_to.frame("框架’')——定位到页面所有input框;
inputs=driver.find_elements(By.TAG_NAME, "input") for input in inputs: # 遍历定位到的input,若元素为单选框,则点击选中 if input.get_attribute('type')=='checkbox': input.click()
3)定位多窗口/多框架
- switch_to.frame("框架id’')——定位到某层级内的框架;
- switch_to.default_content()——返回默认/最外层界面;
举例:
import switch as switch #转换层级 driver.switch_to.frame("f1") driver.switch_to.frame("f2") #要想从f2回到f1,要先回到默认界面 driver.switch_to.default_content() driver.switch_to.frame("f1")
4)定位连续层级
备注:若要定位的元素需要进行一系列操作才展示,那需要我们一层层去定位;
driver.find_element(By.ID, "元素ID").find_element(By.ID, "下一层级才能看到的元素ID")
5)定位下拉框
元素类型为:一月
定位并选择十月份:
方法1:option[value]
#定位到下拉框,注意elements的复数形式 options = driver.find_element(By.CLASS_NAME,"整个日历月份下拉框class名").find_elements(By.TAG_NAME, "option") for option in options: if option.get_attribute('value') == '10': option.click() # 第二种方法option[10].click
6)定位div框
备注:若页面元素太多,利用元素无法精准定位,可以先定位到某div框,在从该div框里去定位:
先定位到DIV1这个模块,在对模块上的元素进行操作 div1=driver.find_element(By.CLASS_NAME, "class名") div1.find_element(By.ID, "ID").click() #如果这个模块上多个button,还可以使用这样的方法 div1=driver.find_element(By.CLASS_NAME, "class名") buttons=div1.find_element(By.ID, "ID") button[0].click()
1.2 基本操作
(1)点击按钮:.click()
举例:
# 通过元素ID 定位到“百度一下”的按钮,点击“百度一下” driver = webdriver.Chrome() driver.get('http://www.baidu.com/') driver.find_element(By.ID, "su").click()
(2)模拟写入对象/元素的内容:.send_keys(“xxxx”)
举例:百度-搜索框输入“孙俪”-点击“百度一下”
driver.find_element(By.ID, "kw").clear() driver.find_element(By.ID, "kw").send_keys("孙俪") driver.find_element(By.ID, "su").click()
(3)模拟清空元素/对象的内容:.clear()
(4)提交表单:.submit()
备注:要求元素为表单类型才可使用
举例:也可通过表单方式提交.submit() == .click() == 点击“百度一下”
(5)用于获取元素的文本信息:.text ()
text=driver.find_element(By.XPATH,'//*[@id="s-top-left"]/a[1]').text print(text)
(6)获取输入框元素内容/值:.get_attribute(‘value’)
driver.find_element(By.ID,"kw").send_keys("selenium") qq=driver.find_element(By.ID,'kw').get_attribute('value') print(qq)
结果:打印 输入框的值-输出 selenium
1.3 等待
(1)强制等待:time.sleep(2)——休眠2s
(2)智能等待:driver.implicitly_wait(5) ——智能等待最长5s
import time from selenium import webdriver driver = webdriver.Firefox() driver.implicitly_wait(5) time.sleep(2)
1.4 浏览器操作
(1)浏览器最大化:driver.maximize_window()
(2)设置浏览器高、宽:driver.set_window_size(500,500)
(3)浏览器后退:driver.back()
(4)浏览器前进:driver.forward()
(5)浏览器滚动条置顶与置底:(借助执行JS语句,如下示例所示)
import time from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Firefox() driver.get('http://www.baidu.com/') driver.maximize_window() #设置浏览器窗口为(500,500) driver.set_window_size(500,500) time.sleep(1) #设置浏览器窗口最大化 driver.maximize_window() driver.implicitly_wait(2) driver.find_element(By.ID,"kw").send_keys("selenium") driver.find_element(By.ID,"su").click() driver.implicitly_wait(3) #将页面滚动条拖到底部 js = "var q=document.documentElement.scrollTop=10000" driver.execute_script(js) time.sleep(3) #将页面滚动条拖到顶部 jjs="var q=document.documentElement.scrollTop=0" driver.execute_script(jjs) time.sleep(3)
1.5 鼠标事件
需from selenium.webdriver.common.action_chains import ActionChains
(1)context_click() 右击
(2)double_click() 双击
(3)drag_and_drop() 拖动
(4)move_to_element() 移动
- ActionChains(driver)
生成用户的行为。所有的行动都存储在actionchains 对象。通过perform()存储的行为。
- move_to_element(menu)
移动鼠标到一个元素中,menu 上面已经定义了他所指向的哪一个元素
- perform()
执行所有存储的行为
qq=driver.find_element(By.ID,"kw") ActionChains(driver).context_click(qq).perform() #右键 ActionChains(driver).double_click(qq).perform() #双击
1.6 键盘事件
需from selenium.webdriver import Keys
(1)快捷回车-enter键:.send_keys(Keys.ENTER) 等价于点击按钮
import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver import Keys driver = webdriver.Firefox() driver.get('http://www.baidu.com/') driver.maximize_window() driver.find_element(By.ID, "kw").clear() driver.find_element(By.ID, "kw").send_keys("孙俪") driver.find_element(By.ID, "su").send_keys(Keys.ENTER)
(2)切换焦点-Tab键:.send_keys(Keys.TAB)
作用:将元素定位位置由当前元素切换至下一个元素
(3)输入框内容-全选与剪切-组合键:全选、剪切、复制、粘贴
#ctrl+a 全选输入框内容 send_keys(Keys.CONTROL,'a') #ctrl+x 剪切输入框内容 send_keys(Keys.CONTROL,'x') #ctrl+c 复制输入框内容 .send_keys(Keys.CONTROL,'c') #ctrl+v 粘贴输入框内容 .send_keys(Keys.CONTROL,'v')
(4)输入空格:.send_keys(Keys.SPACE)
(5)单个删除:.send_keys(Keys.BACK_SPACE)
1.7 弹窗处理
(1)Alert弹窗:只有信息及确认按钮
(2)Confirm弹窗:在Alert弹窗基础上增加了取消按钮
(3)Prompt类型弹框:在Confirm的基础上增加了可输入文本内容的功能
driver.switch_to.alert.accept() #确定、同意;三种弹窗都可使用 driver.switch_to.alert.dismiss() #取消、不同意;confirm和prompt弹窗中使用 title = driver.switch_to.alert.text #打印弹窗信息 alert = driver.switch_to.alert #获取alert对象 alert.send_keys() #Prompt弹窗中输入内容
1.8 上传文件操作
driver.find_element(By.CLASS_NAME, "class名").send_keys("文件路径")
2. unittest介绍
- unittest是Python自带的一个单元测试框架, 它可以做单元测试,提供了去创建测试用例的方法,并能用于编写和运行重复的测试工作;
- 可以利用unittest创建一个类,该类集成unittest的TestCase,其中每个case作为一个最小单元,由测试容器组织起来,统一执行并引入测试报告输出结果;
- test fixture:初始化与清理测试环境。如创建临时的数据库、文件/目录,其中如setUp()用于启动浏览器驱动、setDown()用于关闭游览器驱动等统一操作;
- test case:单元测试用例,在类TestCase中设计编写测试用例;
- test suite:单元测试用例集合,将不同的测试用例封装至类TestSuite中; test runner:执行单元测试用例;
- test report:生成测试报告;
3. unittest 框架的使用示例
- 前置:资源导入:
import csv #用于解析data数据 import sys #用于访问本地资源路径 from selenium import webdriver import time import os #用于输出 import unittest #用于使用unittest资源 from ddt import ddt, data, file_data, unpack #用于data数据读取与输入操作 from unittest import TestLoader #用于输出测试报告
1)测试固件的编写
- 每个测试用例都需要包含测试固件:包含基本的setUp()、tearDown()等统一操作;
def setUp(self): print("------setUp") self.driver=webdriver.Firefox() self.url="https://www.baidu.com" self.driver.maximize_window() time.sleep(3) def tearDown(self): print("-------tearDown") self.driver.quit()
2)单元测试用例——多场景设计测试用例
- 单元测试:可以是同一个场景设计多个测试用例;
示例:该单元测试场景——模拟每次进入百度首页-点击进入不同标签页面
from selenium import webdriver import os import time import unittest class Test1(unittest.TestCase): #绑定浏览器驱动,设置url def setUp(self): print("-----setUp") self.driver=webdriver.Firefox() self.url="https://www.baidu.com" self.driver.maximize_window() time.sleep(3) #测试用例执行结束进行清理,关闭浏览器驱动 def tearDown(self): print("-----tearDown") self.driver.quit() #编写测试用例 def test_xxx: def test_baidu1(self): driver=self.driver url=self.url driver.get(url) driver.find_element_by_link_text("hao123").click() time.sleep(2) def test_baidu2(self): driver=self.driver url=self.url driver.get(url) driver.find_element_by_link_text("图片").click() time.sleep(2) def test_baidu3(self): driver=self.driver url=self.url driver.get(url) driver.find_element_by_link_text("地图").click() time.sleep(3) if __name__ == "__main__": unittest.main()
示例:该单元测试场景——模拟百度搜索功能(对于不同数据类型)
测试数据导入
@data(不带的列表)会将整个列表作为参数传入
@data(带的列表)会将整个列表的子元素作为参数逐个传入,可将二维列表的元素逐个传入,一个元素一个case
@unpack 将要传入的元素解包后传入,将二维列表的元素逐个传入,便于一个测试用例中使用一组数据中的多个不同变量;
首先需要了解数据导入的方法:
备注:数据导入可以选择txt文件、json文件格式、或直接导入数据数组;其中dataTest.py为某单元测试文件;
1)读取txt文件
- 需要引入data数据包-并要设置读取方法;
def getTxT(file_name): rows = [] path = sys.path[0] with open(path + '/data/' + file_name, 'rt',encoding='UTF-8') as f: readers = csv.reader(f, delimiter=',', quotechar='|') next(readers, None) for row in readers: temprow = [] for i in row: temprow.append(i) rows.append(temprow) return rows
- 给该单元测试的类和某测试用例设置@ddt与@data修饰器
@data(*getTxT("baidu_data.txt"))
2)读取json文件
- 格式如下:
[ "hao123", "图片", "地图" ]
3)直接在某用例上输入数组
- 格式如下:
@data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"])
跳过某用例
想跳过某单元用例中的某用例,则添加如下代码至某用例方法上即可:
@unittest.skip("skipping") #表示跳过该测试用例
测试用例断言
unittest单元测试框架提供了一整套内置的断言方法:
1)如果断言失败,抛出AssertionError的错误,case为失败状态
2)如果断言成功,会标识case为成功状态
方法 检查 描述 assertEqual(a, b) a == b 验证a是否等于b assertNotEqual(a, b) a != b 验证a是否不等于b assertTrue(x) bool(x) is True 验证x是否为ture assertFalse(x) bool(x) is False 验证x是否为flase assertIs(a, b) a is b 验证a,b是否为同一个对象 assertIsNot(a, b) a is not b 验证a,b不是同一个对象 assertIsNone(x) x is None 验证x是否是None assertIsNotNone(x) x is not None 验证x是否非None assertIn(a, b) a in b 验证a是否是b的子串 assertNotIn(a, b) a not in b 验证a是否非b的子串 assertIsInstance(a, b) isinstance(a, b) 验证a是否是b的实例 assertNotIsInstance(a, b) not isinstance(a, b) 验证a是否不是b的实例 举例:百度搜索某字段,判断标题是否一致,不一致则断言失败,打印结果:标题不相等!
@data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"]) def test_baidu7(self, value, title): driver = self.driver url = self.url driver.get(url) driver.find_element_by_id("kw").send_keys(value) driver.find_element_by_id("su").submit() time.sleep(5) print(driver.title) self.assertEqual(title, driver.title, msg="标题不相等!")
测试结果截图
可以设置保存截图的方法,根据断言结果调用截图方法并保存截图:
1)设置截图保存方法
def savescreenshot(self, driver, file_name): if not os.path.exists('./image'): os.makedirs('./image') now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time())) # 截图保存 driver.get_screenshot_as_file('./image/' + now + '-' + file_name) time.sleep(1)
2)根据断言抛出异常,调用截图方法
def test_baidu4(self): driver=self.driver url=self.url driver.get(url) driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys("孙俪") driver.find_element_by_id("su").click() time.sleep(3) print(driver.title) try: self.assertEqual("孙俪_百度搜索", driver.title, msg=None) self.assertNotEqual("孙俪_百度搜索",driver.title,msg=None) except: self.savescreenshot(driver,"sunli.png")
3)截图:百度搜索“孙俪”
结果保存至项目文件的/image文件夹中
3)测试套件组合与执行
套件组合-借助装载器:defaultTestLoader、TestLoader
将单元测试文件中的某类测试用例塞入测试套件中,创建一个测试用例组合套件的方法:
def createSuit(): # 添加不同测试用例到套件里 testSuit=unittest.defaultTestLoader.discover("../py2Unittest",pattern="test*.py",top_level_dir=None) return testSuit
注:此方法可以把一个文件夹下面所有的满足test*.py命名规则的测试脚本中的测试用例放入测试套件
套件组合-借助addTest函数
可以将某单元测试文件中的某个用例塞入测试套件中:
注:这样放需要在当前测试套件脚本中引入此处的单元测试脚本,否则addTest()会报错
import test1 import test2 def creatSuit(): #要把不同的测试脚本的类中的需要执行的方法放在一个测试套件中 suit = unittest.TestSuite() suit.addTest(test1.Test1("test_baidu1")) # test_baidu1为某测试用例方法的名称 suit.addTest(test2.Test2("test_baidu3")) return suit
套件组合-借助makeSuit
利用makeSuit不需要导入单元测试用例文件
def creatSuit(): #如果我需要把一个测试脚本中所有的测试用例都添加到suit中-实际将整个单元测试脚本中的类都加入套件中实现 # makeSuit suit = unittest.TestSuite() suit.addTest(unittest.makeSuite(test1.Test1)) suit.addTest(unittest.makeSuite(test2.Test2)) return suit
套件执行-借助TextTestRunner
举例:
将测试套件执行并打印测试结果:
import unittest from unittest import TestLoader def creatSuit(): #TestLoader suit1 = unittest.TestLoader().loadTestsFromTestCase(test1.Test1) suit2 = unittest.TestLoader().loadTestsFromTestCase(test2.Test2) suit = unittest.TestSuite([suit1, suit2]) return suit if __name__ == "__main__": suit = creatSuit() # verbersity= 0, 1, 2 runner = unittest.TextTestRunner(verbosity=2) runner.run(suit)
注:unittest.TextTestRunner的verbosity参数用于控制测试运行时的详细程度。它可以接受的值如下:
0: 静默模式,不输出任何信息。
1: 默认模式,输出每个测试方法的简要摘要和总体摘要(通过、失败、错误等)。
2: 详细模式,输出每个测试方法的详细执行结果,包括测试方法的名称、运行时间、状态等信息。
4)测试报告输出
测试套件&执行&测试报告输出脚本程序入口的设置:
if name ==“main”:
1.设置测试结果文件输出路径
2.设置结果文件名
3.创建并打开文件,执行测试套件,写入测试结果,保存文件
举例:
单元测试脚本:
import csv import sys # -*- coding: utf-8 -*- from selenium import webdriver import os import time import unittest from ddt import ddt, data, file_data, unpack from selenium.webdriver.common.by import By def getTxT(file_name): rows = [] path = sys.path[0] with open(path + '/data/' + file_name, 'rt',encoding='UTF-8') as f: readers = csv.reader(f, delimiter=',', quotechar='|') next(readers, None) for row in readers: temprow = [] for i in row: temprow.append(i) rows.append(temprow) return rows @ddt class Test4(unittest.TestCase): # 绑定浏览器驱动,设置url def setUp(self): print("-----setUp") self.driver = webdriver.Firefox() self.url = "https://www.baidu.com" self.driver.maximize_window() time.sleep(3) # 测试用例执行结束进行清理,关闭浏览器驱动 def tearDown(self): print("-----tearDown") self.driver.quit() # @unittest.skip("skipping") # 注释表示跳过该测试用例 @file_data("data_baidu.json") def test_baidu6(self, value): driver = self.driver url = self.url driver.get(url) driver.find_element(By.LINK_TEXT,value).click() time.sleep(3) # @unittest.skip("skipping") @unpack @data(*getTxT("baidu_data.txt")) # @data(["hao123", "hao123"], [u"视频", u"视频_百度搜索"]) def test_baidu7(self, value, title): driver = self.driver url = self.url driver.get(url) driver.find_element(By.ID,"kw").send_keys(value) driver.find_element(By.ID,"su").submit() time.sleep(5) print(driver.title) self.assertEqual(title, driver.title, msg="标题不相等!")
套件组合与执行&报告输出脚本:
import HTMLTestRunner # 基于html文件的测试执行与报告输出资源模块 import os import sys import time import unittest def createSuit(): # 添加不同测试用例到套件里 testSuit=unittest.defaultTestLoader.discover("../py2Unittest",pattern="dataTest.py",top_level_dir=None) return testSuit if __name__ =="__main__": if not os.path.exists("./result"): os.makedirs("./result") now = time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) print(now) fileName="./result/"+now+"result.html" with open(fileName, "w") as fp: runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"测试报告", description=u"测试用例执行情况",verbosity=2) suit = createSuit() runner.run(suit)
若文件读写时 with open(fileName, “wb”) as fp:可能会报错
不要慌,可以查看文件读取与写入规则,主要是使用write函数时报错,检查open函数,发现参数写为‘wb’,即按二进制write,所以后面出现TypeError: a bytes-like object is required, not 'str’的报错,将此次的参数修改为w后,可以按照字符串输入,编译通过,write的结果正确(w+具有读写属性,写的时候如果文件存在,会被清空,从头开始写)
结果截图:
测试报告:
- 格式如下:
- 格式如下:
- 给该单元测试的类和某测试用例设置@ddt与@data修饰器
- 需要引入data数据包-并要设置读取方法;
- 单元测试:可以是同一个场景设计多个测试用例;
- 每个测试用例都需要包含测试固件:包含基本的setUp()、tearDown()等统一操作;
- ActionChains(driver)
- switch_to.frame("框架’')——定位到页面所有input框;