? ? ? ? PO 模型会增加测试脚本的编写复杂度,尤其是当测试项目规模较大或者业务逻辑较为复杂时,需要编写大量的 Page Object 类,或者一旦我们的项目发生变动甚至更换项目时,就需要大量修改原来的代码,增加了项目的维护成本。关键字模型,更关注的是业务流程,其实很多企业也是如此,我们只需要在excel文件中讲测试用例维护好,而需要编写的脚本量非常小,如果页面有变动,只需要维护excel表格中的用例数据即可,而脚本基本不需要变动。
Keyword Driver Test:关键字驱动测试,建立在pom基础上,解决POM代码封装量太大的弊端。
我们在core目录中新建kdt.py,用于创建kdt类,类里面我们封装一些常用的操作,例如点击、输入、选择等
import time
from pathlib import Path
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
class KeyWord:
"""关键字类:用户操作指令集"""
_split_chr = ';;;'
def __init__(self, driver: Chrome): # 在进行实例化是被自动调用,可以接收参数
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def get(self, url):
"""
跳转指定页面
:param url:
:return:
"""
self.driver.get(url)
def find_element(self, loc: str):
"""
元素定位:自动等待元素出现
:return:
"""
value, *by = loc.split(self._split_chr) # *表示不定长参数, 将loc中通过;;;的符号进行value和by的分割
if not by:
by = By.XPATH # 如果没有指定,默认定位策略是Xpath
else:
by = getattr(By, by[0]) # 通过反射得到By方法
def f(driver):
return self.driver.find_element(by, value)
element = self.wait.until(f)
return element
# 点击
def click(self, loc: str, force=False):
"""
:param loc: 定位表达式 支持几个功能:1.可以用json传输基本数据 2.支持多种定位方法 3. 默认使用XPATH 因此选择loc类型为str
:param force: 是否强制点击
"""
element = self.find_element(loc)
if force:
# 通过js代码实现强制点击
self.driver.execute_script("arguments[0].click()", element)
else:
element.click() # 普通点击
# 输入
def input(self, loc, value, force=False):
element = self.find_element(loc)
if force:
# 通过js代码实现强制输入
self.driver.execute_script(f"arguments[0].value='{value}'", element)
else:
element.send_keys(value) # 普通点击
# 清空
def clear(self, loc, force=False):
element = self.find_element(loc)
if force:
# 通过js代码实现强制清空
self.driver.execute_script(f"arguments[0].value=''", element)
else:
element.clear() # 普通点击
# 进入框架
def iframe_enter(self, loc):
element = self.find_element(loc)
self.driver.switch_to.frame(element)
# 退出框架
def iframe_exit(self):
self.driver.switch_to.default_content()
# 选择
def select(self, loc, text):
"""
下拉选择框选择指定的选项
text:选项的显示文本
"""
element = self.find_element(loc)
Select(element).select_by_visible_text(text)
# 文件上传
def upload(self, loc, file):
"""
:param file: 文件路径
"""
element = self.find_element(loc)
path = Path(file)
path = path.absolute() # 相对路径转绝对路径
element.send_keys(str(path))
def sleep(self, x):
time.sleep(x)
def assert_text(self, loc, text):
"""
断言
:param loc:
:param text:
:return:
"""
element = self.find_element(loc)
assert element.text == text
然后在test_admin.py中新建测试用例,
def test_new_deal_by_kdt(admin_driver):
data = {
'name': '借款1亿买别墅',
'shor_name': '买别墅',
'username': 'beifan',
'cate': '|--房产抵押标',
'upload': r'D:\pythonProject2\temp\code.png',
'type': '个人消费',
'contract': '等额本息合同范本【担保】',
'tcontract': '付息还本合同范本【普通】',
'amount': '100000000',
'rate': '5',
'enddate': '30',
'start_time': '2023-12-25 18:02:02'
}
wd = KeyWord(admin_driver) # 实例化关键字类
wd.iframe_enter('/html/frameset/frame[1]') # 进入框架
wd.click('//*[@id="navs"]/ul/li[2]/a') # 点击贷款管理
wd.iframe_exit() # 退出框架
wd.iframe_enter('//*[@id="menu-frame"]') # 进入框架
wd.click('/html/body/dl[1]/dd[1]/a') # 点击全部贷款
wd.iframe_exit() # 退出框架
wd.iframe_enter('//*[@id="main-frame"]') # 进入框架
wd.click('/html/body/div[2]/div[3]/input[1]') # 点击新增贷款
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[4]/td[2]/input', data['name']) # 贷款名称
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[5]/td[2]/input', data['shor_name']) # 简短名称
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[6]/td[2]/input[1]', data['username']) # 会员名称
wd.click('//strong[text()="beifan"]')
wd.click('//*[@id="citys_box"]/div[1]/div[2]/input[1]') # 城市
# 分类
wd.select('/html/body/div[2]/form/table[1]/tbody/tr[8]/td[2]/select', data['cate'])
# 图片上传
wd.click('/html/body/div[2]/form/table[1]/tbody/tr[14]/td[2]/span/div[1]/div/div/button')
wd.click('/html/body/div[6]/div[1]/div[2]/div/div[1]/ul/li[2]')
wd.upload('//input[@type="file"]', data['upload'])
wd.click('/html/body/div[6]/div[1]/div[3]/span[1]/input')
# 借款用途
wd.select('/html/body/div[2]/form/table[1]/tbody/tr[15]/td[2]/select', data['type'])
# 借款合同范本
wd.select('/html/body/div[2]/form/table[1]/tbody/tr[17]/td[2]/select', data['contract'])
# 转让合同
wd.select('/html/body/div[2]/form/table[1]/tbody/tr[18]/td[2]/select', data['tcontract'])
# 借款金额
wd.clear('/html/body/div[2]/form/table[1]/tbody/tr[19]/td[2]/input')
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[19]/td[2]/input', data['amount'])
# 年利率
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[27]/td[2]/input', data['rate'], force=True)
# 筹标期限
wd.input('/html/body/div[2]/form/table[1]/tbody/tr[28]/td[2]/input', data['enddate'], force=True)
# 借款状态
wd.click('/html/body/div[2]/form/table[1]/tbody/tr[33]/td[2]/label[1]/input')
# 开始时间
wd.input('//*[@id="start_time"]', data['start_time'], force=True)
# 新增提交
wd.click('/html/body/div[2]/form/table[6]/tbody/tr[2]/td[2]/input[4]')
# 系统提示
msg = wd.find_element('/html/body/div/table/tbody/tr[3]/td').text
assert msg == '添加成功'
在tests目录新建一个excel表格,内容如下:
其中admin_driver是用例使用的fixture,不同的用例使用id为0来分辨。
安装openyxl
pip install openpyxl
core目录下新建datas.py封装excel读取的方法
from openpyxl import load_workbook, workbook
def load_case_by_excel(file):
"""从excel中加载用例"""
wb: workbook = load_workbook(file) # 打开文件
# suite_list用来存放所有sheet的名称和对应的用例步骤
suite_list = []
for ws in wb.worksheets:
# case_list用来存放一个sheet中的用例步骤
case_list = []
suite = {
"name": ws.title,
"case_list": case_list
}
suite_list.append(suite)
for line in ws.iter_rows(values_only=True, min_row=2): # 逐行读取单元格的值
# 如果第一个是0,就是用例名称
if line[0] == 0:
case = {
"name": "", # 用例名称
"steps": [] # 用例步骤
}
case_list.append(case)
case['name'] = line[3]
# 如果不是0,就是用例步骤
else:
case['steps'].append(line)
return suite_list
? ? ? ? 在tests目录下创建test_excel.py,从excel中加载数据,根据数据创建pytest可以识别和执行的用例(test_开头的函数)
from core.datas import load_case_by_excel
from core.cases import create_pytest_case
data= load_case_by_excel("D:\\pythonProject2\\tests\\新建 XLSX 工作表.xlsx")
test_list = create_pytest_case(data)
print(test_list)
# 从列表中取出测试用例,保存为全局变量
i = 0
for _test in test_list:
i +=1
globals()[f"test_{i}"] = _test
print(test_list)
? ? ? ? 此处直接将生成allure报告也写了进去,关于allure具体安装方法可以自行百度或者参照我之前在接口自动化的文章。
在core目录下新建cases.py,
import logging
import allure
import pytest
from core.kdt import KeyWord
logger = logging.getLogger()
def handle_name(s):
try:
l = s.index("(")
r = s.index(")")
case_name = s[:l]
fixture_name = s[l + 1:r]
return case_name, fixture_name
except:
return s, "driver"
def handle_step(s):
name = s[1]
key = s[2]
args = []
for arg in s[3:]:
if arg is not None:
args.append(arg)
return name, key, args
def create_pytest_case(suite_list):
"""根据数据,创建pytest可以识别和执行的用例"""
test_list = []
for suite in suite_list:
@allure.feature("web自动化测试平台")
@allure.story(suite["name"])
@pytest.mark.parametrize(
"case",
suite["case_list"],
ids=[case["name"] for case in suite["case_list"]],
)
def test_abc(case, request):
logger.info("测试用例开始执行")
# 根据excel内容,动态调用指定的夹具
# 拿到用例名称
case_name = handle_name(case["name"])[0]
# 拿到夹具名称
fixture_name = handle_name(case["name"])[1]
logger.info(f"用例名称:{case_name=},{fixture_name=}")
driver = request.getfixturevalue(fixture_name)
kw = KeyWord(driver) # 实例化keyword
# 根据excel内容进行关键字调用
for step in case["steps"]:
print(step)
name, key, args = handle_step(step)
logger.info(f"执行关键字{name=},{key=},{args=}")
with allure.step(name):
func = getattr(kw, key) # 通过反射拿到关键字执行函数
func(*args) # 调用关键字函数
logger.info("测试用例结束")
allure.attach(driver.get_screenshot_as_png(), "添加图片")
test_list.append(test_abc)
return test_list
在终端输入?python main.py -k excel -vs来指定运行我们excel测试用例,或者运行main.py
import os
import pytest
if __name__ == '__main__':
os.environ['NO_COLOR'] = '1'
pytest.main()
os.system("allure generate ./temp/allure_results -o report --clean")
可以看到生成了测试报告。