import logging
from core.keywords import Keywords
from utils.Keywords_utils import kw_step
class AssertKeywords(Keywords):
@kw_step
def assert_url(self, step):
"""URL断言"""
expected_url = step['data']
actual_url = self.driver.current_url
# assert的参数2, 断言失败时,抛出异常 AssertException: 参数2信息, 如果没有捕获异常, 会导致后续步骤不执行
assert expected_url in actual_url, f"❌当前URL: {actual_url} 不包含 预期URL: {expected_url}"
logging.info(f"✅当前URL: {actual_url} 包含 预期URL: {expected_url}")
# 这样可以捕获异常, 不影响后续步骤的执行
# try:
# assert expected_url in actual_url
# logging.info(f"✅当前URL: {actual_url} 包含 预期URL: {expected_url}")
# except AssertionError:
# logging.error(f"❌当前URL: {actual_url} 不包含 预期URL: {expected_url}")
@kw_step
def assert_title(self, step):
"""title断言"""
expected_title = step['data']
actual_title = self.driver.title
assert expected_title in actual_title, f"❌当前title: {actual_title} 不包含 预期title: {expected_title}"
logging.info(f"✅当前title: {actual_title} 包含 预期title: {expected_title}")
@kw_step
def assert_text(self, step):
"""text断言"""
expected_text = step['data']
actual_text = self.find(step).text
if expected_text is None or expected_text == "":
raise ValueError("❌断言失败:Excel中断言预期内容为空,请填写断言文本")
assert expected_text in actual_text, f"❌当前text: {actual_text} 不包含 预期text: {expected_text}"
logging.info(f"✅当前text: {actual_text} 包含 预期text: {expected_text}")
@kw_step
def assert_alert_text(self, step):
"""alert文本断言"""
alert = self.driver.switch_to.alert
expected_text = step['data']
actual_text = alert.text
assert expected_text in actual_text, f"❌当前text: {actual_text} 不包含 预期text: {expected_text}"
logging.info(f"✅当前text: {actual_text} 包含 预期text: {expected_text}")
@kw_step
def assert_element_exists(self, step):
"""元素存在断言"""
element = self.find(step)
assert element, f"❌元素不存在: {element}"
logging.info(f"✅元素存在: {element}")import logging
import os
from tkinter.tix import Select
import select
import time
import allure
from selenium.common import TimeoutException
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.ie.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from utils.Keywords_utils import kw_step
class Keywords:
def __init__(self, driver):
self.driver = driver
# 基础操作
# 基础操作
def find(self, step):
"""查找元素"""
wait = WebDriverWait(self.driver, 10)
locator = step['by'], step['value']
try:
# 如果索引为 None 则定位单个元素, 反之则定位一组元素
if step["index"] is None:
return wait.until(EC.presence_of_element_located(locator))
else:
return wait.until(EC.presence_of_all_elements_located(locator))[step['index']]
except TimeoutException:
logging.info(f"❌元素定位失败, 元素定位信息为: {locator}")
@kw_step
def open(self, step):
self.driver.get(step['data'])
@kw_step
def input(self, step):
self.find(step).send_keys(step['data'])
@kw_step
def click(self,step):
self.find( step).click()
@kw_step
def wait(self, step):
time.sleep(step['data'])
@kw_step
def shot(self, step):
'''截图'''
png = self.driver.get_screenshot_as_png()
now_time = time.strftime("%Y-%m-%d %H_%M_%S")
allure.attach(
png,
f'第{step["step_num"]}步_{now_time}',
allure.attachment_type.PNG
)
@kw_step
def refresh(self,step):
self.driver.refresh()
@kw_step
def forward(self,step):
self.driver.forward()
@kw_step
def back(self,step):
self.driver.back()
@kw_step
def switch_to_window(self,step):
handles = self.driver.window_handles
self.driver.switch_to.window(handles[step['data']])
# 下拉框选择,通过文本选择,通过值选择
@kw_step
def select_by_text(self,step):
# 处理原生select,通过文本选择
select = Select(self.find(step))
select.select(self.find(step), step['data'])
@kw_step
def select_by_value(self,step):
# 处理原生select,通过值选择
select = Select(self.find(step))
select.select_by_value(step['data'])
@kw_step
def accept_alert(self,step):
self.driver.switch_to.alert.accept()
@kw_step
def dismiss_alert(self,step):
self.driver.switch_to.alert.dismiss()
@kw_step
def scroll(self,step):
'''
滚动到某个绝对位置坐标
step['data'],数据要写成{'x':100,'y':100}
通过eval函数将"{'x':100,'y':100}"转换为字典
'''
position = eval(step['data'])
js = f"window.scrollTo({position['x']},{position['y']})"
self.driver.execute_script(js)
def js_execute(self,step):
'''
执行js代码
step['data'],数据要写成{'x':100,'y':100}
通过eval函数将"{'x':100,'y':100}"转换为字典
'''
js = step['data']
self.driver.execute_script(step["data"])
@kw_step
def double_click(self,step):
'''
双击某个元素
'''
action = ActionChains(self.driver)
action.double_click(self.find(step))
action.perform()
@kw_step
def right_click(self,step):
'''
右击某个元素
'''
action = ActionChains(self.driver)
action.context_click(self.find(step))
action.perform()
@kw_step
def hover(self,step):
'''
鼠标悬停某个元素
'''
element = self.find(step)
action = ActionChains(self.driver)
action.move_to_element(element)
action.perform()
@kw_step
def drag_and_drop(self, step):
"""拖拽"""
# step['data'] 数据要写成 {"by": "xpath", "value": "xxx"} 的形式
source = self.find(step)
# 目标元素数据需要处理
target_dict = eval(step["data"])
target = self.driver.find_element(target_dict["by"], target_dict["value"])
action = ActionChains(self.driver)
action.drag_and_drop(source, target).perform()
@kw_step
def enter(self, step):
# 回车
element = self.find(step)
element.send_keys(Keys.ENTER)
@kw_step
def upload(self, step):
"""上传文件"""
# 相对路径转化为绝对路径
relative_path = step['data']
absolute_path = os.path.abspath(relative_path)
element = self.find(step)
element.send_keys(absolute_path)
# frame操作: 切换到某个frame, 切回主文档
@kw_step
def switch_to_frame(self, step):
"""根据frame元素把焦点切换到某个frame"""
element = self.find(step)
self.driver.switch_to.frame(element)
@kw_step
def switch_to_default_content(self, step):
"""从frame切回到主文档"""
self.driver.switch_to.default_content()
# utils/allure_utils.py
import allure
def allure_init(case):
"""
初始化allure报告信息,设置测试用例的基本属性
Args:
case (dict): 测试用例数据字典
"""
# 设置模块(feature)
if "feature" in case:
allure.dynamic.feature(case["feature"])
# 设置场景(story)
if "story" in case:
allure.dynamic.story(case["story"])
else:
allure.dynamic.story("默认场景")
# 设置标题
if "title" in case:
allure.dynamic.title(case["title"])
else:
allure.dynamic.title(f"用例ID: {case.get('id', '未知ID')}")
# 设置用例ID作为描述
if "id" in case:
allure.dynamic.description(f"用例ID: {case['id']}")
# 添加测试用例参数信息
test_params = {}
if "id" in case:
test_params["用例ID"] = case["id"]
if "feature" in case:
test_params["模块"] = case["feature"]
if "story" in case:
test_params["场景"] = case["story"]
if "title" in case:
test_params["标题"] = case["title"]
if test_params:
allure.dynamic.parameter("测试信息", test_params)import allure
def allure_init(case):
allure.dynamic.feature(case["feature"])
allure.dynamic.story(case["story"])
# allure.dynamic.title(case["title"])
allure.dynamic.title(f'ID:{case["id"]} -- {case["title"]}')import logging
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.edge.service import Service as EdgeService
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from config.config import BROWSER_TYPE, HEADLESS, DRIVER_TYPE, CHROME_DRIVER_PATH, Edge_DRIVER_PATH
def get_driver():
driver = None
if BROWSER_TYPE == 'chrome':
driver = get_chrome_driver()
if BROWSER_TYPE == 'edge':
driver = get_edge_driver()
return driver
def get_chrome_driver():
options = webdriver.ChromeOptions()
options.add_argument("--start-maximized")
options.add_argument("--headless=new") if HEADLESS else None
if DRIVER_TYPE == "local":
service = Service(CHROME_DRIVER_PATH)
else:
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=options)
logging.info("启动Chrome浏览器成功")
return driver
def get_edge_driver():
options = webdriver.EdgeOptions()
options.add_argument("--start-maximized")
options.add_argument("--headless=new") if HEADLESS else None
if DRIVER_TYPE == "local":
service = EdgeService(Edge_DRIVER_PATH)
else:
service = EdgeService(EdgeChromiumDriverManager().install())
driver = webdriver.Edge(service=service, options=options)
logging.info("启动Edge浏览器成功")
return driverimport logging
import allure
def kw_step(func):
'''
装饰器,用于记录allure步骤和日志信息
'''
def wrapper(self,step):
with allure.step(f'第{step["step_num"]}步,{step["step_name"]}'):
# 记录日志
logging.info(f'第{step["step_num"]}步,{step["step_name"]}')
# 返回原函数
return func(self,step)
return wrapper
import openpyxl
def read_excel():
workbook = openpyxl.load_workbook("../data/ui_test_run.xlsx")
data = []
# 先把所有用例数据读取出来,再进行筛选,合格的用例才添加到data中
all_cases = []
# 定义当前用例数据,组织处理每一条用例数据的问题
current_case = None
# worksheet = workbook["Sheet1"]
for worksheet in workbook.worksheets:
keys = [cell.value for cell in worksheet[2]]
for row in worksheet.iter_rows(min_row=3,values_only=True):
dict_data = dict(zip(keys,row))
# 只要id不为none,代表这是一条新的用例
if dict_data['id'] is not None:
# 组织用例的过程
current_case = {
"id": dict_data["id"],
"feature": dict_data["feature"],
"story": dict_data["story"],
"title": dict_data["title"],
"steps" : [{
"step_num" : dict_data["step_num"],
"step_name": dict_data["step_name"],
"keyword" : dict_data["keyword"],
"by": dict_data["by"],
"value": dict_data["value"],
"data": dict_data["data"],
"index": dict_data["index"]
}],
"is_true" : dict_data["is_true"]
}
# 临时存起来
all_cases.append(current_case)
pass
# id为none的情况下,就是用例里面的步骤(提取步骤)
elif current_case is not None:
current_case["steps"].append({
"step_num" : dict_data["step_num"],
"step_name" : dict_data["step_name"],
"keyword": dict_data["keyword"],
"by": dict_data["by"],
"value": dict_data["value"],
"data": dict_data["data"],
"index": dict_data["index"]
})
# 过滤用例数据,只保留is_true为True的值
data = [case for case in all_cases if case["is_true"]]
workbook.close()
return data
import pytest
from selenium import webdriver
from selenium.webdriver.ie.service import Service
@pytest.fixture(scope='function',autouse=True)
def driver_handler():
# 创建浏览器对象
driver = webdriver.Chrome(service=Service(r"D:\Python\stu_practice\driver\chromedriver.exe"))
# yield两个作用:1、暂停并恢复执行 2、返回值但是不终止函数
# 如果想要把返回值传递到测试函数中,最简单的方法就是测试函数显示调用这个夹具
yield driver
# (手动)关闭浏览器
driver.quit()import logging
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import pytest
import allure
from core.assert_Keywords import AssertKeywords
from core.keywords import Keywords
from utils.allure_utils import allure_init
from utils.read_excel import read_excel
class TestRunner:
# 读测试用例文件中的全部数据, 用属性保存即可
data = read_excel()
# 不确定后续是否需要提取(暂时注释)
# all = {}
@pytest.mark.parametrize("case", data)
def test_case(self, case, driver_handler):
# all = self.all
# case = eval(Template(str(case)).render(all))
# 初始化allure报告
allure_init(case)
# 测试用例的描述信息日志
logging.info(f'用例ID:{case["id"]} 模块:{case["feature"]} 场景:{case["story"]} 标题:{case["title"]}')
# 创建关键字对象
keywords = Keywords(driver_handler)
assert_keywords = AssertKeywords(driver_handler)
# 执行每一步
for step in case["steps"]:
# 遍历查找关键字
for i in [keywords, assert_keywords]:
# 使用hasattr判断关键字是否存在
if hasattr(i, step["keyword"]):
func_name = i.__getattribute__(step["keyword"])
func_name(step)
break
# for...else...语法: 如果没有遇到break则执行else中的代码, 如果遇到break则跳过else
else:
raise AttributeError(f'❌未找到关键字:{step["keyword"]}')import os
import pytest
if __name__ == "__main__":
pytest.main(["-vs", "./testcases/test_runner.py", "--alluredir", "./report/json_report", "--clean-alluredir"])
os.system("allure generate ./report/json_report -o ./report/html_report --clean")
先熟悉下我的ui自动化代码框架
最新发布