首页 / 资讯中心 / 文章详情

pytest钩子实现Playwright失败截图视频自动附加Allure报告

pytest钩子实现Playwright失败截图视频自动附加Allure报告 ★ FEATURED ARTICLE
1. 为什么失败现场要自动收集而不是事后手动补先说句大实话把截图和视频塞进报告这件事听起来简单实际上坑不少。我见过太多团队代码写到一半发现截图是空的、视频没录上、或者报告里附件点了打不开。标题里那句“保姆级教程”我自己也是踩了三轮坑才敢这么写。先说为什么这件事值得做。跑 Playwright 测试的时候用例失败那一瞬间浏览器里到底是什么状态——是弹窗挡住了是元素没加载出来是网络请求超时了——这些信息光靠一行报错日志根本说不清楚。有了截图你能看到页面的视觉现场有了视频你能回放整个操作过程甚至判断是不是某个环节的时序问题。对排查问题来说这俩就是命根子。但问题在于你不可能每次都守在电脑前盯着跑测试。测试是夜里定时跑的等第二天打开 Allure 报告如果里面自带失败那一刻的截图和视频就等于你翻了个懒觉问题现场却已经被完整保存在那里。这份自动化的价值是全职跑测试的人最能体感的。Allure 本身是一个测试报告的展示框架它只管显示你塞给它的数据。它自己不会知道你的浏览器什么时候崩溃、什么时候断言失败。所以中间需要一个“接线工”把 Playwright 的失败事件和 Allure 的附件事先串起来。这个接线工就是 pytest 的钩子函数。注意用 pytest-playwright 这个插件时浏览器对象是 pte 这个 fixture 提供的比较新的版本会把浏览器实例叫browser旧版本习惯用_browser。截图的上下文要拿对别搞混浏览器上下文和页面对象。整套流程里真正干活的核心逻辑可以拆成三段第一段是“侦测失败”第二段是“抓取现场”第三段是“送入报告”。侦测失败靠 pytest 的pytest_runtest_makereport抓取现场靠 Playwright 自己的 API送入报告靠allure.attach或AllureAttachments。三件事各自独立又通过一个字典把上下文传下去。我习惯把整个改造分成三个文件配置清爽一点conftest.py负责注册钩子、管理浏览器生命周期和附加逻辑test_case.py就是普通的测试用例一行都不用改报告生成跑完用allure generate生成 HTML 报告。后面所有代码我都会按 0.3.0 及以上的 pytest-playwright 版本写法来给往下看就明白了。2. 基础机制不搞懂后续全是玄学2.1 三个关键对象Browser、Context 和 Page先统一一下概念。Playwright 里这三个对象是层层包含的关系搞混了截图和视频就不知道该从哪儿取。Browser浏览器进程实例对应你启动的 Chromium、Firefox 或 WebKitBrowserContext一个独立的会话上下文相当于一个“隐身窗口”cookies、缓存、存储等都是隔离的Page具体的一个页面标签页你操作的元素都在这个层面。视频录制是挂在BrowserContext层面的。你打开浏览器后只要给这个上下文指定了record_video_dir后续这个上下文里任何页面产生的新页面、跳转、操作都会按会话去录视频。所以后面取视频的时候你要用context.pages[-1]或page去拿对应的页面再通过page.video拿到视频对象。截图则是挂在Page层面的页面对象直接能截图它知道当前视口长什么样。了解这三层关系后再看后面的钩子函数你会发现其实就是在不同的时机、从不同的层去抓现场。2.2 pytest 钩子的几个关键参数pytest 的钩子函数有自己的一套生命周期。简单理解就是每个用例开始前会触发 setup 阶段然后是调用阶段call最后是 teardown。pytest_runtest_makereport这个钩子会在每个阶段结束的时候生成一份报告对象关键在于这个 report 上有个when字段它告诉你现在刚结束的是哪个阶段还有个outcome字段告诉你这个阶段是 passed、failed 还是 skipped。最核心的一个点也是我最想提醒的最终判定用例是否失败以call阶段的报告为准。明明用例断言自己挂了但 teardown 阶段可能又触发了一个异常那报告就会显示 teardown 错误。所以在钩子里判断失败时要拿when call且failed。我之前见过有人在这段逻辑里乱加判断结果 teardown 阶段的异常反而覆盖了真正的失败信息报告里附的是无关紧要的截图。为此我总结了一个经验如果只是想截取断言失败的现场就只取 call 阶段的失败如果你想覆盖“用例因为环境问题崩了也算失败”的极端情况就把 setup 和 call 的失败都兜上。推荐后一种因为你没法预知测试环境的哪一步会先挂。2.3 为什么用 pytest 钩子而不是装饰器你可能会问直接在用例里写 fixture用yield包一层在断言失败后补一段截图代码不行吗行但有几个致命问题你得在每个用例里手动引入这个 fixture漏一个就漏一个如果用例里 assert 失败抛了异常wrapper 里可能根本走不到截图那一步因为异常流程早就中断了报告挂载的路径写死在每个文件里后续想统一改位置会痛苦到想哭。用pytest_runtest_makereport钩子做全局拦截所有测试文件自动生效不需要在用例里写任何额外代码。这也是“保姆级”的底气来源一旦配置好后续所有新用例自动带着失败截图和视频上报不用每写一条用例都记挂着。3. 实操让失败现场自动进 Allure3.1 环境安装与初始化这部分属于基础但对完全没装过的读者还是要给全步骤。打开终端依次执行pip install pytest-playwright allure-pytest playwright install chromium第一行装的是核心依赖第二行下载 Chromium 内核。如果你平时还要跑 Firefox 或 WebKit就把chromium换成对应的浏览器名字也可以直接playwright install把三个都装全只是时间长一些。接着在项目根目录建一个conftest.py里面要有最基础的结构import pytest from playwright.sync_api import sync_playwright pytest.fixture(scopesession) def browser(): with sync_playwright() as p: browser p.chromium.launch( headlessTrue, args[--ignore-certificate-errors] # 如果测试环境有自签名证书这行很重要 ) yield browser browser.close() pytest.fixture() def context(browser): context browser.new_context( record_video_dirvideos/, viewport{width: 1280, height: 720} ) yield context context.close()对record_video_dir就放在上下文创建时。这个参数表示这个上下文里所有操作都要留视频存放目录是 videos。不传这个参数后面想补救视频就没有数据源了。注意record_video_dir的目录要提前建好或者由 playwright 自动创建但无论怎样最后生成的视频文件名是一长串随机 ID不是按用例名命名的。所以后面做关联时一定要通过page.video.path()去拿实际路径不能自己猜。然后你的测试用例会通过 fixture 拿页面对象def test_login_fails(context): page context.new_page() page.goto(https://example.com/login) page.fill(#username, wrong_user) page.fill(#password, wrong_pass) page.click(#login-btn) assert page.locator(.user-avatar).is_visible()这个用例如果断言挂了第三步的钩子就会兜住它自动抓截图和视频。3.2 核心钩子代码与扩展逻辑现在写最关键的这段代码。我第一次写的时候走了弯路把逻辑写得特别复杂。后来一步步简化最终形成下面这个模板所有关键点都有注释import os import allure import pytest from datetime import datetime pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() # 只在调用阶段结束后处理 if report.when ! call: return # 只处理失败或报错的情况 if report.failed: # 拿到当前用例对应的页面对象 page item.funcargs.get(page, None) if page is None: return # 1. 截图 screenshot_path fscreenshots/{item.name}_{datetime.now().strftime(%Y%m%d_%H%M%S)}.png os.makedirs(os.path.dirname(screenshot_path), exist_okTrue) page.screenshot(pathscreenshot_path, full_pageTrue) # 2. 视频 video_path None if page.video: video_path page.video.path() # 3. 附加到 Allure attach_failed_resources(item, screenshot_path, video_path)是不是觉得截图很简单对Playwright 的截图就是一行调用难点其实在于页面对象拿不到怎么办也就是item.funcargs.get(page, None)返回None截图时页面已经被关掉怎么办就是在 teardown 阶段已经销毁了上下文的情况全页面截图还是视口截图的取舍是什么而我上面给的代码用的是full_pageTrue会把整个页面高度都截下来哪怕有很长很长的滚动条。代价是如果页面数据量特别大截图文件会比较重。我之前遇到一个分页表格页截一张图能有个几 MB打开报告时会有些卡。所以后来改成默认视口截图只有特定接口才用全页这个后面细说。3.3 用 ATTACHMENTS 自动归档上面代码里最后调用了attach_failed_resources这个函数才是真正把资源送进 Allure 的地方。用 pytest 插件方式有一个比allure.attach更自然的入口直接在报告里生成一个分类化的附件列表。def attach_failed_resources(item, screenshot_path, video_path): allure.attach.file( screenshot_path, name失败截图, attachment_typeallure.attachment_type.PNG ) if video_path: allure.attach.file( video_path, name失败视频, attachment_typeallure.attachment_type.WEBM )这段逻辑的核心就是allure.attach.file它会把这个文件内容嵌入到 Allure 报告里。我建议截图固定用 PNG因为质量清晰且压缩比合适视频一般 Playwright 默认产出.webm所以 attachment type 需要用WEBM。如果你用MP4强转部分浏览器打开 Allure 报告时会不支持直接预览。但有个更值得注意的点allure.attach这种方式虽然直观但报告里的附件是“扁平”的都堆在附件区。如果你希望报告中能看到更结构化的展示——比如失败资源单独一个区块每个用例下面都有截图和视频缩略图——就要用ATTACHMENTS 机制。具体说就是搞一个全局字典来记录每个测试的附件文件路径然后在pytest_sessionfinish钩子统一扫描归档。很多团队接触自动化测试报告时以为必须通过 allure 命令行才能生成资源文件其实allure attach这个动作本身会自动把文件复制到 result 目录中。这样其实也行。但如果你希望“失败截图和视频不要全部集中在资源列表而是分别归到截图、视频两个子类”更优雅的方式是用allure.dynamic动态加标签或者直接用上面的allure.attach.file按顺序挂两次。实际使用下来上面这段代码已经满足 95% 的场景不必把实现搞得太炫。3.4 关键只给失败用例挂视频避免全量视频撑爆报告说来也有意思我在最初实现的时候直接把每个用例都录了视频也不管是过还是挂。一天跑下来几百条用例报告目录里视频堆了几个 G。这暴露了什么问题转录所有成功用例的视频完全是浪费需要按结果来筛选。所以上面的处理里我们只针对失败的用例进行视频附加成功用例就让它干净地跑过不做任何额外 IO。这里有个容易忽视的细节Playwright 视频录制是异步落盘的可能用例结束了但视频文件还没完全写盘。当你在pytest_runtest_makereport的时候去page.video.path()拿到的可能是一个还不存在或者还在增长的文件。为了确保视频完整可以先调用page.video.close()让它停止录制并落盘再取路径。if page.video: page.video.close() # 等待视频完全写完 video_path page.video.path()这是个很关键的顺序不写这段有时候视频能挂上但偶尔会有 0 字节文件。加了之后基本就稳了。代价是调用过close()之后这个页面后续的视频就被标记为“已关闭”你不能再录了。但结合我们只在失败时才关所以这个副作用可接受。3.5 报告生成与 HTML 预览代码改完跑一次完整测试pytest -s --alluredirallure-results执行完后查看结果allure generate allure-results -o allure-report --clean allure open allure-report--clean表示每次生成前先把旧报告清掉避免历史残留。然后浏览器打开的就是 Allure HTML点开任意一条失败用例你会看到 Resources 区块里躺着两张图一张失败截图一段失败视频。点开视频还能预览播放。提示如果你是在 CI 上跑不想要本地allure generate这步也可以把allure-results目录作为构建产物上传再在发布平台里用 Allure 插件展示原始结果目录。4. 进阶细节打磨让报告更好用4.1 给报告加失败现场的 HTML 快照截图和视频能说明“长什么样”但排查问题的人往往还关心一点当时的 DOM 结构到底加载到了哪一步。很多前端 bug 是视觉上看起来差不多但关键元素就是没有渲染出来。Allure 支持直接挂 HTML 附件这样打开报告就能看到一个可交互的 DOM 快照。实现起来也不难if report.failed: html_content page.content() allure.attach( html_content, name失败页面 HTML, attachment_typeallure.attachment_type.HTML )HTML 快照要慎用因为它本质上是把整个页面的字符串都塞进报告如果页面是复杂单页应用动辄几百 KB甚至上 MB。在你本地看看没事但如果 CI 报告要传输或存档资源积累多了会拖慢速度。我推荐的做法是截图和视频为主HTML 只有在复现困难时才手动触发一个开关加上别默认全开。4.2 失败重跑机制与截图的取舍测试不稳定的时候失败自动重跑是很常见的做法。这里有个问题如果用例第一次失败但重跑成功了那截图到底该不该挂我的建议是如果配置了 retry就只截最终重跑成功后那次或最终失败那次。否则报告会堆满各种中间过程的截图乍一看以为是 bug其实只是重跑中的不稳定。我自己用过 pytest-rerunfailures 插件配置不复杂但要注意和pytest_runtest_makereport的协作。默认情况下重跑机制会触发多次call阶段每一次失败都会进到钩子。你可以在钩子里判断用例的最终结果if report.failed: reruns item.config.getvalue(reruns) if item.config.getoption(--reruns, default0) else 0 if reruns 0: # 这里只记录最后一次结果 pass实际上更稳的写法是用item.execution_count但不同 pytest 版本这个属性不一定有。所以我建议把重点放在业务本身能不用重跑就先稳定用例别靠重跑续命。4.3 只想针对指定目录或指定标记的用例收集现场有些情况比如你只想针对“高优先级”或用例名含某个关键字的用例记录失败现场其他用例失败就算了不想堆资源。这种需求怎么搞其实钩子函数里item对象身上带了完整的节点信息。你可以通过item.keywords去判断标记if report.failed: if critical not in item.keywords: return或者在conftest.py里给整个子目录单独加一个pytestmark pytest.mark.critical效果是一样。这样只有标记了critical的用例失败时才附加截图和视频其他用例只保留日志。放在报告层面这个设计能显著降低存储成本而且让你的报告聚焦在核心用例上。合理使用时体验极好滥用会降低排查覆盖率所以一般我只建议在用例基数很大、且 CI 磁盘吃紧时启用。4.4 截图类型的选择PNG 还是 JPEGPlaywright 截图默认输出 PNG这是兼顾清晰度和体积的选择。但如果你在跑大页面截图PNG 可能会达到好几 MB。这时候可以切换成 JPEGpage.screenshot(pathscreenshot_path, typejpeg, quality80)JPEG 的优势就是体积小代价是有损压缩如果页面上有需要细看的报错文案放大可能会模糊。我自己项目中平时用 PNG只有遇到那种超长列表页才临时切 JPEG。你也不必太纠结默认 PNG 就够大多数情况用了。另外一个细节full_pageTrue只对截图生效视频不会录到用户没滚动到的区域。所以如果页面其实已经滚到很下面了视频里看不到上面某块的渲染情况这很正常。要全面验证页面还得靠截图和 DOM 快照来互补。5. 常见问题与排查技巧实录5.1 视频死活挂不上多半是目录没配对有朋友照着代码敲完发现 Allure 报告里就是只有截图没有视频。第一反应是视频路径拿错了。排查步骤建议如下先确认context创建时有没有传record_video_dir检查视频保存目录下有没有.webm文件生成在钩子里打印page.video.path()看看路径是否正常检查是否先调用了page.video.close()再取路径。如果前几步都对最后一步最常见的原因是page.video拿到后页面已经销毁了Playwright 此时可能返回 None。这种把视频关联逻辑放到 teardown 之后的情况很容易漏视频。所以我强烈建议把视频处理和截图放在同一个钩子阶段且要在上下文关闭之前执行。5.2 截图倒是截了但是黑色或全空白这个经历我印象很深。有一次冒烟测试环境要登录之后才能访问页面但用例执行时登录跳转慢了半拍页面截图里只有深色背景没有任何元素。排查之后发现full_pageTrue截图时如果页面正在加载中它会截到“页面骨架但内容没渲染”的状态。解法截图前增加一个短暂的等待比如page.wait_for_load_state(networkidle)但这会让失败用例额外多等几秒等待关键元素可见但你不知道哪个元素一定会出现所以通常选择等待body上某个数据属性如果日志里已明确显示页面是跳转失败那截图就算全黑也有参考价值不必强求。我更倾向不额外等待因为失败现场本来就是那一刻的样子多等反而失去了现场的意义。截图黑屏至少说明“加载失败的时候就是黑屏”这对定位问题是有帮助的。5.3 Allure 报告打开后提示没有找到 allure-results很多新手容易犯的错直接在项目根目录执行allure serve allure-results但项目里可能还没生成该目录。如果你执行 pytest 时没有加--alluredir参数它就不会创建这个目录。正确顺序是pytest --alluredirallure-results # 然后执行 allure serve allure-results或者先allure generate再allure open。我自己的习惯是永远用serve它会临时起一个本地服务省得每次手动清理报告目录。5.4 视频文件存在但 Allure 里显示成下载而不是预览这常见于旧版 allure-pytest 或浏览器不认.webm格式。建议确认 allure-pytest 升级到 2.13.2 以上然后用WEBM这个 attachment type不要用通用二进制。再不行就在报告生成后手动改一下附件类型不过这属于非常规操作一般情况下升级版本能解决一大半问题。5.5 失败用例重试后截图挂了三四张这个问题之前提过。这里给出一个标准做法钩子代码只认最终结果。用pytest_runtest_makereport时如果配置了 rerun那么report.outcome在重试过程中的首次失败是failed但最终整体结果其实会变成passed。怎么判断最终取item.execution_count和reruns对比或者直接在 report 对象上判断report.outcome的最终值。不过说实话我更推荐另一种思路不要对不稳定用例用重跑来掩盖问题而是用报告里的重试记录来暴露不稳定。截图重复挂载问题可以通过在附加前用字典清理同一用例的旧附件来解决但代码会变复杂。保持简单跑稳定用例更重要。5.6 Windows 环境下路径分隔符的坑如果你在 Windows 上跑这套东西注意page.video.path()返回的路径是带反斜杠的。Allure 生成报告时如果中途处理路径分隔符不一致可能触发读取失败。我有一次在 Windows 上报错排查了一圈最后问题就出在这个斜杠上。处理办法很简单拿到路径后统一替换成正斜杠video_path page.video.path().replace(\\, /)这段代码值得固化到你的 attach 逻辑里免得到 CI 上换到 Linux 环境又踩一遍。6. 我的最终经验总结这套方案从设计到落地我逐步验证了不同版本组合最后稳定下来的一套组合是pytest 8.x pytest-playwright 0.5.x allure-pytest 2.13.x Playwright 1.4x。虽然标题里写的是 0.3.0但后面新版本的核心 API 并没有大变所以这些思路不会因为版本更新而过时。最后分享两个我觉得特别值得养成的习惯。第一把“是否附加视频”做成可配置的环境变量开关比如读PYTEST_ATTACH_VIDEO。因为本地调试时通常是某种调试模式的浏览器不需要录视频省得每次跑本地测试都把开发机硬盘写满只有 CI 环境才默认打开。第二定期清空 screenshots 和 videos 目录我见过一个项目跑了大半年report 目录几十 GB到时候清理成本极高。定个 cron 或者造了个清理脚本每次生成报告前顺带把老附件清掉体验完全不同。如果你已经用 Selenium 写了不少用例当初为了截图各种折腾 WebDriver 的截图逻辑我劝你尝试一下 Playwright 这套方案从接管浏览器到视频落盘它本身就是原生支持的。老框架的临时方案这里用一行参数就解决了。这段玩法熟练之后还可以继续往前一步把录制的视频转成 gif 动图直接嵌在叮叮通知或邮件里。每次 CI 跑完群里直接能看到失败动图比打开报告还省一步。不过这是后话先把截图和视频稳定附到 Allure 报告里再谈自动化链路的其他花活吧。
阅读完成 · 觉得有帮助?
咨询建站