我最近花了两天时间深入学习了一个基于 Playwright 和 Pytest 的 Web UI 自动化测试框架。这个项目不仅让我对自动化测试有了全新的认识,更让我体会到了企业级测试框架的设计理念。
今天,我想通过这篇博客分享我的学习心得,包括项目的架构设计、技术选型、核心实现,以及我个人的学习过程。
项目概述
什么是 Playwright-UI?
这是一个企业级的 Web UI 自动化测试框架,专门用于测试 Web 应用的各种功能模块。项目采用现代化的技术栈,实现了完整的测试流程:从用例编写到报告生成。
核心特性:
基于 Playwright 的浏览器自动化
采用 Page Object Model (POM) 设计模式
支持网络请求拦截和 Mock API
自动生成美观的 Allure 测试报告
完整的调试和追踪功能
项目架构深度剖析
整体架构图
playwright-ui/
├── cases/ # 测试用例集
├── pages/ # 页面对象层 (POM)
├── mocks/ # Mock API 数据
├── plugins/ # 自定义插件
├── reports/ # 测试报告临时文件
├── allure_report/ # 生成的 Allure 报告
├── test-results/ # 截图、视频、追踪日志
├── conftest.py # Pytest 全局配置
├── pytest.ini # Pytest 参数配置
├── run.py # 框架运行入口
└── requirements.txt # 项目依赖
核心目录详解
cases/ - 测试用例集
这是项目的心脏,存放所有测试用例。每个功能模块都有对应的测试类:
cases/
├── test_login.py # 登录功能测试
├── test_register.py # 注册功能测试
├── test_project_list.py # 项目列表测试
├── test_add_project.py # 新增项目测试
├── test_add_module.py # 新增模块测试
├── test_env_list.py # 环境列表测试
└── conftest.py # 用例级配置
设计亮点:
按功能模块组织测试类
使用 Pytest 的 class 结构
每个测试方法都有清晰的文档字符串
pages/ - 页面对象层 (POM 模式)
这是项目的灵魂,实现了经典的 Page Object Model 设计模式:
pages/
├── login_page.py # 登录页面封装
├── register_page.py # 注册页面封装
├── project_list_page.py # 项目列表页面
├── add_project_page.py # 新增项目页面
├── add_module_page.py # 新增模块页面
└── list_env_page.py # 环境列表页面
POM 模式的核心思想:
将页面元素定位和业务操作分离
提高代码的可维护性和复用性
当页面 UI 变化时,只需修改 Page 类
mocks/ - Mock API 数据
用于模拟各种异常场景的 API 响应:
mocks/
└── mock_api.py # Mock API 定义
Mock 的作用:
测试异常场景(400、500 错误)
模拟后端未开发完成的情况
提高测试的稳定性和速度
配置文件详解
pytest.ini - Pytest 参数配置:
pytest
addopts = -p no:playwright # 禁用默认插件
-p no:base_url # 禁用基础 URL 插件
--headed # 有界面模式
--tracing=retain-on-failure # 失败时保留追踪
--screenshot=only-on-failure # 失败时截图
--video=retain-on-failure # 失败时录视频
--base-url=http://47.116.12.183 # 测试服务器
conftest.py - 全局配置:
注册自定义插件
配置浏览器参数
设置 Allure 报告的动态标题
💻 核心技术实现
Playwright 浏览器自动化
项目使用 Playwright 进行浏览器操作,支持多种定位方式:
元素定位
page.get_by_label("用户名:") # 按标签定位
page.get_by_text("立即登录") # 按文本定位
page.locator('[data-testid="btn"]') # CSS 选择器
元素交互
element.fill("输入内容") # 填充输入框
element.click() # 点击操作
element.select_option("选项值") # 选择下拉框
Page Object Model 实现
以 LoginPage 为例:
class LoginPage:
def init (self, page: Page):
self.page = page
元素定位器
self.locator_username = page.get_by_label("用 户 名:")
self.locator_password = page.get_by_label("密 码:")
self.locator_login_btn = page.locator('text=立即登录')
# 错误提示元素
self.locator_username_tip1 = page.locator('[data-fv-validator="notEmpty"]')
self.locator_login_error = page.locator('text=账号或密码不正确!')
def navigate(self):
self.page.goto("/login.html")
def fill_username(self, username):
self.locator_username.fill(username)
def fill_password(self, password):
self.locator_password.fill(password)
def click_login_button(self):
self.locator_login_btn.click()
def login(self, username, password):
"""完整登录流程"""
self.fill_username(username)
self.fill_password(password)
self.click_login_button()
网络请求拦截
Playwright 支持拦截和验证网络请求:
验证 API 请求内容
with page.expect_request('**/api/login') as req:
login_button.click()
assert req.value.method == 'POST'
assert req.value.post_data_json == {
'username': 'testuser',
'password': 'testpass'
}
验证 API 响应
with page.expect_response('**/api/login') as res:
login_button.click()
assert res.value.status == 200
assert res.value.ok
Mock API 实现
通过 Mock 模拟各种异常场景:
模拟项目名重复的错误
mock_project_400 = {
"url": "**/api/project",
"handler": lambda route: route.fulfill(
status=400,
body=json.dumps({
"errors": {"project_name": "项目名已存在"},
"message": "Input payload validation failed"
})
)
}
模拟服务器错误
mock_project_500 = {
"url": "**/api/project",
"handler": lambda route: route.fulfill(
status=500,
body="服务端错误"
)
}
📝 测试用例编写
基本测试结构
class TestLogin:
"""登录功能测试"""
@pytest.fixture(autouse=True)
def start_for_each(self, unlogin_page: Page):
"""前置操作:初始化页面对象"""
self.login = LoginPage(unlogin_page)
self.login.navigate()
yield
# 后置操作(如果需要)
def test_login_success(self):
"""正常登录流程"""
self.login.fill_username("py")
self.login.fill_password("123456")
self.login.click_login_button()
# 断言验证
expect(self.login.page).to_have_title("首页")
expect(self.login.page).to_have_url("/index.html")
def test_login_empty_username(self):
"""用户名为空验证"""
self.login.fill_username("")
self.login.fill_password("123456")
self.login.click_login_button()
# 验证错误提示
expect(self.login.locator_username_tip1).to_be_visible()
expect(self.login.locator_username_tip1).to_contain_text("不能为空")
@pytest.mark.parametrize("username,password", [
["py", "wrongpass"],
["wronguser", "123456"],
])
def test_login_error(self, username, password):
"""参数化测试:错误账号密码"""
self.login.fill_username(username)
self.login.fill_password(password)
self.login.click_login_button()
expect(self.login.locator_login_error).to_be_visible()
测试场景覆盖
项目测试了完整的用户旅程:
表单验证(必填、空值、格式等)
正常业务流程
异常错误处理
API 请求验证
页面导航验证
测试报告和调试
Allure 报告
项目使用 Allure 生成美观的测试报告:
运行测试:
python run.py
生成报告:
allure generate ./reports -o ./allure_report --clean
查看报告:
allure serve ./reports
或直接打开 allure_report/index.html
报告包含:
测试用例执行结果统计
每个用例的详细执行时间
失败用例的截图和视频
测试步骤的详细日志
调试功能
Playwright 提供了强大的调试工具:
1. 截图调试
--screenshot=only-on-failure # 失败时自动截图
2. 视频录制
--video=retain-on-failure # 失败时录制视频
3. 追踪日志
--tracing=retain-on-failure # 失败时记录追踪
查看追踪日志
playwright show-trace test-results/trace.zip
测试报告截图
服务器有点问题,很多测试没过
绿色 (13):通过(Passed)。
红色 (6):Product defects(产品缺陷)。通常指断言失败,即程序运行了,但结果不对。
黄色 (38):Test defects(测试脚本缺陷/环境问题)。环境不通挂掉。
