Web UI 自动化篇最后一篇。本篇完成 Allure 报告的完整集成、CI/CD 流水线配置、自动化测试策略文档,以及整个 Web UI 篇的总结回顾。
前言
框架搭好了,用例写好了,三种模式都有了。接下来要解决企业落地的最后三个问题:
- 报告怎么看:Allure 报告如何自定义分类、环境信息、趋势图
- 怎么自动跑:GitHub Actions / Jenkins Pipeline 配置
- 什么该自动化:自动化测试策略文档
一、Allure 报告完整集成
1.1 创建 conftest 的 Allure 元数据
在项目根目录创建 allure_config.py:
python
"""
Allure 报告配置
自定义分类、环境信息、执行器信息
"""
import json
import platform
from pathlib import Path
from datetime import datetime
REPORT_DIR = Path("reports/allure-results")
REPORT_DIR.mkdir(parents=True, exist_ok=True)
def write_categories():
"""
自定义缺陷分类
Allure 报告首页会按此分类统计失败原因
"""
categories = [
{
"name": "元素定位失败",
"matchedStatuses": ["failed"],
"messageRegex": ".*(?:locator|selector|element|定位).*"
},
{
"name": "断言失败",
"matchedStatuses": ["failed"],
"messageRegex": ".*(?:assert|AssertionError|断言|期望).*"
},
{
"name": "超时失败",
"matchedStatuses": ["failed"],
"messageRegex": ".*(?:timeout|Timeout|超时|timed out).*"
},
{
"name": "页面加载失败",
"matchedStatuses": ["failed"],
"messageRegex": ".*(?:Navigation|load|加载|net::ERR).*"
},
{
"name": "系统错误",
"matchedStatuses": ["broken"],
"messageRegex": ".*(?:Exception|Error|Traceback).*"
},
{
"name": "未知失败",
"matchedStatuses": ["failed"]
}
]
filepath = REPORT_DIR / "categories.json"
filepath.write_text(json.dumps(categories, ensure_ascii=False, indent=2))
print(f"Allure 分类配置已写入:{filepath}")
def write_environment():
"""
环境信息
Allure 报告的 Environment 标签页会显示此信息
"""
env_info = [
{"name": "Project", "value": "MallLite UI Automation"},
{"name": "Python", "value": platform.python_version()},
{"name": "OS", "value": f"{platform.system()} {platform.release()}"},
{"name": "Browser", "value": "Chromium"},
{"name": "Playwright", "value": _get_playwright_version()},
{"name": "Pytest", "value": _get_pytest_version()},
{"name": "Base URL", "value": "http://localhost:8000"},
{"name": "Environment", "value": "dev"},
{"name": "Run Time", "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S")},
{"name": "Executor", "value": platform.node()},
]
filepath = REPORT_DIR / "environment.properties"
lines = [f"{item['name']}={item['value']}" for item in env_info]
filepath.write_text("\n".join(lines))
print(f"Allure 环境信息已写入:{filepath}")
def write_executor():
"""
执行器信息
支持 CI/CD 环境自动识别
"""
import os
executor = {
"name": "MallLite UI Automation",
"type": "pytest",
"buildOrder": int(datetime.now().timestamp()),
"buildName": f"UI Test {datetime.now().strftime('%Y%m%d_%H%M')}",
}
# GitHub Actions 环境
if os.environ.get("GITHUB_ACTIONS"):
executor["name"] = "GitHub Actions"
executor["buildName"] = os.environ.get("GITHUB_WORKFLOW", "CI")
executor["buildOrder"] = int(os.environ.get("GITHUB_RUN_NUMBER", 0))
executor["reportName"] = "MallLite UI Test Report"
# Jenkins 环境
elif os.environ.get("JENKINS_URL"):
executor["name"] = "Jenkins"
executor["buildName"] = os.environ.get("JOB_NAME", "UI-Test")
executor["buildOrder"] = int(os.environ.get("BUILD_NUMBER", 0))
filepath = REPORT_DIR / "executor.json"
filepath.write_text(json.dumps(executor, ensure_ascii=False, indent=2))
print(f"Allure 执行器信息已写入:{filepath}")
def _get_playwright_version():
try:
import playwright
return playwright.__version__
except Exception:
return "unknown"
def _get_pytest_version():
try:
import pytest
return pytest.__version__
except Exception:
return "unknown"
if __name__ == "__main__":
write_categories()
write_environment()
write_executor()
print("Allure 配置文件生成完成")
1.2 更新 conftest.py 集成 Allure 配置
在 conftest.py 中追加会话级 hook:
python
def pytest_sessionstart(session):
"""测试会话开始时生成 Allure 配置文件"""
try:
from allure_config import write_categories, write_environment, write_executor
write_categories()
write_environment()
write_executor()
except Exception as e:
logger.warning(f"生成 Allure 配置失败:{e}")
1.3 Allure 报告自定义标签
在测试用例中使用 Allure 的各种标签,让报告更有结构:
python
import allure
@allure.epic("MallLite 电商系统")
@allure.feature("登录功能")
class TestLogin:
@allure.story("正常登录")
@allure.severity(allure.severity_level.BLOCKER)
@allure.title("管理员登录成功")
@allure.description("使用管理员账号密码验证登录功能")
@allure.link("http://localhost:8000/login", name="登录页面")
@allure.issue("JIRA-1234", "登录功能需求")
@allure.testcase("TC-001", "登录用例")
def test_admin_login(self, login_page, admin_user):
login_page.login(admin_user["username"], admin_user["password"])
assert login_page.is_login_success()
Allure 报告中的展示层级:
Epic: MallLite 电商系统
└── Feature: 登录功能
└── Story: 正常登录
├── TC-001: 管理员登录成功 [BLOCKER] ✅
├── TC-002: 普通用户登录成功 [BLOCKER] ✅
└── TC-003: VIP用户登录成功 [BLOCKER] ✅
1.4 Allure 报告核心页面说明
Allure 报告页面:
Overview(总览)
├── 统计面板:通过/失败/跳过/破损的数量和百分比
├── 趋势图:历史多次运行的通过率变化
├── 环境信息:来自 environment.properties
├── 缺陷分类:来自 categories.json(按失败原因分组)
└── 严重级别分布:Blocker/Critical/Normal/Minor/Trivial
Behaviors(行为)
├── 按 Epic → Feature → Story 三层组织
└── 每个 Story 下展示对应的测试用例
Packages(包)
├── 按 Python 模块/类组织
└── 类似代码目录结构
Graphs(图表)
├── 严重级别分布饼图
├── 状态分布饼图
├── 持续时间 TOP 10
└── 通过率趋势
Timeline(时间线)
└── 按执行时间轴展示,可看到并行执行情况
Categories(分类)
└── 按失败原因分类:元素定位失败/断言失败/超时失败/...
1.5 生成和查看报告
bash
# 方式一:两步操作
# 第一步:运行测试生成数据
pytest -v --alluredir=reports/allure-results --clean-alluredir
# 第二步:生成报告并打开
allure serve reports/allure-results
# 方式二:用 run.py
python run.py report
# 方式三:生成静态报告(用于分享)
pytest -v --alluredir=reports/allure-results --clean-alluredir
allure generate reports/allure-results -o reports/allure-report --clean
# 打开 reports/allure-report/index.html
二、GitHub Actions CI/CD
2.1 创建 Workflow 配置
bash
mkdir -p .github/workflows
创建 .github/workflows/ui-test.yml:
yaml
# MallLite UI 自动化测试 CI/CD
# 触发条件:push 到 main 分支、PR、手动触发、定时触发
name: UI Automation Test
on:
push:
branches: [main, develop]
paths:
- 'web_ui/**'
- 'mall-lite/**'
pull_request:
branches: [main]
schedule:
# 每天凌晨 2 点运行冒烟测试
- cron: '0 18 * * *'
workflow_dispatch:
inputs:
test_suite:
description: '测试套件'
required: true
default: 'smoke'
type: choice
options:
- smoke
- regression
- all
environment:
description: '测试环境'
required: true
default: 'dev'
type: choice
options:
- dev
- staging
env:
PYTHON_VERSION: '3.12'
jobs:
ui-test:
name: UI Test (${{ github.event.inputs.test_suite || 'smoke' }})
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# 1. 检出代码
- name: Checkout
uses: actions/checkout@v4
# 2. 配置 Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
# 3. 安装依赖
- name: Install Dependencies
run: |
cd web_ui
pip install -r requirements.txt
playwright install chromium
playwright install-deps chromium
# 4. 启动被测系统
- name: Start MallLite
run: |
cd mall-lite
pip install -r requirements.txt
python run.py &
sleep 5
# 验证服务启动
curl -s http://localhost:8000/ | head -5
# 5. 运行测试
- name: Run Tests
run: |
cd web_ui
TEST_SUITE=${{ github.event.inputs.test_suite || 'smoke' }}
ENV=${{ github.event.inputs.environment || 'dev' }}
if [ "$TEST_SUITE" = "smoke" ]; then
pytest -v -m smoke --headed=false --env=$ENV \
--alluredir=reports/allure-results --clean-alluredir
elif [ "$TEST_SUITE" = "regression" ]; then
pytest -v -m regression --headed=false --env=$ENV \
--alluredir=reports/allure-results --clean-alluredir
else
pytest -v --headed=false --env=$ENV \
--alluredir=reports/allure-results --clean-alluredir
fi
env:
PYTHONDONTWRITEBYTECODE: 1
# 6. 上传 Allure 数据
- name: Upload Allure Results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results
path: web_ui/reports/allure-results/
retention-days: 30
# 7. 上传失败截图
- name: Upload Screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: failure-screenshots
path: web_ui/reports/screenshots/
retention-days: 7
# 8. 上传测试日志
- name: Upload Test Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs
path: web_ui/reports/logs/
retention-days: 7
# 9. 生成 Allure 报告
- name: Generate Allure Report
if: always()
uses: simple-elf/allure-report-action@master
with:
allure_results: web_ui/reports/allure-results
# 10. 部署报告到 GitHub Pages
- name: Deploy Report to GitHub Pages
if: always()
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: allure-history
2.2 Workflow 说明
触发条件:
├── push 到 main/develop 且改动了 web_ui 或 mall-lite → 自动运行
├── Pull Request → 自动运行
├── 每天凌晨 2 点(北京时间) → 自动运行冒烟测试
└── 手动触发 → 选择测试套件和环境
执行流程:
1. 检出代码
2. 安装 Python + 依赖 + Playwright 浏览器
3. 启动 MallLite 被测系统
4. 运行测试(无头模式)
5. 上传报告和截图(无论成功失败)
6. 生成 Allure 报告
7. 部署到 GitHub Pages
三、Jenkins Pipeline
创建 Jenkinsfile:
groovy
// MallLite UI 自动化测试 Jenkins Pipeline
pipeline {
agent any
parameters {
choice(
name: 'TEST_SUITE',
choices: ['smoke', 'regression', 'all'],
description: '选择测试套件'
)
choice(
name: 'ENVIRONMENT',
choices: ['dev', 'staging'],
description: '选择测试环境'
)
}
triggers {
// 每天凌晨 2 点运行冒烟测试
cron('0 18 * * *')
}
environment {
PYTHON_VERSION = '3.12'
BASE_URL = 'http://localhost:8000'
}
options {
timeout(time: 30, unit: 'MINUTES')
timestamps()
ansiColor('xterm')
}
stages {
stage('准备环境') {
steps {
sh '''
cd web_ui
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
playwright install chromium
playwright install-deps chromium
'''
}
}
stage('启动被测系统') {
steps {
sh '''
cd mall-lite
nohup python run.py > /tmp/malllite.log 2>&1 &
sleep 5
curl -sf http://localhost:8000/ || (echo "启动失败" && exit 1)
'''
}
}
stage('运行测试') {
steps {
sh """
cd web_ui
source venv/bin/activate
MARKER_OPT=""
if [ "${params.TEST_SUITE}" = "smoke" ]; then
MARKER_OPT="-m smoke"
elif [ "${params.TEST_SUITE}" = "regression" ]; then
MARKER_OPT="-m regression"
fi
pytest -v \$MARKER_OPT \\
--headed=false \\
--env=${params.ENVIRONMENT} \\
--alluredir=reports/allure-results \\
--clean-alluredir \\
--junitxml=reports/junit.xml \\
|| true
"""
}
}
}
post {
always {
// 发布 JUnit 报告
junit allowEmptyResults: true, testResults: 'web_ui/reports/junit.xml'
// 发布 Allure 报告
allure([
includeProperties: false,
results: [[path: 'web_ui/reports/allure-results']]
])
// 归档截图
archiveArtifacts allowEmptyArchive: true,
artifacts: 'web_ui/reports/screenshots/**'
// 归档日志
archiveArtifacts allowEmptyArchive: true,
artifacts: 'web_ui/reports/logs/**'
}
failure {
echo '测试失败!请检查 Allure 报告和截图。'
}
success {
echo '测试全部通过!'
}
cleanup {
// 清理被测系统
sh 'pkill -f "python run.py" || true'
}
}
}
四、自动化测试策略文档
4.1 什么适合自动化
适合自动化 ✅:
├── 高频执行的冒烟测试(每次发布前必跑)
├── 核心业务链路(登录→搜索→下单)
├── 回归测试(确保老功能没被破坏)
├── 数据驱动测试(同一逻辑多组数据)
├── 跨浏览器兼容性测试
└── 接口和 UI 联合测试
不适合自动化 ❌:
├── 一次性测试(只测一次的功能)
├── 探索性测试(需要人脑判断)
├── UI 视觉验证(样式、颜色、布局)
├── 涉及第三方支付的真实交易
├── 需要物理设备操作的场景
└── 需求频繁变更、尚未稳定的功能
4.2 用例分层金字塔
┌──────────────┐
/│ E2E (UI) │\ 10% ← POM 模式
/ │ 约 10-15 条 │ \ 核心链路
/ └──────────────┘ \
/ ┌──────────────┐ \
/ │ 接口测试 │ \ 20% ← API 自动化
/ │ 约 30-50 条 │ \
/ └──────────────┘ \
/ ┌──────────────────┐ \
/ │ 单元测试 │ \ 70% ← 开发自测
/ │ 约 100-200 条 │ \
/ └──────────────────┘ \
└────────────────────────────────────────┘
投入比例:单元测试 > 接口测试 > UI 测试
回报比例:单元测试回报最高,UI 测试成本最高
4.3 自动化用例优先级
| 优先级 | 标记 | 说明 | 执行频率 | 模式 |
|---|---|---|---|---|
| P0 | smoke + p0 | 核心冒烟,发布前必跑 | 每次提交 | POM |
| P1 | regression + p1 | 功能回归 | 每天定时 | KDT + DDT |
| P2 | regression + p2 | 边界和兼容性 | 每周 | KDT + DDT |
| 验收 | bdd | 产品验收测试 | 迭代结束 | BDD |
4.4 执行策略
bash
# 开发提交代码时:运行冒烟测试(约 2 分钟)
pytest -v -m smoke --headed=false
# 每天凌晨:运行全量回归(约 10 分钟)
pytest -v -m regression --headed=false
# 每周:运行全部用例(POM + KDT + BDD)约 15 分钟
pytest -v --headed=false
behave test_cases/bdd/features/
# 发布前:运行 P0 冒烟 + BDD 验收
pytest -v -m "smoke and p0" --headed=false
behave test_cases/bdd/features/ --tags=@smoke
五、更新 run.py 最终版
python
"""
MallLite UI 自动化测试 - 一键运行脚本(最终版)
用法:
python run.py smoke # POM 冒烟
python run.py regression # POM 回归
python run.py login # 登录模块
python run.py search # 搜索模块
python run.py cart # 购物车模块
python run.py order # 订单模块
python run.py e2e # 端到端
python run.py kdt # KDT 全部
python run.py kdt-login # KDT 登录
python run.py ddt # DDT 全部
python run.py bdd # BDD 全部
python run.py bdd-smoke # BDD 冒烟
python run.py all # 全部(POM + KDT)
python run.py report # 生成 Allure 报告
python run.py bdd-report # 生成 BDD Allure 报告
python run.py headless # 无头模式全量
python run.py setup # 生成 Allure 配置文件
"""
import subprocess
import sys
def run(cmd):
print(f"\n{'=' * 60}")
print(f" 执行:{' '.join(cmd)}")
print(f"{'=' * 60}\n")
return subprocess.run(cmd, cwd=".", shell=True).returncode
def pytest_cmd(*args):
return run([sys.executable, "-m", "pytest", "-v", *args])
def main():
if len(sys.argv) < 2:
print(__doc__)
return
cmd = sys.argv[1].lower()
# POM 模式
pom_modules = {
"smoke": lambda: pytest_cmd("-m", "smoke"),
"regression": lambda: pytest_cmd("-m", "regression"),
"login": lambda: pytest_cmd("-m", "login"),
"search": lambda: pytest_cmd("-m", "search"),
"product": lambda: pytest_cmd("-m", "product"),
"cart": lambda: pytest_cmd("-m", "cart"),
"order": lambda: pytest_cmd("-m", "order"),
"e2e": lambda: pytest_cmd("-m", "e2e"),
"p0": lambda: pytest_cmd("-m", "p0"),
}
# KDT 模式
kdt_commands = {
"kdt": lambda: pytest_cmd("test_cases/kdt/"),
"kdt-login": lambda: pytest_cmd("test_cases/kdt/", "-k", "login"),
"kdt-search": lambda: pytest_cmd("test_cases/kdt/", "-k", "search"),
"kdt-cart": lambda: pytest_cmd("test_cases/kdt/", "-k", "cart"),
"ddt": lambda: pytest_cmd("test_cases/kdt/", "-k", "ddt"),
}
# BDD 模式
def behave_cmd(*args):
return run(["behave", "test_cases/bdd/features/", *args])
bdd_commands = {
"bdd": lambda: behave_cmd(),
"bdd-smoke": lambda: behave_cmd("--tags=@smoke"),
"bdd-login": lambda: run(["behave", "test_cases/bdd/features/login.feature"]),
"bdd-search": lambda: run(["behave", "test_cases/bdd/features/search.feature"]),
"bdd-cart": lambda: run(["behave", "test_cases/bdd/features/cart.feature"]),
"bdd-e2e": lambda: run(["behave", "test_cases/bdd/features/e2e.feature"]),
}
# 特殊命令
def run_report():
pytest_cmd("--alluredir=reports/allure-results", "--clean-alluredir")
return run(["allure", "serve", "reports/allure-results"])
def run_bdd_report():
behave_cmd(
"-f", "allure_behave.formatter:AllureFormatter",
"-o", "reports/allure-results",
)
return run(["allure", "serve", "reports/allure-results"])
def run_all():
pytest_cmd()
return behave_cmd()
def run_headless():
return pytest_cmd("--headed=false")
def run_setup():
from allure_config import write_categories, write_environment, write_executor
write_categories()
write_environment()
write_executor()
special = {
"all": run_all,
"report": run_report,
"bdd-report": run_bdd_report,
"headless": run_headless,
"setup": run_setup,
}
all_commands = {**pom_modules, **kdt_commands, **bdd_commands, **special}
if cmd in all_commands:
exit_code = all_commands[cmd]()
else:
print(f"未知命令:{cmd}")
print(f"支持的命令:{', '.join(sorted(all_commands.keys()))}")
exit_code = 1
sys.exit(exit_code)
if __name__ == "__main__":
main()
六、项目完整结构总览
web_ui/
├── config/ ← 配置层
│ ├── __init__.py
│ ├── config.py ← 多环境配置管理
│ └── env_config.yaml ← dev / staging / prod
│
├── common/ ← 工具层
│ ├── __init__.py
│ ├── logger.py ← 日志封装
│ ├── data_reader.py ← JSON/YAML/CSV/Excel 读取
│ ├── screenshot.py ← 截图工具
│ ├── random_data.py ← 随机数据生成
│ └── allure_helper.py ← Allure 报告辅助
│
├── pages/ ← POM 页面对象层(三种模式共用)
│ ├── __init__.py
│ ├── base_page.py ← 基类(40+ 通用方法)
│ ├── login_page.py ← 登录页
│ ├── home_page.py ← 首页
│ ├── product_page.py ← 商品详情页
│ ├── cart_page.py ← 购物车页
│ ├── order_page.py ← 订单页
│ └── profile_page.py ← 个人中心页
│
├── keywords/ ← KDT 关键字层
│ ├── __init__.py
│ ├── base_keywords.py ← 底层通用关键字(25 个)
│ ├── login_keywords.py ← 登录业务关键字(7 个)
│ ├── search_keywords.py ← 搜索业务关键字(8 个)
│ ├── cart_keywords.py ← 购物车关键字(9 个)
│ ├── order_keywords.py ← 订单关键字(4 个)
│ └── keyword_registry.py ← 关键字注册表(53 个)
│
├── engine/ ← KDT 驱动引擎
│ ├── __init__.py
│ └── test_engine.py ← 支持 KDT + DDT + 变量替换
│
├── test_cases/
│ ├── __init__.py
│ ├── pom/ ← POM 模式用例
│ │ ├── __init__.py
│ │ ├── test_login.py ← 登录(10 条)
│ │ ├── test_search.py ← 搜索(10 条)
│ │ ├── test_product.py ← 商品(4 条)
│ │ ├── test_cart.py ← 购物车(5 条)
│ │ ├── test_order.py ← 订单(3 条)
│ │ └── test_e2e.py ← 端到端(4 条)
│ ├── kdt/ ← KDT + DDT 模式用例
│ │ ├── __init__.py
│ │ ├── test_kdt_runner.py ← KDT 运行器
│ │ ├── login/
│ │ │ ├── test_login.yaml ← 纯 KDT(8 条)
│ │ │ └── test_login_ddt.yaml ← KDT+DDT(11 条)
│ │ ├── search/
│ │ │ ├── test_search.yaml ← 纯 KDT(10 条)
│ │ │ └── test_search_ddt.yaml ← KDT+DDT(9 条)
│ │ └── cart/
│ │ └── test_cart.yaml ← 纯 KDT(5 条)
│ └── bdd/ ← BDD 模式用例
│ ├── __init__.py
│ ├── environment.py ← 浏览器生命周期
│ ├── features/
│ │ ├── login.feature ← 登录(14 条)
│ │ ├── search.feature ← 搜索(10 条)
│ │ ├── cart.feature ← 购物车(4 条)
│ │ └── e2e.feature ← 端到端(2 条)
│ └── steps/
│ ├── login_steps.py
│ ├── search_steps.py
│ ├── cart_steps.py
│ └── common_steps.py
│
├── test_data/ ← 测试数据
│ ├── login_data.json
│ ├── login_fail_data.yaml
│ ├── login_success_data.yaml
│ ├── search_data.yaml
│ ├── search_ddt_data.yaml
│ ├── category_ddt_data.yaml
│ └── cart_test_data.yaml
│
├── reports/ ← 报告输出(运行时生成)
│ ├── screenshots/
│ ├── logs/
│ ├── allure-results/
│ └── junit.xml
│
├── .github/workflows/ui-test.yml ← GitHub Actions CI/CD
├── Jenkinsfile ← Jenkins Pipeline
├── allure_config.py ← Allure 配置生成
├── conftest.py ← Pytest 核心 fixture
├── behave.ini ← BDD 配置
├── pytest.ini ← Pytest 配置
├── run.py ← 一键运行脚本
├── requirements.txt ← 依赖清单
└── venv/ ← 虚拟环境
七、用例总览
| 模式 | 模块 | 用例数 |
|---|---|---|
| POM | 登录 | 10 |
| 搜索 | 10 | |
| 商品 | 4 | |
| 购物车 | 5 | |
| 订单 | 3 | |
| 端到端 | 4 | |
| 小计 | 36 | |
| KDT | 登录 | 8 |
| 搜索 | 10 | |
| 购物车 | 5 | |
| 小计 | 23 | |
| KDT+DDT | 登录 | 11 |
| 搜索 | 9 | |
| 小计 | 20 | |
| BDD | 登录 | 14 |
| 搜索 | 10 | |
| 购物车 | 4 | |
| 端到端 | 2 | |
| 小计 | 30 | |
| 合计 | 109 |
八、关键技术点回顾
接着来,从 8.1 继续。
8.1 每篇文章的核心产出
| 篇 | 核心产出 | 代码量 |
|---|---|---|
| 01 项目总览 | 项目骨架 + 12 个基础用例 | ~200 行 |
| 02 基础层 | config + common + test_data + conftest | ~600 行 |
| 03 POM 实现 | BasePage(40 方法) + 6 个页面对象 | ~900 行 |
| 04 POM 用例 | 36 条完整测试用例 | ~500 行 |
| 05 KDT 实现 | 53 个关键字 + 注册表 + 引擎 | ~800 行 |
| 06 KDT+DDT | 引擎升级 + DDT 数据源 + 20 条展开用例 | ~300 行 |
| 07 BDD | 4 个 Feature + 4 个 Steps | ~500 行 |
| 08 企业落地 | Allure 完整集成 + CI/CD + 策略文档 | ~400 行 |
| 合计 | 109 条用例 + 完整框架 | ~4200 行 |
8.2 框架各层的核心价值
config/(配置层)
→ 价值:一处改 URL,全项目生效
→ 不用这个:改 109 条用例中的 URL
common/(工具层)
→ 价值:日志、截图、数据读取、报告统一管理
→ 不用这个:每个文件各写一套,风格不统一
pages/(页面对象层)
→ 价值:前端改了元素 id,只改 1 个文件
→ 不用这个:改 36 条 POM 用例 + 23 条 KDT 用例 + 30 条 BDD 用例
keywords/(关键字层)
→ 价值:非技术人员用 YAML 写用例
→ 不用这个:所有用例必须会 Python
engine/(驱动引擎)
→ 价值:一条模板跑 N 组数据
→ 不用这个:复制粘贴 N 份 YAML
test_data/(数据层)
→ 价值:新增测试数据不改代码
→ 不用这个:每次加数据都改 Python 文件
conftest.py(核心配置)
→ 价值:浏览器生命周期统一管理,失败自动截图
→ 不用这个:每个用例手动创建和销毁浏览器
8.3 关键技术选型回顾
| 决策点 | 选择 | 原因 |
|---|---|---|
| 浏览器驱动 | Playwright | 自动等待、原生同步/异步、强大定位器 |
| 测试框架 | Pytest | fixture 机制、插件丰富、零样板代码 |
| 设计模式 | POM + KDT + BDD | 组合使用覆盖不同场景 |
| 数据驱动 | JSON + YAML + CSV + Excel | 覆盖主流数据格式 |
| 报告 | Allure | 层级清晰、附件丰富、CI 友好 |
| CI/CD | GitHub Actions + Jenkins | 覆盖主流 CI 平台 |
九、常见问题 FAQ
9.1 环境问题
Q:Playwright 安装后运行报错 "Browser not found"
bash
# 重新安装浏览器驱动
playwright install chromium
# 如果网络问题,设置镜像
PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright playwright install chromium
Q:MallLite 启动后测试访问不到
bash
# 检查 MallLite 是否正常运行
curl http://localhost:8000/
# 检查端口是否被占用
netstat -ano | findstr 8000 # Windows
lsof -i :8000 # macOS/Linux
# 如果用 conftest 中的 base_url 配置了其他地址,检查地址是否正确
Q:Allure 报告打开是空白页
bash
# 确认 Allure 版本
allure --version
# 确认数据目录有内容
ls reports/allure-results/
# 如果目录为空,先运行测试生成数据
pytest -v --alluredir=reports/allure-results
# 重新生成报告
allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report
9.2 用例问题
Q:元素定位失败 "waiting for selector ... timeout"
python
# 原因 1:选择器写错了
# 打开浏览器开发者工具,确认选择器
page.locator("#correct-selector")
# 原因 2:页面还没加载完
# 加等待
page.wait_for_load_state("networkidle")
page.wait_for_selector("#element", state="visible")
# 原因 3:元素在 iframe 中
frame = page.frame_locator("iframe#main")
frame.locator("#element").click()
# 原因 4:元素在新窗口中
with page.expect_popup() as popup_info:
page.click("#open-new-window")
new_page = popup_info.value
new_page.locator("#element").click()
Q:用例在本地通过,CI 环境失败
bash
# 常见原因:
# 1. 无头模式差异 → 确保 CI 用 --headed=false
# 2. 分辨率差异 → 在 context 中固定 viewport
# 3. 网络差异 → CI 环境增加超时时间
# 4. 时序差异 → 用 wait_for 代替 sleep
# 排查方法:在 CI 中截图
- name: Debug Screenshot
if: failure()
uses: actions/upload-artifact@v4
with:
name: debug-screenshot
path: web_ui/reports/screenshots/
Q:DDT 展开后用例名重复
yaml
# 确保 data_source 中有唯一字段用于命名
- name: "登录失败 - ${case_name}" # case_name 在数据中唯一
data_source: "login_fail_data.yaml"
steps: ...
9.3 框架扩展问题
Q:如何添加一个新的页面?
三步操作:
第一步:创建页面对象
pages/new_page.py → 继承 BasePage
第二步:添加 fixture
conftest.py → @pytest.fixture def new_page(page): return NewPage(page)
第三步:编写用例
test_cases/pom/test_new.py → 使用 new_page fixture
Q:如何添加一个新的关键字?
两步操作:
第一步:在关键字类中添加方法
keywords/xxx_keywords.py → def new_keyword(self, param): ...
方法名就是关键字名,不需要额外注册
KeywordRegistry 自动扫描所有公开方法
第二步:在 YAML 中使用
- keyword: new_keyword
params:
param: "value"
Q:如何支持新的浏览器?
bash
# 在 conftest.py 中已有浏览器选择逻辑
# 运行时指定即可
pytest --browser=firefox -v
pytest --browser=webkit -v
# 确保已安装对应驱动
playwright install firefox
playwright install webkit
Q:如何对接企业内部系统(如飞书、钉钉通知)?
python
# 在 conftest.py 的 pytest_sessionfinish 中添加通知逻辑
def pytest_sessionfinish(session, exitstatus):
"""测试结束后发送通知"""
passed = session.testscollected - session.testsfailed
total = session.testscollected
failed = session.testsfailed
report_url = "https://your-allure-report-url.com"
# 飞书通知示例
if failed > 0:
send_feishu_notification(
title=f"UI 测试完成:{passed}/{total} 通过",
content=f"失败 {failed} 条\n报告:{report_url}",
)
十、Web UI 自动化篇总结
10.1 八篇文章的知识图谱
01 项目总览与环境搭建
│ → Playwright 选型、fixture、marker、第一个测试
│
02 框架基础层搭建
│ → config 配置管理、common 工具层、test_data 数据层
│
03 POM 模式原理与实现
│ → BasePage 基类、6 个页面对象、调用链路
│
04 POM 实战用例
│ → 36 条完整用例、数据驱动、Allure 标记
│
05 KDT 模式原理与实现
│ → 关键字层、注册表、驱动引擎、YAML 用例
│
06 KDT + DDT 实战
│ → 变量替换、数据源加载、模板展开
│
07 BDD 模式实战
│ → Behave 框架、Feature 文件、Step Definitions
│
08 企业选型·报告·CI/CD
│ → Allure 完整集成、GitHub Actions、Jenkins、策略文档
│
└── 产出:109 条用例 + 完整企业级 UI 自动化框架
10.2 能力成长路线
通过这 8 篇文章,你从"会用 Playwright 打开网页"成长到了能独立搭建企业级自动化测试框架:
之前:手动打开浏览器 → 输入 → 点击 → 眼睛看结果
现在:
→ 109 条用例自动执行
→ POM / KDT / BDD 三种模式按需选择
→ 外部数据文件驱动,新增场景不改代码
→ Allure 报告自动生成,失败自动截图
→ CI/CD 自动触发,定时执行
→ 一份完整的自动化测试策略文档
十一、系列导航
| 序号 | 标题 | 状态 |
|---|---|---|
| Python 基础篇(P01-P06) | 从零到能写 FastAPI 后端 | ✅ 已完成 |
| Web UI 自动化篇 | ||
| 01 | 项目总览与环境搭建 | ✅ |
| 02 | 框架基础层搭建 | ✅ |
| 03 | POM 模式原理与实现 | ✅ |
| 04 | POM 实战用例 | ✅ |
| 05 | KDT 模式原理与实现 | ✅ |
| 06 | KDT + DDT 实战 | ✅ |
| 07 | BDD 模式实战 | ✅ |
| 08 | 企业选型·报告·CI/CD | ✅ 本文 |
| 接口自动化篇 | ||
| 09 | 接口自动化基础与环境 | 下一篇 |
| 10 | 接口自动化框架搭建 | 待更新 |
| 11 | 接口自动化实战 | 待更新 |
| 12 | 接口自动化进阶 | 待更新 |
| 13 | 接口数据驱动与报告 | 待更新 |
Web UI 自动化篇完结。下一篇进入接口自动化篇------用 Requests 库 + 自研框架对 MallLite 的全部 API 接口进行自动化测试。