从零深入:基于 Playwright + Pytest + Allure 的企业级 Web 端到端自动化测试框架实战
适合人群 :测试开发、自动化测试工程师、希望从 Selenium 迁移到 Playwright 的同学、想搭建可落地 E2E 基建的研发团队
文章定位 :不只讲「怎么跑通」,而是把框架设计、POM、环境配置、失败取证、Allure 报告、Docker/Jenkins CI,以及 AI Skill 辅助写用例整条链路讲透
建议阅读时长:30~45 分钟(可作为系列教程骨架一次性收藏)
一、写在前面:为什么还要再搭一套 E2E 框架?
很多团队「有自动化」,但长期跑不起来,常见原因并不是不会写 click / fill,而是:
- 用例与页面耦合:locator 散落在测试里,UI 一改,几十处一起炸
- 环境不可切换 :本地能跑,CI 上
localhost连不上被测服务 - 失败不可诊断:红了只有一行 AssertionError,没有截图、没有视频、没有 trace
- 报告不可交付:领导/业务只想看「测了什么、挂了什么」,终端日志读不下去
- CI 不可复现:本机装了一堆依赖,流水线环境不一致,偶现失败查三天
本文介绍的这套开源框架,正是围绕上述痛点设计的一套 「可本地调试 + 可 Docker 隔离 + 可 Jenkins 集成 + 可 Allure 交付 + 可 AI 辅助扩写」 的 Web E2E 解决方案。技术栈清晰:
| 层级 | 技术选型 | 职责 |
|---|---|---|
| 浏览器驱动 | Playwright(Python Sync API) | 打开页面、操作、断言、录制 trace/视频 |
| 测试运行器 | Pytest | 发现用例、fixture、参数化、钩子 |
| 报告 | Allure | 用例分层、附件、趋势、失败证据 |
| 配置 | YAML + --env |
本地 / Jenkins 等环境切换 |
| 容器 | Docker / Compose | 前端 Demo、Jenkins、测试 Agent |
| CI | Jenkins Pipeline | 拉代码 → Docker Agent 跑测 → Allure / 邮件 |
| 智能化(可选) | Claude Skills + Playwright MCP | 规划 spec、生成 POM、失败根因分析 |
如果你正在做 从 0 到 1 搭 UI 自动化,或要把现有脚本工程化,本文可以当作一份「可照抄再改造」的蓝图。
二、整体架构:仓库里到底有什么?
仓库大致分三块(外加编排与文章):
playwright-pytest-allure-demo/
├── demo-frontend/ # 被测对象:React + Vite + Ant Design Demo
├── playwright-auto-test/ # ★ 真正可复用的测试工程
├── jenkins/ # Jenkins 镜像、插件、Pipeline
├── docker-compose.yml # 一键起 Jenkins + Demo 前端
└── README.md / README.zh-CN.md
落地到真实业务时,真正要带走的是 playwright-auto-test/ 。demo-frontend 只是方便你本地立刻有一个可测的页面;换成你们自己的 staging 前端即可。
测试工程内部结构:
playwright-auto-test/
├── configs/ # env.default.yaml / env.jenkins.yaml ...
├── fixtures/ # browser、截图目录等 pytest fixtures
├── pages/ # Page Object Model
├── tests/ # 测试用例
├── data/ # 参数化 JSON 数据
├── specs/ # AI Planner 产出的语言无关规格(中间产物)
├── utils/ # 配置加载、测试数据加载、失败分析等
├── output/ # 运行产物:allure、日志、截图、trace、视频
├── conftest.py # 全局钩子、config fixture、失败附件
├── pytest.ini # 默认参数、alluredir、日志
├── requirements.txt
├── Dockerfile # 测试 Agent 镜像
└── .claude/skills/ # triaging / planner / generator 三大 Skill
一句话理解数据流:
YAML 配置 ──► config fixture ──► Page Object 打开 base_url
│
Pytest 用例 ──► page fixture ──► 操作页面 ──► expect 断言
│
失败时:截图 / HTML / trace / 视频
│
全部写入 Allure + output/ 目录
三、技术点详解(一):为什么选 Playwright 而不是 Selenium?
本框架强制使用 playwright.sync_api,并在规范里明确 禁止 async。原因很实际:和 Pytest 的同步用例风格天然契合,调试栈更直观,团队上手成本更低。
相对 Selenium,Playwright 在 E2E 场景里有几个「工程向」优势,本框架都用上了:
3.1 Auto-waiting(自动等待)
传统写法大量 time.sleep / WebDriverWait,既慢又脆。Playwright 的 click、fill、expect(...).to_be_visible() 自带等待可操作 / 可见条件。框架规范因此 绝对禁止 time.sleep() ,也禁止 wait_for_load_state("networkidle")(SPA / 长轮询场景下极易超时或假绿)。
3.2 Trace:失败后能「回放」一次操作
本框架在 browser_fixture 里对每个 context 开启 tracing:
python
context.tracing.start(screenshots=True, snapshots=True, sources=True)
仅在用例失败时把 trace.zip 落到磁盘并挂到 Allure;通过则 stop() 不落盘,节省空间。本地可用:
bash
playwright show-trace output/traces/2026-07-28-1/test_tc_login_001.zip
这比「只有一张失败截图」强一个数量级:能看到点击前 DOM、网络请求、控制台错误。
3.3 视频与视口
创建 context 时固定 1920x1080,并开启 record_video_dir。pytest.ini 配置了 --video=retain-on-failure,失败视频最终会作为 Allure 附件。对「偶现、难复现」问题特别有用。
3.4 定位器 API 与可测试性
框架要求优先 get_by_test_id,逼着前端配合加 data-testid。短期可能多沟通一次,长期比 XPath / 动态 class 稳得多。
四、技术点详解(二):Pytest 如何「托住」整套框架?
Playwright 只是浏览器能力;可维护性几乎全部来自 Pytest 的 fixture + hook。
4.1 命令行环境切换:--env
conftest.py 注册:
python
parser.addoption("--env", action="store", default="default", help="Environment config name")
pytest.ini 默认带上 --env=default,本地零参数也能跑。CI 上则:
bash
pytest tests/test_login.py --env=jenkins
对应加载 configs/env.jenkins.yaml。
4.2 session 级 browser,function 级 page
| Fixture | Scope | 含义 |
|---|---|---|
config |
session | 整次运行只读一次 YAML |
browser |
session | 整次运行共用一个浏览器进程 |
page |
function(默认) | 每个用例独立 context + page,互不污染 |
这是 E2E 里很经典的折中:启动浏览器贵,隔离用 context/page 做。每个用例的 cookie、storage、打开的页都是干净的。
浏览器启动参数完全来自配置:
yaml
# configs/env.default.yaml
base_url: "http://localhost:5173"
browser: "chromium"
headless: false # 本地调试可看界面
slowmo: 0
yaml
# configs/env.jenkins.yaml
base_url: "http://demo-frontend" # Docker 网络内服务名
browser: "chromium"
headless: true
slowmo: 0
注意:Jenkins 里用的是 Compose 网络里的服务名 demo-frontend,不是宿主机的 localhost。这是容器化 CI 最常见的坑之一,本框架用「环境 YAML」优雅解决。
4.3 失败钩子:截图 + 页面源码 + Allure 附件
pytest_runtest_makereport 在 call 阶段失败 时:
- 调
save_screenshot存 PNG - 把
page.content()存成 HTML - 二者都
allure.attach.file
teardown 阶段再尝试附加视频。这样 Allure 报告里点开失败用例就能直接看证据,而不必翻本地目录。
4.4 pytest.ini 里的「默认体验」
ini
[pytest]
addopts =
-s
-q
--env=default
--alluredir=output/reports/allure-results
--video=retain-on-failure
log_cli = true
log_file = output/logs/test.log
含义速记:
-s:不截获 print/日志,调试友好--alluredir=...:每次跑测自动写 Allure 原始结果log_file:落盘统一日志,方便事后按TEST START/END切片排查
五、技术点详解(三):Page Object Model(POM)怎么写才规范?
5.1 职责切分(强制)
| 层 | 允许 | 禁止 |
|---|---|---|
| Page Object | 定位器、导航、业务操作(login/fill/click) | expect 断言 |
| Test Case | Arrange / Act / Assert,调用 Page 方法 | 直接 page.locator(...) |
原因:断言散落在 Page 里会导致「这个方法到底是在操作还是在验证」变得模糊,复用时也容易把断言条件写死。
5.2 LoginPage 示例(项目真实代码结构)
python
class LoginPage:
URL_PATH = "/"
def __init__(self, page: Page) -> None:
self.page = page
self.username_input: Locator = page.get_by_test_id("input-username")
self.password_input: Locator = page.get_by_test_id("input-password")
self.submit_button: Locator = page.get_by_test_id("btn-login")
# 页面暂无 testid 时的兜底 + TODO,推动前端补齐
self.error_message: Locator = page.locator(".ant-message-notice-error")
def open(self, base_url: str) -> None:
self.page.goto(f"{base_url}{self.URL_PATH}")
def login(self, username: str, password: str) -> None:
self.username_input.fill(username)
self.password_input.fill(password)
self.submit_button.click()
5.3 Locator 优先级(框架铁律)
| 优先级 | 策略 | 示例 |
|---|---|---|
| 1 | data-testid |
page.get_by_test_id("btn-login") |
| 2 | 稳定 id |
page.locator("#user-menu") |
| 3 | placeholder |
page.get_by_placeholder("请输入密码") |
| 4 | 语义 CSS(≤3 层) | page.locator("form[name=login] button[type=submit]") |
| 5 | 文本(仅表格单元格等) | page.get_by_text("已完成", exact=True) |
绝对禁止 :XPath、动态哈希 class、样式类当主定位(如 btn-primary)、带数字后缀的动态 id。
5.4 测试结构:Arrange / Act / Assert
python
@allure.feature("User Authentication")
class TestLogin:
@allure.story("Valid login")
@allure.title("使用有效凭据登录成功并跳转首页")
def test_tc_login_001(self, page: Page, config: dict) -> None:
# Arrange
login_page = LoginPage(page)
home_page = HomePage(page)
login_page.open(config["base_url"])
# Act
login_page.login(username="admin", password="123456")
# Assert
expect(page).to_have_url(f"{config['base_url']}/home")
expect(home_page.welcome_text).to_be_visible()
Allure 的 @feature / @story / @title 在本项目是 强制项:报告里才能按业务域聚合,而不是一堆函数名。
5.5 参数化数据外置
无效登录用例不写死在代码里,而从 data/login/invalid_login_cases.json 加载:
json
[
{
"id": "LOGIN_002_WRONG_PASSWORD",
"description": "用户名正确,密码错误",
"username": "admin",
"password": "wrong123"
}
]
配合:
python
_INVALID_LOGIN_CASES = load_test_data("login", "invalid_login_cases.json")
@pytest.mark.parametrize(
"case",
_INVALID_LOGIN_CASES,
ids=[c["description"] for c in _INVALID_LOGIN_CASES],
)
def test_tc_login_002(self, page, config, case):
...
ids= 用中文 description,pytest / Allure 报表里一眼能看懂「挂的是哪一种错误输入」。
六、技术点详解(四):配置中心与多环境
加载逻辑极简但够用:
python
def load_config(env="default"):
file_path = f"./configs/env.{env}.yaml"
if not os.path.exists(file_path):
file_path = "./configs/env.default.yaml"
with open(file_path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
扩展建议(真实项目常做,本仓库刻意保持简单):
- 密钥不上仓库:账号密码用环境变量 / CI Secret 注入,YAML 只放非敏感项
- 按产品线拆配置 :
env.staging.yaml、env.uat.yaml - 超时统一配置 :把
expecttimeout、导航 timeout 也放进 YAML
本地默认 base_url 是 Vite 开发端口 http://localhost:5173。若你改了前端端口,必须同步改 YAML,否则全军覆没。
七、技术点详解(五):运行产物与 Allure 报告体系
7.1 output/ 目录约定
| 路径 | 内容 | 何时产生 |
|---|---|---|
output/reports/allure-results/ |
Allure 原始结果(json/附件) | 每次运行 |
output/reports/allure-report/ |
生成后的 HTML 报告(需手动 generate) | 执行 allure generate 后 |
output/logs/test.log |
运行日志 | 每次运行(追加) |
output/screenshots/<日期-序号>/ |
失败 PNG + HTML DOM | 失败 |
output/traces/<日期-序号>/ |
失败 trace.zip | 失败 |
output/videos/<日期-序号>/ |
失败视频 | 失败(并 attach 到 Allure) |
同一次运行,screenshots / traces / videos 使用相同的 <日期-序号>,方便对齐。
7.2 如何查看漂亮报告
先装 Allure Commandline,再在测试工程目录执行:
bash
allure generate output/reports/allure-results --clean -o output/reports/allure-report
allure serve output/reports/allure-results
Allure 能看到:Feature/Story 树、步骤、截图、页面源码、视频、Playwright Trace 压缩包。
7.3 和「仅 pytest 终端结果」的关系
- 终端:最快反馈,适合开发中快速迭代
- Allure:适合评审、回归汇总、失败归档
- Trace:适合深度排障,尤其是「我本地过、CI 不过」
三者互补,不要指望一种产物打天下。
八、技术点详解(六):Docker 与 Jenkins 持续集成
8.1 docker-compose 一键起基础设施
docker-compose.yml 起两个服务:
- jenkins :挂载宿主机
docker.sock,让 Jenkins 容器能再拉起测试 Agent 容器(Docker-out-of-Docker / socket 挂载模式) - demo-frontend :被测前端,映射
3000:80,并加入e2e-test网络
测试 Agent 与前端同处 e2e-test 网络时,用 http://demo-frontend 访问,而不是 localhost:3000。
8.2 测试 Agent 镜像
playwright-auto-test/Dockerfile 基于官方 Playwright Python 镜像,例如:
dockerfile
FROM mcr.microsoft.com/playwright/python:v1.56.0-noble
WORKDIR /workspace
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
COPY . /workspace/
好处:浏览器依赖、系统库都已在官方镜像打好,避免 CI 上「装 Chromium 失败」。
本地可手动:
bash
cd playwright-auto-test
docker build -t playwright-test-agent:latest .
docker run --rm --network e2e-test -v $(pwd):/workspace \
playwright-test-agent:latest \
pytest tests/test_login.py --env=jenkins
8.3 Jenkinsfile 流水线逻辑
核心两阶段:
- Checkout:拉仓库
- Run UI Tests :用
playwright-test-agent:latest作为 agent,--network e2e-test,执行pytest tests/test_login.py --env=jenkins
post 阶段:
- 收集 Allure 结果
emailext发邮件,附带 Build 链接与 Allure 链接
注意:Jenkinsfile 里 Allure 路径若与本地 output/reports/allure-results 不完全一致,接入时要按实际 pytest.ini / 工作目录对齐,这是拷贝框架到自有仓库时最需要改的一处。
九、技术点详解(七):AI Skills ------ 从「手写」到「规划 → 生成 → 分诊」
本仓库的差异化能力,是内置了三套面向 Claude Code / Cursor 类 Agent 的 Skills。即使你暂时不用 AI,理解这套流水线也对「如何把测试设计产品化」很有启发。
9.1 Playwright Test Planner(规划)
- 输入:功能意图 + 页面 URL
- 手段 :通过 Playwright MCP 抓取无障碍树(accessibility snapshot),提取可交互元素
- 输出 :
specs/{feature}_spec.yaml(语言无关规格)
定位策略优先级与人工规范一致:data-testid > 稳定 id > placeholder > 语义 CSS。找不到稳定定位时标记 needs_testid: true,留给生成器写 TODO。
触发示例:分析页面 / 生成 spec / 帮我规划测试。
9.2 Playwright Test Generator(生成)
- 唯一权威输入 :
spec.yaml(没有 spec 不允许瞎生成) - 输出 :
pages/{feature}_page.py+tests/test_{feature}.py(含 Allure 注解、AAA 注释) - 校验 :在 venv 里跑
pytest
参数化场景会生成 @pytest.mark.parametrize,并从 data/ 读 JSON。
触发示例:根据 spec 生成 / 帮我生成 POM。
9.3 Triaging E2E Failures(失败根因分析)
失败后自动/半自动读取:
output/traces/output/screenshots/output/logs/test.log
按 triage_rules.md 分类,例如:
| 类别 | 子类型 | 含义 |
|---|---|---|
real_bug |
api_failure |
后端 4xx/5xx 导致页面不可用 |
real_bug |
assertion_mismatch |
期望与实际不符 |
flaky_test |
selector_renamed |
UI 改了 testid,用例未更新 |
flaky_test |
element_missing |
渲染晚或功能删除 |
flaky_data |
data_contamination |
数据污染 |
unknown |
--- | 证据不足,建议 show-trace |
高置信度的 flaky 还可按 fix_patterns.md 给出修复建议。仓库里还有故意失败的 test_failures_showcase.py,专门用来演示各类 trace 特征。
9.4 和 playwright codegen 的区别
官方录制(playwright codegen)产出的是线性脚本,不会直接变成符合本仓库规范的 POM。本项目推荐路径是:
MCP 探索页面 → spec.yaml → Generator → pages/ + tests/
录制最多当「摸清路径」的草稿。
十、最小可运行方案(个人电脑)
不需要 Docker / Jenkins,也能完成本地闭环。
10.1 环境准备
- Python 3.12(推荐;仓库 CI 亦用 3.12,最低 3.11+)
- Node.js 20+(跑 Demo 前端)
- (可选)Allure CLI、Docker Desktop
官网安装的 Python 默认带 pip。Windows 记得勾选 Add to PATH。
10.2 启动被测前端
bash
cd demo-frontend
npm install
npm run dev
看终端打印的 Local 地址(常见 http://localhost:5173),与 configs/env.default.yaml 保持一致。
10.3 安装测试依赖并执行
bash
cd playwright-auto-test
python -m pip install -r requirements.txt
python -m playwright install chromium
python -m pytest tests/test_login.py --env=default
建议先跑 test_login.py,不要一上来跑全部 tests/(其中可能包含故意失败的 showcase)。
10.4 依赖清单解读(requirements.txt)
| 包 | 作用 |
|---|---|
| playwright / pytest-playwright | 浏览器自动化与 Pytest 插件能力 |
| pytest / pytest-xdist / pytest-rerunfailures | 运行、并行、失败重试 |
| allure-pytest / allure-python-commons | 报告 |
| pyyaml | 环境配置 |
| loguru / rich | 日志与终端展示 |
| requests | 辅助 HTTP(扩展用) |
| pylint | 代码质量门禁(仓库要求评分 ≥ 8.0) |
十一、如何把框架用到真实业务项目?
11.1 迁移清单
- 拷贝
playwright-auto-test/到业务仓(或独立 E2E 仓) - 删除 Demo 相关用例 / 页面,保留目录骨架与 fixtures
- 新建
env.staging.yaml,base_url指向真实环境 - 推动前端为关键控件加
data-testid - 先写 1~2 条冒烟(登录 + 核心路径)跑绿
- 再接 CI(Jenkins / GitHub Actions / GitLab CI 均可)
- Allure 报告挂到流水线制品或报告插件
11.2 真实项目几乎必补的能力
框架示例没有替你做完:
- 登录态复用:SSO、Cookie 注入、storageState,避免每条用例都走完整登录(冒烟除外)
- 测试账号与数据:独立租户、造数/清数,防止污染
- 密钥管理:密码、Token 走 Secret
- 并行策略:xdist 下注意数据冲突与浏览器资源
- 分层测试:E2E 只保核心路径;大量逻辑下放接口测 / 单测
11.3 本框架覆盖边界(避免误解)
它擅长:Web UI E2E + 报告 + CI +(可选)AI 辅助。
它不自动等于你会:API 测试、移动端、性能压测、安全测试、视觉回归等。那些需要另建能力栈。
十二、编码与工程规范摘要(可直接当团队约定)
摘自项目 CLAUDE.md,建议团队直接采纳:
- Python:PEP8、类型注解、禁止裸
except、禁止测试/pages里print - Playwright:仅 Sync API;断言用
expect,不用assert locator.is_visible() - 测试:一条用例验证一个行为;用例间独立
- 变更后跑 pylint(≥ 8.0)与 pytest
- Git:conventional commits;不直接推 main,走分支 + PR
这些「看起来像约束」的东西,恰恰是自动化项目活过半年的关键。
十三、常见问题 FAQ
Q1:Page Object 是手写还是录制?
两者都可以。本仓库示例有手写,也有「Planner → spec → Generator」生成。官方 codegen 可辅助探索,但产出需整理进 POM。
Q2:报告在哪?
Allure 原始结果:playwright-auto-test/output/reports/allure-results/。需 allure serve 或 allure generate 才能看 HTML。失败证据另见 output/screenshots|traces|videos。
Q3:必须装 Docker 吗?
个人学习最小方案不需要。Docker/Jenkins 面向环境一致性与 CI。
Q4:Python 装 3.12.9 还是 3.12.10?
同属 3.12,选更新的补丁版(如 3.12.10)即可。
Q5:本地 headless 怎么开?
改 env.default.yaml 的 headless: true,或另建一份调试配置。
Q6:为什么 CI 里不能写 localhost?
容器网络里 localhost 是容器自己。应使用 Compose 服务名或宿主机可达地址,并通过 --env 切换。
Q7:用例全绿但没有截图?
正常。截图 / trace / 视频默认在失败时保留。
十四、推荐学习路径(按周拆)
| 阶段 | 目标 | 练习 |
|---|---|---|
| 第 1 天 | 环境跑通 | Demo + test_login.py 绿 |
| 第 2~3 天 | 读懂 POM + fixture | 自己加一个简单页面用例 |
| 第 4~5 天 | 报告与失败取证 | 故意写挂,看截图/trace/Allure |
| 第 2 周 | 配置与参数化 | 多环境 YAML + JSON 数据驱动 |
| 第 3 周 | CI | Docker Agent 或任意 CI 跑通一条冒烟 |
| 第 4 周 | 工程化 / AI | 试 Planner→Generator;或整理团队规范 |
十五、总结
这套 Playwright + Pytest + Allure 框架的价值,不在于「又一个 Demo」,而在于把 E2E 工程里最容易烂掉的几件事一次性做对了:
- POM + Locator 规范:降低 UI 变更成本
- YAML 多环境:本地与 CI 同一套代码
- 失败取证闭环:截图 / DOM / 视频 / Trace / 日志
- Allure 可交付报告:面向人而不仅面向终端
- Docker + Jenkins:执行环境可复现
- AI Skills(可选加速器):规格驱动生成与失败分诊
如果你只记住一句话:
自动化的竞争力不是会点按钮,而是失败可诊断、环境可切换、用例可维护、结果可交付。
欢迎 Clone 仓库动手跑一遍登录冒烟,再把 pages/ / tests/ 替换成你们自己的业务页面。把本文当说明书,把仓库当脚手架------这才是它设计出来的用法。