开箱即用!Pytest+Selenium+Allure+Flask 搭建企业级 UI 自动化测试平台,附完整可运行代码
目录
1. 通用元素操作封装(operateElement.py)
告别手工回归测试的重复痛点,实现一键触发测试、定时无人值守执行、可视化精美报告、历史趋势全追踪,测试人可直接落地的全栈自动化方案,所有代码复制即可用。
一、为什么要做这套自动化测试平台?
做测试的同学一定都遇到过这些痛点:
- 迭代快、回归场景多,手工点点点耗时耗力,还容易漏测
- 自动化脚本只能本地 / 服务器命令行执行,非技术同学无法操作
- 测试报告零散、不规范,无法追溯历史执行趋势,问题定位难
- 定时回归、夜间巡检需求,无法实现无人值守自动执行
今天这套完整方案,一次性解决以上所有问题:
✅ 基于 PO 设计模式,代码耦合度极低,用例可维护性拉满
✅ 封装通用元素操作,新手也能快速编写业务用例
✅ Web 可视化控制面板,一键执行测试,无需敲任何命令
✅ 深度定制 Allure 报告,自带环境信息、历史趋势、失败自动截图
✅ 内置定时任务调度,轻松实现夜间自动回归、定时巡检
✅ 全量代码开箱即用,替换业务逻辑即可快速落地到自己的项目
二、先看全貌:项目架构与目录结构
先给大家看完整的项目目录结构,清晰的分层是自动化框架可维护的核心:
每个目录的作用一目了然,严格遵循 PO 设计模式的分层思想:
| 目录 / 文件 | 核心作用 |
|---|---|
| business 层 | 业务逻辑封装,比如登录、通用业务流程,减少用例冗余代码 |
| page 层 | 页面元素统一管理,所有元素定位集中存放,一改全改 |
| test_case 层 | 测试用例编写,只聚焦业务场景,不关心底层元素和操作 |
| reports 目录 | 测试原始数据、Allure 报告、历史趋势数据统一存放 |
| templates 目录 | Flask 前端页面模板,存放 Web 控制面板页面 |
| testRunner.py | 框架核心启动文件,集成测试执行、Flask 服务、定时任务全能力 |
三、核心技术栈一览
本方案用到的技术栈都是测试圈主流、稳定、社区活跃的工具,无小众依赖,新手也能快速掌握:
| 技术栈 | 核心作用 |
|---|---|
| Python 3.8+ | 开发语言,自动化测试的首选 |
| Selenium | 主流 UI 自动化工具,支持 Chrome 等多浏览器 |
| Pytest | 最流行的 Python 测试框架,灵活度高、插件生态丰富 |
| Allure | 高颜值测试报告生成工具,支持步骤追溯、截图、趋势统计 |
| Flask | 轻量级 Web 框架,快速搭建可视化控制面板 |
| APScheduler | 定时任务框架,实现后台无人值守自动执行测试 |
| PO 设计模式 | 自动化框架核心设计思想,实现页面、元素、用例解耦 |
四、核心基石:PO 模式 + 通用操作封装,告别维护噩梦
很多人写自动化脚本,最后变成 “一次性脚本”,核心问题就是没有做好分层和封装。这套框架先从底层解决可维护性问题。
1. 通用元素操作封装(operateElement.py)
我们把 Selenium 的高频操作(点击、输入、断言、截图等)全部封装,统一处理显式等待、异常捕获、失败截图、Allure 步骤埋点,不用在每个用例里重复写冗余代码。
完整可直接使用的代码:
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
import allure
class OperateElement:
def __init__(self, driver):
self.driver = driver
def click(self, locator):
"""封装点击操作,自带等待、异常重试、失败截图"""
with allure.step('点击元素'):
try:
WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, locator))).click()
except:
allure.attach(self.driver.get_screenshot_as_png(),name='点击元素失败',attachment_type=allure.attachment_type.PNG)
# 失败重试一次
WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, locator))).click()
def send_keys(self, locator, text):
"""封装输入操作,自带等待、失败截图"""
with allure.step(f'输入内容:{text}'):
try:
WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, locator))).send_keys(text)
except:
allure.attach(self.driver.get_screenshot_as_png(),name='输入内容失败',attachment_type=allure.attachment_type.PNG)
assert False, f"元素{locator}输入失败"
def get_text(self, locator):
"""封装获取文本操作,自带等待、失败截图"""
with allure.step('获取元素文本'):
try:
return WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator))).text
except:
allure.attach(self.driver.get_screenshot_as_png(),name='获取文本失败',attachment_type=allure.attachment_type.PNG)
return WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator))).text
def get_element(self, locator):
"""封装获取元素操作,自带等待、失败截图"""
with allure.step('获取页面元素'):
try:
return WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator)))
except:
allure.attach(self.driver.get_screenshot_as_png(),name='获取元素失败',attachment_type=allure.attachment_type.PNG)
return WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator)))
def clear(self, locator):
"""封装清空输入框操作,自带等待、失败截图"""
with allure.step('清除输入框文本'):
try:
WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator))).clear()
except:
allure.attach(self.driver.get_screenshot_as_png(),name='清除文本失败',attachment_type=allure.attachment_type.PNG)
WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, locator))).clear()
def assert_text(self, xpath, expect_text):
"""封装文本断言,自带等待、截图、详细报错信息"""
time.sleep(1)
with allure.step(f'验证元素包含文本:{expect_text}'):
try:
actual_text = self.get_text(xpath)
assert expect_text in actual_text, f"文本断言失败!预期包含:'{expect_text}',实际文本:'{actual_text}'"
except Exception as e:
allure.attach(self.driver.get_screenshot_as_png(),name='断言文本失败',attachment_type=allure.attachment_type.PNG)
raise
2. 页面元素层(page 层)
把每个页面的元素定位统一管理,彻底告别在代码里到处写 XPATH 的问题。后续页面元素变更,只需要改这一个文件,不用修改上百条用例。
以项目管理页面为例,完整代码(projectManagementPage.py):
class ProjectManagementPage:
# 导航栏元素
projectManagementLi='//*[@id="sidebar-container-wrap"]/div[1]/div[1]/div/ul/li[2]/span'
# 项目相关元素
addProject='//*[@id="layout-classic-wrap"]/div[2]/div[2]/section/div/div[1]/div[1]/div[2]/button[2]'
projectName='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[3]/div/div/div/div/div/div[2]/form/div[1]/div[2]/div/div/input'
projectAddress='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[3]/div/div/div/div/div/div[2]/form/div[2]/div[2]/div/div/div/input'
projectAddressUl='/html/body/div[3]/div[6]/div[2]/div[1]/ul'
projectAddressUlLi='/html/body/div[3]/div[6]/div[2]/div[1]/ul/li'
projectIntroduction='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[3]/div/div/div/div/div/div[2]/form/div[3]/div[2]/div/textarea'
confirm='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[3]/div/div/div/footer/div/div/button[1]'
more='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[2]/div[1]/div[3]/div[2]/div[3]/div'
deleteProject='/html/body/div[3]/div[6]/div/div[2]'
deleteConfirm='/html/body/div[4]/div/div/div[3]/button[2]'
# 分组相关元素
addGroup='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[1]/div[1]/div[2]/button[1]'
groupName='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[5]/div/div/div/div/form/div/div[2]/div/div/input'
groupAddConfirm='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[5]/div/div/div/footer/div/div/button[1]'
groupMore='/html/body/div[1]/div[1]/div[1]/div/div[2]/div[2]/section/div/div[2]/div[2]/div/div/button/div/div[2]/div'
groupUpdate='/html/body/div[3]/div[6]/div/div[2]'
groupDelete='/html/body/div[3]/div[6]/div/div[1]'
3. 业务逻辑层(business 层)
把高频的业务流程(比如登录、进入模块)封装成通用方法,用例里一行代码就能完成复杂的业务前置操作,大幅减少代码冗余。
这里补充登录业务的完整示例(loginBusiness.py),可直接使用:
from page.login_page import LoginPage
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
class LoginBusiness:
@classmethod
def loginBusiness(self, driver, name, password):
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.XPATH,LoginPage.login))).click()
driver.find_element(*LoginPage.username).send_keys(name)
driver.find_element(*LoginPage.password).send_keys(password)
driver.find_element(*LoginPage.button).click()
同时把元素操作类也放到 business 层,目录结构完全匹配。
4. 测试用例层(test_case 层)
有了前面的三层封装,用例编写变得极其简洁,只需要聚焦业务场景,不用关心底层实现。同时集成 Allure 装饰器,生成的报告结构清晰、可追溯。
完整的项目管理模块测试用例(test_projectManagement.py):
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from business.loginBusiness import LoginBusiness
from page.projectManagementPage import ProjectManagementPage as pmp
import time,allure
from selenium.webdriver.chrome.options import Options
from business.operateElement import OperateElement
@allure.feature('项目管理模块')
class TestProjectMangement:
def setup_method(self):
"""用例前置操作:初始化浏览器、登录、进入对应模块"""
# 配置无头浏览器,服务器运行也不会报错
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
# 初始化浏览器
self.driver = webdriver.Chrome(options=chrome_options)
self.driver.maximize_window()
self.driver.get('https://single.swarm-bim.com/login?appId=85319726')
time.sleep(3)
# 调用封装好的登录业务
LoginBusiness.loginBusiness(self.driver, '173******62', '70**62')
time.sleep(2)
# 进入项目管理模块
OperateElement(self.driver).click(pmp.projectManagementLi)
time.sleep(6)
@allure.title('正常创建项目场景')
def test_prjecctMangemtAdd(self):
OperateElement(self.driver).click(pmp.addProject)
OperateElement(self.driver).send_keys(pmp.projectName, '自动化测试添加项目')
OperateElement(self.driver).send_keys(pmp.projectAddress, '北京-北京')
time.sleep(1)
# 处理地址下拉框
option_list = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(
(By.XPATH, pmp.projectAddressUl)))
time.sleep(1)
target_option = option_list.find_element(By.XPATH, pmp.projectAddressUlLi)
target_option.click()
OperateElement(self.driver).send_keys(pmp.projectIntroduction, '自动化测试简介')
OperateElement(self.driver).click(pmp.confirm)
# 断言结果
OperateElement(self.driver).assert_text( '//div[@role="alert"]','项目创建成功')
# 操作完成截图,存入报告
with allure.step('创建成功截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='正常创建项目',attachment_type=allure.attachment_type.PNG)
@allure.title('删除项目场景')
def test_deleteProject(self):
time.sleep(3)
OperateElement(self.driver).click(pmp.more)
OperateElement(self.driver).click(pmp.deleteProject)
OperateElement(self.driver).click(pmp.deleteConfirm)
OperateElement(self.driver).assert_text( '//div[@role="alert"]','删除项目成功')
with allure.step('删除成功截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='删除项目',attachment_type=allure.attachment_type.PNG)
@allure.title('正常创建分组场景')
def test_addGroup01(self):
time.sleep(3)
OperateElement(self.driver).click(pmp.addGroup)
OperateElement(self.driver).send_keys(pmp.groupName, '自动化测试添加分组')
OperateElement(self.driver).click(pmp.groupAddConfirm)
OperateElement(self.driver).assert_text( '//div[@role="alert"]','分组创建成功')
with allure.step('创建分组成功截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='正常创建分组',attachment_type=allure.attachment_type.PNG)
@allure.title('添加已存在的分组场景-反向用例')
def test_addGroup02(self):
time.sleep(3)
OperateElement(self.driver).click(pmp.addGroup)
OperateElement(self.driver).send_keys(pmp.groupName, '自动化测试添加分组')
OperateElement(self.driver).click(pmp.groupAddConfirm)
OperateElement(self.driver).assert_text( '//div[@role="alert"]','已存在分组名称,请勿重复添加')
with allure.step('反向用例截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='添加已存在的分组',attachment_type=allure.attachment_type.PNG)
@allure.title('重命名分组场景')
def test_updateGroup(self):
time.sleep(1)
OperateElement(self.driver).click(pmp.groupMore)
OperateElement(self.driver).click(pmp.groupUpdate)
OperateElement(self.driver).clear(pmp.groupName)
OperateElement(self.driver).send_keys(pmp.groupName, '自动化测试重命名分组')
OperateElement(self.driver).click(pmp.groupAddConfirm)
OperateElement(self.driver).assert_text( '//div[@role="alert"]','重命名分组成功')
with allure.step('重命名成功截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='重命名分组',attachment_type=allure.attachment_type.PNG)
@allure.title('删除分组场景')
def test_deleteGroup(self):
time.sleep(1)
OperateElement(self.driver).click(pmp.groupMore)
OperateElement(self.driver).click(pmp.groupDelete)
OperateElement(self.driver).click(pmp.deleteConfirm)
OperateElement(self.driver).assert_text( '//div[@role="alert"]','删除分组成功')
with allure.step('删除分组成功截图'):
allure.attach(self.driver.get_screenshot_as_png(),name='删除分组',attachment_type=allure.attachment_type.PNG)
def teardown_method(self):
"""用例后置操作:关闭浏览器,释放资源"""
self.driver.quit()
五、核心亮点:Allure 报告深度定制 + 历史趋势保留
很多同学用 Allure,只实现了基础的报告生成,却没有环境信息、没有历史执行趋势,报告的实用性大打折扣。这套框架里,我们做了全量的定制化优化。
核心实现逻辑
完整的测试执行 + 报告生成核心代码,都在 testRunner.py 中,我们拆解核心知识点:
- 环境信息自动生成自动写入项目信息、Python 版本、浏览器版本、操作系统、执行时间等,报告里直接展示,不用手动填写。
- 历史趋势完整保留这是 Allure 报告的核心痛点:默认生成的报告没有历史趋势。解决方法很简单:每次执行前,把上一次报告的 history 文件夹复制到本次的原始数据目录;生成新报告后,再把新的 history 保存下来,实现趋势持续追踪。
- 报告个性化定制修改报告浏览器标题、报告名称,完全贴合自己的项目,告别默认的 Allure 标题。
完整的核心执行代码(testRunner.py)
包含测试执行、报告生成、Flask Web 服务、定时任务全能力,复制即可用:
import json
import pytest
import os
import datetime
import shutil
from pathlib import Path
import platform
from flask import Flask, render_template, send_from_directory, url_for, jsonify
from apscheduler.schedulers.background import BackgroundScheduler
# 初始化Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
# 全局路径配置(统一管理,修改一处全量生效)
REPORTS_DIR = Path("reports")
HISTORY_DIR = REPORTS_DIR / "allure-history/history"
HTML_REPORT_DIR = REPORTS_DIR / "html"
def generate_timestamp():
"""生成时间戳,用于区分每次执行的原始数据"""
return datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
def create_environment_file(raw_dir):
"""创建Allure环境信息文件,报告中展示执行环境详情"""
env_file = raw_dir / "environment.properties"
env_content = f"""projectName=SwarmBIM
pythonVersion={platform.python_version()}
allureVersion=2.13.8
baseUrl=https://single.swarm-bim.com
executionTime={datetime.datetime.now().isoformat()}
author=TestTeam
osName={platform.system()}
osVersion={platform.release()}
browserName=Chrome
browserVersion=87.0.4280.88
"""
with env_file.open('w', encoding='utf-8-sig') as f:
f.write(env_content)
def run_tests():
"""核心方法:执行测试、生成报告、处理历史数据"""
timestamp = generate_timestamp()
raw_dir = REPORTS_DIR / f"raw_{timestamp}"
# 1. 创建目录结构
raw_dir.mkdir(parents=True, exist_ok=True)
HISTORY_DIR.mkdir(parents=True, exist_ok=True)
# 2. 复制历史数据,用于生成趋势图(核心!解决无历史趋势的问题)
if HISTORY_DIR.exists():
shutil.copytree(
HISTORY_DIR,
raw_dir / "history",
dirs_exist_ok=True
)
# 3. 执行pytest测试用例,生成Allure原始数据
pytest_args = [
'test_case', # 测试用例目录,可自行修改
f'--alluredir={raw_dir}',
'--clean-alluredir'
]
exit_code = pytest.main(pytest_args)
# 4. 生成环境信息文件
create_environment_file(raw_dir)
# 5. 生成Allure HTML报告,--clean覆盖旧报告
os.system(f'allure generate {raw_dir} -o {HTML_REPORT_DIR} --clean')
# 6. 保存本次报告的历史数据,供下次执行使用
if (HTML_REPORT_DIR / 'history').exists():
shutil.copytree(
HTML_REPORT_DIR / 'history',
HISTORY_DIR,
dirs_exist_ok=True
)
# 7. 自定义报告标题
index_html = HTML_REPORT_DIR / "index.html"
if index_html.exists():
with open(index_html, "r+", encoding="utf-8") as f:
content = f.read().replace(
'<title>Allure Report</title>',
'<title>SIM协同系统测试报告</title>'
)
f.seek(0)
f.write(content)
f.truncate()
# 8. 自定义报告名称
summary_path = HTML_REPORT_DIR / "widgets/summary.json"
if summary_path.exists():
data = json.loads(summary_path.read_text())
data['reportName'] = 'SIM协同系统正式服测试报告'
summary_path.write_text(json.dumps(data))
return HTML_REPORT_DIR / "index.html"
# ---------------------- Flask Web路由配置 ----------------------
@app.route('/')
def control_panel():
"""首页:测试控制面板"""
return render_template('report_control.html')
@app.route('/run-tests', methods=['POST'])
def trigger_tests():
"""接口:一键触发测试执行"""
try:
report_path = run_tests()
return jsonify({
'status': 'success',
'report_url': url_for('serve_report', path=report_path.name)
})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/reports')
def list_reports():
"""接口:获取历史报告列表"""
reports = []
for f in sorted(HTML_REPORT_DIR.glob("*.html"), key=lambda x: x.stat().st_mtime, reverse=True):
reports.append({
'name': f"报告 - {datetime.datetime.fromtimestamp(f.stat().st_mtime).strftime('%Y-%m-%d %H:%M')}",
'path': url_for('serve_report', path=f.name)
})
return jsonify(reports)
@app.route('/report/<path:path>')
def serve_report(path):
"""接口:在线查看Allure报告"""
return send_from_directory(HTML_REPORT_DIR, path)
# ---------------------- 定时任务配置 ----------------------
def scheduled_test_job():
"""定时执行的测试任务"""
with app.app_context():
print(f"[{datetime.datetime.now()}] 开始定时测试任务...")
run_tests()
print("定时任务执行完成")
# 初始化后台调度器(守护进程,不阻塞Flask服务)
scheduler = BackgroundScheduler(daemon=True)
# 配置定时规则:cron表达式,这里默认每天00:21执行,可自行修改
scheduler.add_job(
func=scheduled_test_job,
trigger='cron',
hour=0,
minute=21,
timezone='Asia/Shanghai'
)
# 启动定时任务
scheduler.start()
# ---------------------- 程序入口 ----------------------
if __name__ == '__main__':
# 初始化必要目录,避免首次运行报错
REPORTS_DIR.mkdir(exist_ok=True)
HTML_REPORT_DIR.mkdir(exist_ok=True)
# 启动Flask服务,host=0.0.0.0支持局域网访问,端口可自行修改
app.run(host='0.0.0.0', port=5400, debug=False)
补全可用的 Web 控制面板页面
之前的 report_control.html 有报错,这里给大家提供一个完整可用、美观的前端页面,放到 templates 目录下即可:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI自动化测试平台</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
body {
background-color: #f5f7fa;
padding: 40px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 28px;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 16px;
}
.card {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.card h2 {
font-size: 20px;
margin-bottom: 20px;
color: #303133;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background-color: #667eea;
color: white;
}
.btn-primary:hover {
background-color: #5568d3;
}
.btn-primary:disabled {
background-color: #c0c4cc;
cursor: not-allowed;
}
.btn-link {
background: none;
color: #409eff;
padding: 8px 0;
text-decoration: underline;
}
.btn-link:hover {
color: #66b1ff;
}
.loading {
display: none;
margin-left: 15px;
color: #909399;
}
.report-list {
list-style: none;
}
.report-item {
padding: 15px 0;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
}
.report-item:last-child {
border-bottom: none;
}
.report-name {
color: #303133;
font-size: 16px;
}
.tips {
margin-top: 15px;
padding: 10px 15px;
background-color: #ecf5ff;
border-left: 4px solid #409eff;
color: #409eff;
border-radius: 4px;
}
.error-tips {
margin-top: 15px;
padding: 10px 15px;
background-color: #fef0f0;
border-left: 4px solid #f56c6c;
color: #f56c6c;
border-radius: 4px;
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>UI自动化测试管理平台</h1>
<p>基于Pytest+Selenium+Allure构建,一键执行测试 | 定时自动回归 | 可视化报告</p>
</div>
<div class="card">
<h2>测试执行</h2>
<button id="runBtn" class="btn btn-primary" onclick="runTests()">立即执行自动化测试</button>
<span class="loading" id="loading">测试执行中,请耐心等待,请勿重复点击...</span>
<div class="tips">执行完成后会自动跳转到测试报告页面,全程无需人工干预</div>
<div class="error-tips" id="errorTips"></div>
</div>
<div class="card">
<h2>历史报告</h2>
<ul class="report-list" id="reportList">
<li style="color: #909399;">加载中...</li>
</ul>
</div>
</div>
<script>
// 页面加载时获取报告列表
window.onload = function() {
loadReportList();
}
// 执行测试
function runTests() {
const runBtn = document.getElementById('runBtn');
const loading = document.getElementById('loading');
const errorTips = document.getElementById('errorTips');
// 禁用按钮,显示加载状态
runBtn.disabled = true;
loading.style.display = 'inline-block';
errorTips.style.display = 'none';
axios.post('/run-tests')
.then(function (response) {
if (response.data.status === 'success') {
// 跳转到报告页面
window.location.href = response.data.report_url;
} else {
errorTips.innerText = '执行失败:' + response.data.message;
errorTips.style.display = 'block';
}
})
.catch(function (error) {
errorTips.innerText = '请求失败:' + error.message;
errorTips.style.display = 'block';
})
.finally(function () {
// 恢复按钮状态
runBtn.disabled = false;
loading.style.display = 'none';
// 重新加载报告列表
loadReportList();
});
}
// 加载报告列表
function loadReportList() {
const reportList = document.getElementById('reportList');
axios.get('/reports')
.then(function (response) {
const reports = response.data;
if (reports.length === 0) {
reportList.innerHTML = '<li style="color: #909399;">暂无历史报告,先执行一次测试吧</li>';
return;
}
let html = '';
reports.forEach(item => {
html += `
<li class="report-item">
<span class="report-name">${item.name}</span>
<button class="btn btn-link" onclick="window.open('${item.path}')">查看报告</button>
</li>
`;
});
reportList.innerHTML = html;
})
.catch(function (error) {
reportList.innerHTML = '<li style="color: #f56c6c;">报告列表加载失败</li>';
});
}
</script>
</body>
</html>
六、环境搭建与快速启动,一步到位
1. 前置环境准备
- Python 环境:安装 Python 3.8 及以上版本,配置好环境变量
- Chrome 与 ChromeDriver:安装 Chrome 浏览器,下载对应版本的 ChromeDriver, 将下载的ChromeDriver放在python安装的目录中(访问华为云镜像:Index of chromedriver-local)。
- Allure 命令行工具:
- 下载地址:https://github.com/allure-framework/allure2/releases
- 解压后把 bin 目录配置到系统环境变量
- 验证:cmd 执行
allure --version,能输出版本号即为成功
2. 依赖包一键安装
新建 requirements.txt 文件,复制以下内容,执行pip install -r requirements.txt即可安装所有依赖:
pytest>=7.0.0
selenium>=4.0.0
allure-pytest>=2.13.0
Flask>=2.0.0
APScheduler>=3.10.0
3. 快速启动运行
- 按照上面的目录结构,把所有代码放到对应文件夹里
- 修改自己的业务地址、账号密码、元素定位、测试用例
- 执行启动命令:
python testRunner.py - 打开浏览器,访问 http://127.0.0.1:5400 ,即可进入自动化测试控制面板

- 点击「立即执行自动化测试」,等待用例执行完成,自动跳转到 Allure 报告页面,和可查看以前执行的情况。

七、进阶优化方向(可直接扩展)
这套框架已经满足了企业级 UI 自动化的核心需求,还可以根据自己的团队场景,做进一步的扩展:
- 测试结果通知:增加钉钉 / 企业微信 / 邮件推送,测试执行完成自动推送结果和报告链接
- 多环境支持:配置测试环境、预发环境、正式环境,一键切换执行环境
- 并发执行提速:集成 pytest-xdist,实现多用例并发执行,大幅缩短执行时间
- 失败重试机制:增加用例失败自动重试,减少网络波动等偶发因素导致的误报
- 数据驱动测试:结合 @pytest.mark.parametrize,实现一套用例覆盖多组测试数据
- 容器化部署:把项目打包成 Docker 镜像,一键部署,无需在服务器配置环境
- 用户权限控制:增加登录鉴权功能,控制测试执行和报告查看的权限
- 用例管理功能:扩展用例筛选、指定用例执行、批量执行等能力
八、结尾总结
这套自动化测试平台,从底层的 PO 分层设计,到用例编写、执行管理、报告生成、定时任务,实现了 UI 自动化全流程的闭环。代码结构清晰,注释完整,新手也能快速上手,测试团队可以直接基于这套框架,快速落地自己的业务自动化用例。
不用再零散地找教程拼凑功能,这套方案直接给你完整可运行的生产级代码,复制就能用,改改业务逻辑就能落地到自己的项目里,大幅提升测试效率,告别重复的手工回归。
如果在使用过程中有任何问题,欢迎在评论区交流讨论~点赞收藏不迷路,后续会分享更多测试开发的落地干货!
更多推荐



所有评论(0)