ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Playwright+Pytest+Yaml+Allure:构建稳定可维护的UI自动化测试框架

Playwright+Pytest+Yaml+Allure:构建稳定可维护的UI自动化测试框架 1. 为什么我最终选了 Playwright Pytest Yaml Allure 这套组合做 UI 自动化测试这些年我踩过的坑比写过的用例还多。早期用 Selenium 那套光是等元素加载就能把人逼疯time.sleep()满天飞脚本跑起来跟抽奖一样今天过明天挂。后来团队要重构测试框架我把市面上主流的方案都摸了一遍最终敲定了Playwright Pytest Yaml Allure这个组合。不是说别的不好而是这套东西在稳定性、可维护性和报告呈现上刚好卡在了我最在意的几个点上。先说 Playwright。它跟 Selenium 最大的区别在于自动等待机制。Selenium 里你得手动写WebDriverWait条件写不对就超时Playwright 内置了 actionability 检查点击之前会自动确认元素可见、可交互、没被遮挡这一下就砍掉了大半的 flaky 用例。而且它原生支持多浏览器Chromium、Firefox、WebKit一套代码跑三端不用像以前那样为每个浏览器装不同的 driver。还有个我特别喜欢的点网络请求拦截和监听。做 UI 测试的时候经常需要 mock 后端返回或者验证某个接口有没有被调用Playwright 的page.route()和page.on(request)用起来非常顺手这在纯 UI 层面是很难做到的。Pytest 作为测试运行器生态成熟得没话说。参数化、fixture、插件体系、断言重写这些都是实打实提升效率的东西。特别是 fixture 的依赖注入能把登录、初始化数据、清理环境这些公共逻辑抽得干干净净用例里只写业务操作。Yaml 负责数据驱动把测试数据和代码分离改数据不用动 Python 文件产品和运营也能看懂。Allure 则是报告的门面层级清晰、步骤可视化、失败截图和视频都能挂上去给领导汇报的时候拿得出手。这套组合解决的核心问题是让 UI 自动化从能跑变成敢用。很多团队的自动化脚本写完就放着吃灰根本原因是维护成本太高、失败原因看不清、数据改起来麻烦。这四个工具各管一摊拼起来刚好覆盖了编写、运行、数据、报告全链路。适合谁学有一定 Python 基础、想从 Selenium 迁移或者从零搭建 UI 自动化体系的测试同学前端和运维想了解 E2E 测试的也能看。2. 框架整体设计与目录结构拆解2.1 分层设计思路搭框架最忌讳一上来就写用例。我见过太多项目所有代码堆在一个文件里几百行下去自己都找不到北。这套框架我按四层结构来组织页面对象层、测试用例层、数据层、公共层。每层职责单一改一处不影响其他地方。页面对象层Page Object封装元素定位和页面操作一个页面对应一个类。测试用例层只调用页面对象的方法不直接碰选择器。数据层放 Yaml 文件按模块拆分。公共层放配置读取、日志、断言工具、fixture 定义这些基础设施。这么分的好处是前端改了个按钮的 id我只需要改页面对象里的一行所有用例自动生效不用全局搜索替换。为什么用 Page Object 而不是直接写因为 UI 是易变的今天这个按钮叫submit-btn明天可能改成confirm-button。如果把选择器散落在几百个用例里维护就是灾难。Page Object 把这层变化隔离住了这是行业里验证过的最佳实践不是过度设计。2.2 目录结构长什么样我实际项目里的目录大概是这样project/ ├── config/ │ └── config.yaml # 环境配置、URL、账号 ├── pages/ │ ├── base_page.py # 页面基类封装通用操作 │ ├── login_page.py │ └── order_page.py ├── testcases/ │ ├── conftest.py # fixture 定义 │ ├── test_login.py │ └── test_order.py ├── testdata/ │ ├── login_data.yaml │ └── order_data.yaml ├── utils/ │ ├── yaml_util.py # Yaml 读写封装 │ ├── logger.py │ └── assert_util.py ├── reports/ # Allure 结果和报告 ├── pytest.ini # Pytest 配置 └── requirements.txtconftest.py放在 testcases 目录下Pytest 会自动识别里面的 fixture 对整个目录生效。pytest.ini里配置默认参数比如--alluredir./reports/allure-results这样每次跑用例自动生成 Allure 原始数据不用每次敲一长串命令。2.3 技术选型的取舍逻辑有人问我为什么不用 Robot Framework它也是关键字驱动、报告好看。我的理由是Robot 的语法对程序员不够友好复杂逻辑写起来别扭而且调试体验差。Pytest 是纯 Python想加什么逻辑加什么逻辑IDE 的断点调试、类型提示全都支持。Yaml 做数据驱动比 Excel 强Excel 版本管理是噩梦Yaml 是纯文本Git diff 一目了然。Allure 对比 Pytest 自带的 html 报告优势在于步骤化和附件。自带报告只能看到用例通过失败Allure 能把每个操作步骤展开失败时自动附上截图、页面源码、视频排查问题效率天差地别。这套选型不是拍脑袋是权衡了学习成本、维护成本、团队接受度之后的结论。3. 环境搭建与核心依赖配置3.1 Python 环境与依赖安装基础环境我建议 Python 3.9 以上Playwright 对版本有要求太老的版本装不上。虚拟环境用 venv 或者 conda 都行我习惯 venv轻量。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install playwright pytest pytest-playwright allure-pytest pyyaml playwright install # 下载浏览器内核这里有个坑要提醒playwright install下载的是 Chromium、Firefox、WebKit 三个浏览器加起来几百兆网络不好的时候容易断。可以只装需要的比如playwright install chromium。另外pytest-playwright这个插件提供了pagefixture省得自己管理浏览器生命周期强烈建议装上。requirements.txt里把版本锁死避免不同机器装出不同版本导致行为不一致playwright1.40.0 pytest7.4.3 pytest-playwright0.4.3 allure-pytest2.13.2 PyYAML6.0.13.2 Allure 命令行工具安装allure-pytest只是生成原始数据要出报告还得装 Allure 命令行工具。这个跟 Python 包是两码事很多人卡在这里。Mac 用brew install allureWindows 用 scoop 或者直接下压缩包配环境变量。装完执行allure --version能出版本号就对了。生成报告的命令是allure serve ./reports/allure-results它会起个本地服务自动打开浏览器。如果要生成静态报告给别人看用allure generate ./reports/allure-results -o ./reports/html --clean然后把 html 目录打包发出去。3.3 Pytest 配置文件详解pytest.ini是整个运行器的中枢我一般这么配[pytest] testpaths testcases python_files test_*.py python_classes Test* python_functions test_* addopts -vs --alluredir./reports/allure-results --clean-alluredir markers smoke: 冒烟测试 regression: 回归测试-vs让输出更详细--clean-alluredir每次跑之前清空旧数据避免报告里混入历史结果。markers 定义好之后可以用pytest -m smoke只跑冒烟用例回归的时候跑全量这个在 CI 里特别有用。注意--clean-alluredir会删掉整个结果目录如果你有并行跑用例再合并报告的需求别加这个参数否则后跑的会把先跑的覆盖掉。4. Yaml 数据驱动的落地细节4.1 为什么用 Yaml 而不是 Excel 或 JSON数据驱动的核心目的是让测试数据和代码解耦。Excel 的问题是二进制格式Git 里没法 diff两个人同时改还容易冲突。JSON 倒是文本但不支持注释写复杂数据结构时可读性差。Yaml 兼顾了可读性和结构化支持注释、锚点引用、多行字符串写测试数据非常舒服。举个实际例子登录用例的数据login_cases: - case_name: 正确账号密码登录 username: admin password: 123456 expect: 登录成功 - case_name: 错误密码登录 username: admin password: wrong expect: 密码错误这种结构一眼就能看懂产品经理都能帮你补充用例。而且 Yaml 支持嵌套复杂场景比如下单流程可以把商品信息、收货地址、支付方式都塞进去。4.2 Yaml 读取封装与常见坑直接yaml.safe_load()读文件就行但要注意编码问题Windows 下默认可能是 GBK读中文会乱码一定要指定encodingutf-8。我封装了一个工具类import yaml def read_yaml(file_path): with open(file_path, r, encodingutf-8) as f: return yaml.safe_load(f)用safe_load而不是load因为load能执行任意 Python 对象有安全风险虽然测试数据一般可控但养成好习惯。有个高频报错ModuleNotFoundError: No module named yaml注意包名是pyyaml不是yaml装错了就报这个。还有人问 toml 和 yaml 怎么选toml 更适合配置文件比如pyproject.tomlyaml 更适合数据驱动因为 yaml 的列表和嵌套表达更自然。4.3 参数化结合 Yaml 的写法Pytest 的pytest.mark.parametrize配合 Yaml 读取是数据驱动的标准姿势import pytest from utils.yaml_util import read_yaml data read_yaml(testdata/login_data.yaml) pytest.mark.parametrize(case, data[login_cases]) def test_login(page, case): # 用例逻辑 pass这样每一条 Yaml 数据都会生成一个独立的测试用例报告里能看到每条数据的执行结果失败时一眼定位是哪组数据的问题。比循环执行强太多循环的话一条失败后面全跳过而且报告里只有一个用例条目。实操心得Yaml 里的case_name字段建议加上配合pytest.param(..., idcase[case_name])可以让报告里的用例名显示成中文描述而不是case0、case1这种看不懂的编号。5. 页面对象与 Playwright 核心操作5.1 页面基类封装Playwright 的 API 已经很简洁了但项目里还是建议封一层基类把日志、截图、等待这些通用逻辑收进去。我的base_page.py大概长这样from playwright.sync_api import Page class BasePage: def __init__(self, page: Page): self.page page def goto(self, url): self.page.goto(url) def click(self, selector): self.page.click(selector) def fill(self, selector, text): self.page.fill(selector, text) def get_text(self, selector): return self.page.text_content(selector)看起来简单但好处是以后要加日志、加截图、加重试只改这一处。比如我想在每次点击失败时自动截图就在click方法里包一层 try-except。5.2 元素定位策略Playwright 推荐用get_by_role、get_by_text、get_by_label这些语义化定位比 CSS 和 XPath 稳定。因为前端改样式的时候role 和文本一般不会变。比如登录按钮page.get_by_role(button, name登录).click()比page.click(#login-btn)抗造得多。当然实际项目里不可能全用语义定位有些元素确实没有合适的 role那就退而求其次用 CSS。XPath 我尽量不用因为它对 DOM 结构太敏感加个 div 就可能失效。5.3 自动等待与超时设置Playwright 的自动等待是它最大的卖点。click之前会自动等元素 attached、visible、stable、enabled、receives events这五个条件都满足才点。所以大部分场景不需要手动wait_for。但有些特殊情况比如等一个 loading 消失还是得手动处理page.wait_for_selector(.loading, statehidden, timeout10000)超时时间默认 30 秒可以在pytest.ini或者 fixture 里全局改。我一般设成 15 秒太长的话失败用例拖慢整体执行太短又容易误报。踩过的坑Playwright 同步 API 和异步 API 不能混用。如果你看到It looks like you are using Playwright Sync API inside the asyncio loop这个报错说明你在异步环境里用了同步接口。Pytest 默认是同步的用sync_playwright就行别去碰 async。6. Fixture 设计与用例组织6.1 浏览器生命周期管理pytest-playwright插件提供了page、context、browser这些 fixture但默认是每个用例开一个新页面。如果用例多频繁开关浏览器很慢。我的做法是 session 级别开浏览器function 级别开 contextimport pytest from playwright.sync_api import sync_playwright pytest.fixture(scopesession) def browser(): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) yield browser browser.close() pytest.fixture(scopefunction) def page(browser): context browser.new_context() page context.new_page() yield page context.close()context 隔离了 cookie 和缓存每个用例环境干净不会互相污染。session 级别的 browser 复用进程省去反复启动的开销。实测下来几百条用例能省一半时间。6.2 登录态复用大部分用例都需要登录如果每个用例都走一遍登录流程浪费时间还增加失败点。用 storage_state 保存登录态pytest.fixture(scopesession) def logged_in_page(browser): context browser.new_context(storage_stateauth.json) page context.new_page() yield page context.close()auth.json是提前跑一次登录生成的里面存了 cookie 和 localStorage。这样后续用例直接带着登录态跑跳过登录步骤。生成脚本单独写一个CI 里先跑它再跑主用例。6.3 失败自动截图Allure 报告里最有价值的就是失败截图。在 conftest 里加个 hookpytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: page item.funcargs.get(page) if page: screenshot page.screenshot() allure.attach(screenshot, 失败截图, allure.attachment_type.PNG)这样任何用例失败截图自动挂到 Allure 报告里不用手动加。排查问题的时候点开报告就能看到失败瞬间的页面状态效率提升非常明显。7. Allure 报告定制与美化7.1 报告层级与步骤标注Allure 的报告结构靠装饰器控制。allure.epic、allure.feature、allure.story三级分类allure.title定义用例标题allure.step标注操作步骤import allure allure.epic(用户中心) allure.feature(登录模块) allure.title(正确账号密码登录) def test_login_success(page): with allure.step(打开登录页): page.goto(https://example.com/login) with allure.step(输入账号密码): page.fill(#username, admin) page.fill(#password, 123456) with allure.step(点击登录): page.click(#submit) with allure.step(验证登录成功): assert page.url https://example.com/home报告里每个 step 都能展开失败时精确定位到哪一步。给不写代码的人看也一目了然。7.2 附件与参数展示除了截图还可以挂页面源码、请求日志、视频。Playwright 支持录制视频在 context 里开record_video_dir失败用例的视频自动保存。Allure 里用allure.attach.file()挂上去。参数展示用allure.param把测试数据的关键字段显示在报告里方便对比。7.3 报告生成与 CI 集成本地跑完allure serve看报告CI 里一般用allure generate生成静态 HTML然后归档成构建产物。Jenkins 有 Allure 插件直接配置结果目录就行。GitLab CI 的话把 html 目录放到 artifacts 里加个 pages 任务就能在线看。注意Allure 报告里的历史趋势需要保留history目录--clean会清掉。如果想让报告显示通过率趋势图别用 clean或者把 history 单独备份再合并。8. 常见问题排查与避坑实录8.1 高频报错速查表报错信息原因解决方法No module named yaml装错包pip install pyyamlSync API inside asyncio loop同步异步混用统一用 sync_playwright元素定位超时选择器失效或页面未加载检查选择器加 wait_for_selectorAllure 报告空白没装命令行工具安装 allure 并配环境变量中文乱码文件编码问题读写都指定 utf-8用例间数据污染context 未隔离每个用例新建 context8.2 稳定性优化经验UI 自动化最大的敌人是 flaky。我的经验是能用 API 准备的测试数据就别用 UI 操作。比如下单用例商品和库存用接口提前造好UI 只验证下单流程本身。这样既快又稳。另外选择器优先用>
返回列表