转测试开发之 “web自动化测试” 急速版
环境搭建
python
allure 用来生成测试报告 分为客户端和python依赖 都需要安装
Jenkins 如果需要自动化目前我只了解到用它可以实现
selecnium 帮助我们通过选中浏览器中的元素
pytest 测试框架
执行测试命令python demo.py
这里
python demo.py这不是这一种方案,只是因为我使用了__name__ = "__main__",其他操作文件或者系统的依赖不在叙述自行学习,还有selecnium 不是唯一方案,建议去了解一下 selecnium 和 webdriver的关系
脚本编写
直接上代码,像我这样自己读一下代码行基本上手了
其中keyworld参数是通过 装饰器 @pytest.fixture 在conftest.py中定义,这也是个知识点
我的登录方案其实有点误解了,这里我直接承认,当你认真学了POM和KDT两种模式就明白。
demo.py
import pandas as pd
import pytest
import os
import subprocess
import allure
def attach_screenshot_to_allure(driver, step_name="步骤截图"):
"""
截图并附加到 Allure 报告的辅助函数
driver: WebDriver 实例
step_name: 截图名称
"""
try:
screenshot = driver.get_screenshot_as_png()
allure.attach(
screenshot,
name=step_name,
attachment_type=allure.attachment_type.PNG
)
except Exception as e:
print(f"截图失败: {e}")
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def load_cases_from_excel(path):
df = pd.read_excel(path)
df = df.fillna("")
cases = df.to_dict(orient="records")
return cases
cases = load_cases_from_excel("files/2026-01-12.xlsx")
@pytest.mark.parametrize("case_info", cases)
def test_demo(keywords, case_info):
# 使用 allure 步骤记录测试过程
with allure.step(f"执行关键字: {case_info.get('关键字', '未知')}"):
print("=== 执行到 test_demo 了 ===")
print(case_info["关键字"])
key = case_info.get("关键字", "").strip() # 获取关键字并去除空格
# 检查关键字是否为空
if not key:
allure.attach(
"关键字为空",
name="跳过原因",
attachment_type=allure.attachment_type.TEXT
)
pytest.skip("关键字为空,跳过此用例")
print(case_info)
# 检查方法是否存在
if not hasattr(keywords, key):
allure.attach(
f"Keywords 类没有 '{key}' 方法",
name="跳过原因",
attachment_type=allure.attachment_type.TEXT
)
pytest.skip(f"Keywords 类没有 '{key}' 方法")
# 检查是否登录(需要先检查属性是否存在,避免 AttributeError)
if not hasattr(keywords, 'login_success'):
# 如果还没有执行过 check_login,先不判断
pass
elif keywords.login_success and case_info["登录操作"] != "":
allure.attach(
"已登录状态",
name="跳过原因",
attachment_type=allure.attachment_type.TEXT
)
allure.attach(
str(keywords.login_success),
name="登录状态",
attachment_type=allure.attachment_type.TEXT
)
pytest.skip("已登录,跳过此用例")
# 执行关键字操作
with allure.step(f"调用方法: {key}"):
key_func = getattr(keywords, key) # 用 getattr 更安全
key_func(**case_info)
# 如果执行成功,记录到报告
allure.attach(
str(case_info),
name="执行参数",
attachment_type=allure.attachment_type.TEXT
)
# 截图并附加到报告
attach_screenshot_to_allure(keywords.driver, f"步骤截图-{key}")
if __name__ == "__main__":
try:
allure_results_dir = "allure-results"
allure_report_dir = "allure-report"
os.makedirs(allure_results_dir, exist_ok=True)
# 运行测试(加上日志收集参数,让 allure 能捕获日志)
exit_code = pytest.main([
"-vs",
"web/demo.py",
"--alluredir", allure_results_dir,
"--log-cli-level=INFO", # 收集 INFO 级别及以上的日志
"--log-cli-format=%(asctime)s [%(levelname)s] %(message)s", # 日志格式
"--log-cli-date-format=%Y-%m-%d %H:%M:%S" # 时间格式
])
# 测试完成后,生成并打开 allure 报告
try:
# 生成 HTML 报告
subprocess.run([
"allure", "generate", allure_results_dir,
"-o", allure_report_dir,
"--clean"
], check=True, cwd=PROJECT_ROOT)
# 打开报告(会自动在浏览器打开)
# 注意:allure open 会启动一个服务器,需要手动关闭(Ctrl+C)
print("\n正在打开 Allure 报告...")
print("提示:按 Ctrl+C 可以关闭报告服务器")
subprocess.run([
"allure", "open", allure_report_dir
], check=True, cwd=PROJECT_ROOT)
except KeyboardInterrupt:
# 用户按 Ctrl+C 中断,优雅退出
print("\n\n报告服务器已关闭,程序退出")
except subprocess.CalledProcessError as e:
print(f"生成 allure 报告失败: {e}")
print("请确保已安装 allure 命令行工具")
except FileNotFoundError:
print("未找到 allure 命令,请先安装 allure")
print("安装方法: brew install allure 或手动下载安装")
except KeyboardInterrupt:
# 用户按 Ctrl+C 中断整个程序,优雅退出
print("\n\n程序被用户中断,退出")
exit(0)
conftest.py
# conftest.py
import pytest
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from .extend.Keywords import Keywords
@pytest.fixture(scope="session")
def driver():
options = Options()
# options.add_argument("--headless")
# options.add_argument("--disable-gpu")
# options.add_argument("--no-sandbox")
# options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
yield driver
driver.quit()
@pytest.fixture(scope="session")
def selenium_tools(driver):
"""Selenium 工具类,封装所有依赖"""
class SeleniumTools:
def __init__(self, driver):
self.driver = driver
self.time = time
self.ActionChains = ActionChains
self.WebDriverWait = WebDriverWait
self.EC = EC
self.By = By
return SeleniumTools(driver)
@pytest.fixture(scope="session")
def keywords(driver, selenium_tools):
"""创建 Keywords 实例,注入 driver 和工具依赖"""
return Keywords(driver, selenium_tools)
Keywords.py
# 常用操作
class Keywords:
def __init__(self, driver, tools=None):
self.driver = driver
self.tools = tools # 注入的工具依赖
def open(self, **keyword):
self.driver.get(keyword["数据内容"])
def click(self, **keyword):
"""
点击元素(带等待和滚动)
keyword参数:
- 定位方式: 定位方式(如 By.ID, By.XPATH)
- 目标对象: 定位值
- 等待时间: 可选,默认 10 秒
"""
from selenium.common.exceptions import TimeoutException
locator_type = keyword["定位方式"]
locator_value = keyword["目标对象"]
timeout = keyword.get("等待时间", 10) # 可配置等待时间,默认 10 秒
# 使用注入的工具
wait = self.tools.WebDriverWait(self.driver, timeout)
try:
element = wait.until(self.tools.EC.element_to_be_clickable((locator_type, locator_value)))
except TimeoutException:
# 超时时截图并抛出更详细的错误
screenshot = self.driver.get_screenshot_as_png()
error_msg = (
f"等待元素超时({timeout}秒)\n"
f"定位方式: {locator_type}\n"
f"定位值: {locator_value}\n"
f"当前URL: {self.driver.current_url}"
)
print(error_msg)
# 可以在这里附加截图到 allure(如果需要在报告中显示)
raise TimeoutException(error_msg)
# 滚动到元素位置(确保元素在视口中)
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
self.tools.time.sleep(0.3) # 等待滚动完成
# 尝试普通点击,如果失败则使用 JavaScript 点击
try:
element.click()
except Exception as e:
# 如果普通点击失败,使用 JavaScript 点击
print(f"普通点击失败,使用 JavaScript 点击: {e}")
self.driver.execute_script("arguments[0].click();", element)
def on_input(self, **keyword):
self.driver.find_element(keyword["定位方式"], keyword["目标对象"]).send_keys(keyword["数据内容"])
def wait(self, **keyword):
self.tools.time.sleep(keyword["数据内容"])
def get_screenshot_png(self):
"""获取截图的 PNG 二进制数据(用于附加到报告)"""
return self.driver.get_screenshot_as_png()
def check_login(self, **keyword):
"""
检查元素是否存在
keyword参数:
- 定位方式: 定位方式(如 By.ID, By.XPATH)
- 目标对象: 定位值
返回: True(找到元素)/ False(找不到元素)
"""
from selenium.common.exceptions import NoSuchElementException
try:
self.driver.find_element(keyword["定位方式"], keyword["目标对象"])
self.login_success = False # Python 中布尔值首字母大写
return False
except NoSuchElementException:
self.login_success = True # Python 中布尔值首字母大写
return True
def hover_and_click(self, **keyword):
"""
悬停到元素上,然后点击另一个元素
keyword参数:
- 定位方式: 定位方式(如 By.ID, By.XPATH)
- 悬停对象: 要悬停的元素定位值
- 目标对象: 要点击的元素定位值
"""
# 找到要悬停的元素
hover_element = self.driver.find_element(keyword["定位方式"], keyword["悬停对象"])
# 使用注入的 ActionChains 实现悬停
actions = self.tools.ActionChains(self.driver)
actions.move_to_element(hover_element).perform()
# 等待一下,确保悬停效果生效
self.tools.time.sleep(0.5)
# 点击目标元素(使用等待和滚动)
from selenium.common.exceptions import TimeoutException
locator_type = keyword["定位方式"]
locator_value = keyword["目标对象"]
timeout = keyword.get("等待时间", 10)
wait = self.tools.WebDriverWait(self.driver, timeout)
try:
click_element = wait.until(self.tools.EC.element_to_be_clickable((locator_type, locator_value)))
except TimeoutException:
# 超时时截图并抛出更详细的错误
screenshot = self.driver.get_screenshot_as_png()
error_msg = (
f"悬停后等待点击元素超时({timeout}秒)\n"
f"定位方式: {locator_type}\n"
f"定位值: {locator_value}\n"
f"当前URL: {self.driver.current_url}"
)
print(error_msg)
raise TimeoutException(error_msg)
# 滚动到元素位置
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", click_element)
self.tools.time.sleep(0.3)
# 尝试普通点击,如果失败则使用 JavaScript 点击
try:
click_element.click()
except Exception as e:
# 如果普通点击失败,使用 JavaScript 点击
print(f"普通点击失败,使用 JavaScript 点击: {e}")
self.driver.execute_script("arguments[0].click();", click_element)
总结
祝你好运!
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
推荐文章: