
一、项目介绍在前后端分离成为主流的今天Web UI 自动化测试是保障前端质量、回归验证的核心手段。传统 Selenium 框架存在元素等待繁琐、驱动版本匹配困难、多场景调试能力弱等痛点而微软开源的 Playwright 凭借自动等待、多引擎原生支持、强大的追踪能力逐渐成为 Web 自动化的首选方案。本文将基于一套完整的企业级项目实践详细拆解Playwright Pytest Allure POM的 UI 自动化测试框架设计从目录架构、核心分层、Fixture 设计到 Mock 机制与报告体系全方位讲解可落地的自动化测试工程化方案。技术栈PythonpytestplaywrightAjaxallureJenkinsdockerLinuxgiteehttps://gitee.com/xiaomalou/playwright-ui.git二、项目整体架构D:\playwright-ui ├── .venv/ # 虚拟环境Python 和所有第三方库都装这里 ├── .idea/ # PyCharm 的项目配置不用管 ├── pages/ # ★ 页面对象层每个页面封装成一个类 │ ├── login_page.py # 登录页 │ ├── register_page.py # 注册页 │ ├── project_list_page.py # 项目列表页 │ ├── add_project_page.py # 新增项目页 │ ├── add_module_page.py # 新增模块页 │ └── list_env_page.py # 环境列表页 ├── cases/ # ★ 测试用例层写测试的地方 │ ├── conftest.py # 用例层的 fixture全局登录、独立登录上下文 │ ├── test_login.py # 登录用例 │ ├── test_register.py # 注册用例 │ ├── test_project_list.py # 项目列表用例 │ ├── test_add_project.py # 新增项目用例 │ ├── test_add_module.py # 新增模块用例 │ ├── test_env_list.py # 环境列表用例 │ └── more_accounts/ # 多账号切换演示 │ ├── conftest.py # admin 管理员上下文 │ └── test_x.py # A 账号创建、B 账号删除 ├── mocks/ # ★ Mock 数据拦截接口返回假数据 │ └── mock_api.py ├── plugins/ # ★ 二次开发插件官方插件源码的本地改造版 │ ├── pytest_playwright.py # 复制官方插件 增加 allure 截图/视频 │ └── pytest_base_url_plugin.py # 复制官方 base-url 插件 ├── conftest.py # pytest 全局配置根目录 ├── pytest.ini # pytest 运行规则配置 ├── run.py # 框架运行入口 ├── requirements.txt # 依赖清单 ├── reports/ # allure 结果文件运行自动生成 ├── allure_report/ # 生成的 HTML 测试报告运行自动生成 ├── test-results/ # 截图、视频、trace 临时文件运行自动生成 └── README.md # 项目说明pages 层页面对象模型的核心每个页面对应一个类内部封装元素定位器、页面操作方法测试用例不直接接触元素定位只调用页面方法。cases 层业务测试用例按模块拆分通过 Fixture 注入页面对象聚焦业务场景与断言逻辑。mocks 层统一管理接口 Mock 数据覆盖成功、失败、空数据、服务器异常等场景实现前端 UI 的全场景验证。plugins 层对官方 Playwright 插件与 base_url 插件进行二次开发扩展窗口最大化、HTTPS 忽略、全局启动参数等定制化能力。conftest 体系分层设计 Fixture全局层管理浏览器 / 上下文用例层管理单页面生命周期与失败捕获。三、核心架构详细解1. POM 页面对象模型实现元素与用例解耦框架严格遵循 POM 设计原则将每个页面的元素定位、操作逻辑封装为独立的 Page 类测试用例仅关注业务流程与结果断言。设计示例pages/login_page.pyfrom playwright.sync_api import Page 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_register_link page.locator(text没有账号点这注册) # 用户名输入框提示语 self.locator_username_tip1 page.locator([data-fv-validatornotEmpty][data-fv-forusername]) self.locator_username_tip2 page.locator([data-fv-validatorstringLength][data-fv-forusername]) self.locator_username_tip3 page.locator([data-fv-validatorregexp][data-fv-forusername]) # 密码输入框提示语 self.locator_password_tip1 page.locator([data-fv-validatornotEmpty][data-fv-forpassword]) self.locator_password_tip2 page.locator([data-fv-validatorstringLength][data-fv-forpassword]) self.locator_password_tip3 page.locator([data-fv-validatorregexp][data-fv-forpassword]) # 账号或密码不正确 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 click_register_link(self): self.locator_register_link.click() def login(self, username, password) - None: 完整登录操作 self.locator_username.fill(username) self.locator_password.fill(password) self.locator_login_btn.click()元素定位仅维护一份页面迭代时只需修改 Page 类无需改动用例操作方法语义化测试用例可读性大幅提升支持 Allure 步骤埋点报告中可清晰看到每一步操作2. Fixture 分层设计上下文隔离与生命周期管理框架基于 Pytest Fixture 同时解决了「登录态复用」与「非登录场景隔离」的核心矛盾。1全局浏览器与上下文session 级在plugins/pytest_playwright.py中定义 session 级别的browser和contextFixture全局只启动一次浏览器复用浏览器进程提升执行效率。同时内置失败自动截图、录屏、Trace 追踪能力。2登录态全局复用针对需登录的业务场景提供login_firstFixturesession 级别执行前登录一次并保存上下文状态后续所有登录态用例直接复用避免每个用例重复登录。pytest.fixture(scopesession) def login_first(context, base_url, pytestconfig) - None: 有些网站网页关闭cookie就失效了全局登录一次 # context browser.new_context(base_urlbase_url, no_viewportTrue) print(base_url----, base_url) page context.new_page() LoginPage(page).navigate() LoginPage(page).login(py, 123456) # 等待登录成功页面重定向 page.wait_for_url(url**/index.html)3非登录场景独立上下文针对登录、注册等无需前置登录的页面提供unlogin_contextFixturemodule 级别创建独立的浏览器上下文完全隔离 Cookie避免全局登录态导致登录页直接跳转到首页的问题。unlogin_page为什么登录/注册要另起炉灶如果登录页用例也复用已登录的 context浏览器会带着 cookie 直接跳到首页index.html根本停不到登录页——用例就没法测了。所以unlogin_context用browser 新开一个干净 contextmodule 级unlogin_page从它开页不加载 cookie。代码里注释写得很直白避免全局先登录加载 cookie导致有些打开登录页直接跳到首页去了。pytest.fixture(scopemodule) def unlogin_context(browser, base_url, pytestconfig, browser_context_args: Dict): 登录注册页面不依赖于先登录单独创建独立的 context 上下文 避免全局先登录加载cookie导致有些打开登录页直接跳到首页去了 :return: context browser.new_context(**browser_context_args) yield context context.close() pytest.fixture def unlogin_page(unlogin_context: BrowserContext, pytestconfig: Any, request: pytest.FixtureRequest): 登录注册页面不依赖于先登录单独创建独立的 page 对象 带上用例失败截图和添加视频功能 pages: List[Page] [] unlogin_context.on(page, lambda page: pages.append(page)) page unlogin_context.new_page() yield page failed request.node.rep_call.failed if hasattr(request.node, rep_call) else True # 截图判断 screenshot_option pytestconfig.getoption(--screenshot) capture_screenshot screenshot_option on or (failed and screenshot_option only-on-failure) print(fcapture_screenshot:{capture_screenshot}) if capture_screenshot: for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder( pytestconfig, request, ftest-{human_readable_status}-{index 1}.png ) print(f-----------------{screenshot_path}) try: page.screenshot(timeout5000, pathscreenshot_path) # 把截图放入allure报告 allure.attach.file(screenshot_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.PNG ) except Error: pass page.close() # 用例添加视频 video_option pytestconfig.getoption(--video) preserve_video video_option on or (failed and video_option retain-on-failure) if preserve_video: for page in pages: video page.video if not video: continue try: video_path video.path() file_name os.path.basename(video_path) file_path _build_artifact_test_folder(pytestconfig, request, file_name) video.save_as(pathfile_path) # 放入视频 allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.WEBM) except Error: # Silent catch empty videos. pass4断言的几种姿势① 页面元素断言:expect(locator).to_be_visible() # 元素可见expect(locator).to_contain_text(不能为空) # 包含某段文字expect(locator).not_to_be_enabled() # 按钮不可点击提交按钮 disabledexpect(locator).to_have_value(test) # 输入框的值expect(locator).to_have_count(1) # 匹配到 1 个元素② 页面跳转断言expect(self.login.page).to_have_title(首页) # 断言标题expect(self.login.page).to_have_url(/index.html) # 断言 URL③ 显式等待导航with self.login.page.expect_navigation(url**/index.html):self.login.click_login_button() # 点击后页面跳转expect_navigation 等待跳转完成④ 断言 Ajax 请求:with self.login.page.expect_request(**/api/login) as req:self.login.click_login_button() # 点登录按钮assert req.value.method POST # 断言请求方法是 POSTassert req.value.header_value(content-type) application/json # 断言请求头assert req.value.post_data_json {username: py, password: 123456} # 断言请求体⑤ 断言 Ajax 响应ith self.login.page.expect_response(**/api/login) as res:self.login.click_login_button()assert res.value.okassert res.value.status 2003. 接口 Mock 机制前端 UI 全场景验证通过 Playwright 原生的page.route()接口拦截能力结合 mocks 层统一管理的模拟数据可在不依赖后端的前提下覆盖各类异常边界场景。Mock 示例模拟项目名称重复 400 场景mock_project_400 { url: **/api/project, handler: lambda route: route.fulfill( status400, bodyjson.dumps({ errors: {project_name: yo yo 已存在}, message: Input payload validation failed }) ) }用例中使用 Mockdef test_add_project_400(self, page: Page): 项目名称重复弹出模态框 self.add_project.fill_project_name(yo yo) self.add_project.fill_publish_app(xx) # 拦截接口返回400模拟数据 page.route(**mock_api.mock_project_400) self.add_project.click_save_button() # 断言前端提示 expect(self.add_project.locator_boot_box).to_be_visible() expect(self.add_project.locator_boot_box).to_contain_text(已存在)Mock 覆盖场景正常 200 返回、参数校验 400、服务器异常 500列表空数据、单条数据、分页场景权限不足 403、接口超时等异常场景4. Allure 报告深度集成框架实现了 Allure 报告的全方位集成提升报告可读性与问题定位效率。动态用例标题自动读取用例 docstring 作为 Allure 用例标题无需手动打标签def pytest_runtest_call(item: Item): if item.parent._obj.__doc__: allure.dynamic.feature(item.parent._obj.__doc__) # 类 docstring → feature if item.function.__doc__: allure.dynamic.title(item.function.__doc__) # 方法 docstring → title失败附件自动嵌入失败截图、录制视频、Trace 文件自动挂载到对应用例allure.attach.file(screenshot_path, namef{request.node.name}-failed-1, attachment_typeallure.attachment_type.PNG)操作步骤埋点页面操作方法通过allure.step封装报告中可还原完整操作路径allure.step(导航到注册页) # 或用 f-string 带参数 def navigate(self): self.page.goto(/register.html) allure.step(f输入用户名:{username}) def fill_username(self, username): self.locator_username.fill(username)5. 插件化扩展能力项目未直接使用官方pytest-playwright插件而是通过自定义插件实现了更贴合企业场景的扩展禁用官方插件自主管理浏览器启动参数默认窗口最大化、忽略 HTTPS 证书错误addopts -p no:playwright -p no:base_url ...统一 base_url 管理支持命令行、ini 配置、环境变量多种配置方式保留官方全部命令行参数--headed、--browser、--tracing 等同时新增定制化能力四、框架运行与使用指南1. 环境搭建# 1. 安装Python依赖 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # 2. 安装Playwright浏览器驱动 playwright install # 也可仅安装指定引擎 playwright install chromium2. 执行测试用例方式一运行入口文件python run.py方式二命令行执行# 执行全部用例生成Allure结果 pytest --alluredir ./reports # 指定模块执行 pytest cases/test_login.py --alluredir ./reports3. 查看测试报告# 执行全部用例生成Allure结果 pytest --alluredir ./reports # 指定模块执行 pytest cases/test_login.py --alluredir ./reports4. 失败问题追踪框架默认开启「失败保留 Trace、截图、视频」策略定位问题时可通过 Trace 文件完整复现执行过程# 命令行查看Trace playwright show-trace test-results/xxx/trace.zip # 网页端查看访问 https://trace.playwright.dev/ 上传trace.zip五、框架优势与亮点高稳定性Playwright 原生自动等待机制消除强制等待解决元素未加载导致的用例不稳定问题强可维护性POM 分层设计页面变更不影响用例逻辑新增业务模块成本低调试效率高失败自动保留截图、视频、Trace 三件套问题定位从「看日志」变为「看回放」场景覆盖全内置 Mock 机制可覆盖后端未实现、异常难模拟的边界场景隔离性优秀浏览器上下文隔离登录态与非登录态互不干扰用例无耦合低学习成本保留 Playwright 原生 API 与 Pytest 使用习惯团队上手快六、总结这套 PlaywrightPytest 的 UI 自动化框架既吸收了 Playwright 在技术层面的先进性又通过 POM 分层、Fixture 设计、Mock 体系、报告集成完成了工程化落地。它不仅适用于中小型项目的回归测试也可通过扩展支持多环境执行、分布式运行、CI/CD 集成等企业级场景。对于正在从 Selenium 转向 Playwright或者想要搭建规范化 UI 自动化体系的团队这套架构可以作为参考模板根据自身业务进行裁剪与扩展快速构建起高效、稳定、可维护的 Web 自动化测试体系