系列从 0 到 1 搭建桌面应用 UI 自动化测试框架上一篇第 3 篇跑通业务主链路下一篇第 5 篇治理可重复性弹窗、状态与等待关键词证据链、loguru、capture_step、pytest_runtest_makereport、Allure本篇导读UI 自动化的调试成本极高。如果失败时还要人工复现框架就很难规模化。第四步的目标是任何一个失败都能从报告直接看到失败在哪个步骤、当时界面长什么样、日志里发生了什么。证据链由四部分组成日志、主动截图、失败自动截图、Allure 报告。一句话allure-pytest 2.x 必须用 attach(bytes) 而非 attach(file)复制策略和 UUID 不一致会让附件在报告里消失。数据流图一次用例的证据如何汇合失败时用例执行loguru 双 sinkcapture_step 主动截图allure.step 记录步骤pytest_runtest_makereportlogs/YYYYMMDD/pytest_HHMMSS.logscreenshots/*.png Allure 附件allure-results失败定位allure generatereports/YYYYMMDD/allure_HHMMSS1. 日志双 sink# conftest.pylogger.remove()logger.add(sys.stdout,levelINFO,format...)logger.add(config.ROOT/logs/日期/fpytest_{时间}.log,levelDEBUG,encodingutf-8,)两个 sink 分工stdout sinkpytest 捕获Allure 报告可见文件 sink提权后 stdout 不可靠文件最稳。日志按日期分目录logs/YYYYMMDD/。2. 主动截图capture_stepBasePage.capture_step()做三件事记录当前步骤名到current_step截图并加上底部标注以 bytes 形式挂到 Allure。defcapture_step(self,step:str,prefix:str)-str:self.current_stepstep# 省略构造截图路径、调用 capture_annotated(self.window, step, path) 带底部步骤标注、读回 bytes...allure.attach(png_bytes,namef截图-{step},extensionpng)关键点用allure.attach(bytes)而不是allure.attach(file...)避免 allure-pytest 复制文件导致result.json里的 source UUID 与实际附件文件名不一致。3. 失败自动截图钩子pytest.hookimpl(hookwrapperTrue)defpytest_runtest_makereport(item,call):outcomeyieldreportoutcome.get_result()ifreport.whencallandreport.failed:# 从 Page Object 取 current_step# 优先截“失败时正在操作的窗口”# 省略遍历 funcargs 找带 current_step 属性且是 str 的对象、调用 capture_annotated 截图、bytes attach...两个容易忽略的细节必须检查isinstance(current_step, str)pywinauto 的WindowSpecification有__getattr__hasattr永远为 True截 Page Object 的窗口不要截顶层窗口失败时顶层可能是加载框/弹窗截出来只有一个小图。4. Allure 报告生成run.py的三段式pytest --alluredir./allure-results allure generate ./allure-results -o reports/YYYYMMDD/allure_HHMMSS --clean webbrowser.open(index.html)报告按日期分目录不覆盖历史。run.py还会替换 favicon、侧栏 logo 和品牌文字为 DESKTOPAPP。5. 证据链的四个检查点检查点问题合格标准日志失败时能否看到 DEBUG 细节有独立文件按日期归档步骤能否知道失败在哪个业务动作每步有allure.step截图能否看到失败瞬间的界面主动截图 失败自动截图报告能否离线打开、历史可追溯allure generate 日期目录6. 真实踩坑现象根因修正Allure 报告 500 failed to fetch浏览器禁止file://fetch用allure generate生成静态目录失败截图只有底部标注条截了app.top_window()当时顶层是加载框截 Page Object 的window报告里附件找不到attach 用绝对路径文件复制策略不一致改用 bytes attach提权后看不到输出stdout 在独立控制台Tee 到日志文件7. 验收清单每个关键步骤都有截图任意用例失败后报告里有“步骤名 截图 日志”日志按日期归档不会覆盖报告能离线打开不依赖 HTTP 服务截图能看出失败时正在操作的是哪个窗口。8. 可复用要点证据链和用例同时设计不要等失败后再补日志。截图要截“操作现场”不要截“当前最顶层窗口”。报告产物按日期分目录历史结果可追溯。附件用 bytes不用路径避免复制策略差异。关键代码示例示例 1loguru 双 sink真实conftest.py节选8 行配置同时满足 pytest 捕获和文件落盘logger.remove()# sink 1stdout → pytest 捕获 → Allure 报告可见logger.add(sinksys.stdout,levelINFO,formatg{time:HH:mm:ss}/g lvl{level:7}/lvl {message},)# sink 2按日期分目录的 DEBUG 文件 → 提权后 run.py 的 _Tee 不继承到子进程# loguru 自己写文件最稳用于排查 dismiss 弹窗、启动流程等细节异常_log_dirconfig.ROOT/logs/datetime.now().strftime(%Y%m%d)_log_dir.mkdir(parentsTrue,exist_okTrue)logger.add(sink_log_dir/fpytest_{datetime.now():%H%M%S}.log,levelDEBUG,format{time:HH:mm:ss} | {level:7} | {message},encodingutf-8,)示例 2主动截图标注 bytes attach真实pages/base_page.py的capture_step节选defcapture_step(self,step:str,prefix:str)-str:fromutils.screenshotimportcapture_annotated self.current_stepstep tstime.strftime(%H%M%S)fnamef{prefix}_{step}_{ts}.pngifprefixelsef{step}_{ts}.pngpathstr(config.SCREENSHOTS/fname)# 带底部步骤标注的截图capture_annotated(self.window,step,path)# 关键allure.attach(bytes) 而不是 attach(file...)# 原因allure-pytest 2.x allure2 generate 下attach 文件时会把文件复制到# data/attachments/ 并在 result.json 里记录 source UUID绝对路径 file 的复制策略# 与 UUID 生成不一致最终报告里附件找不到必须 inline bytes 才能保证 source 一致png_bytesPath(path).read_bytes()safe.join(cforcinstepifcnotin\\/:*?|)[:60]allure.attach(png_bytes,namef截图-{safe},extensionpng,attachment_typeallure.attachment_type.PNG,)returnpath示例 3失败自动截图钩子真实conftest.py的pytest_runtest_makereport节选两个容易忽略的细节pytest.hookimpl(hookwrapperTrue)defpytest_runtest_makereport(item,call):outcomeyieldreportoutcome.get_result()ifreport.whencallandreport.failed:sessionitem.funcargs.get(app)fromutils.screenshotimportcapture_annotated# 细节 1必须 isinstance(current_step, str)# pywinauto 的 WindowSpecification 有 __getattr__hasattr 永远 Truestep未知步骤shot_windowNoneforvinitem.funcargs.values():ifhasattr(v,current_step)\andisinstance(getattr(v,current_step,None),str):stepv.current_step# 细节 2优先截 Page Object 的 window不是 app.top_window()# 失败时顶层可能是加载框/弹窗top_window 会截到极小图try:shot_windowv.windowexceptException:shot_windowNonebreakifshot_windowisNone:shot_windowsession.main_window# fallbacksafe_step.join(cforcinstepifcnotin\\/:*?|)[:30]shotconfig.SCREENSHOTS/f{item.name}__{safe_step}_FAILED.pngcapture_annotated(shot_window,step,str(shot))# 同样用 bytes attach避免文件复制策略不一致allure.attach(shot.read_bytes(),namef失败截图-{safe_step},extensionpng,attachment_typeallure.attachment_type.PNG)示例 4提权后日志可见真实run.py的_Tee类3 行起作用class_Tee:stdout 替身同时写控制台和日志文件每次 write 立即 flush。 提权后 run.py 跑在独立控制台父进程拿不到 stdout 外部轮询日志即可实时看进度。def__init__(self,console,fp):self.consoleconsole;self.fpfpdefwrite(self,s):self.console.write(s);self.console.flush()self.fp.write(s);self.fp.flush()defflush(self):self.console.flush();self.fp.flush()关键结论证据链和用例同时设计不要等失败后再补日志。loguru 双 sink loguru 自己写文件8 行配置同时解决 pytest 捕获和提权后 I/O 边界两个问题。截图要截「操作现场」不是「最顶层窗口」失败时顶层可能是加载框/弹窗会截到极小图必须截 Page Object 的window即业务操作正在进行的那个窗口。allure.attach 用 bytes不要用 file 路径allure-pytest 2.x allure2 generate 的文件复制策略与 UUID 生成不一致inline bytes 才能保证附件稳定可追溯。提权会改变 I/O 边界run.py 的_Tee虽然只做了两件事write 同时发给 console fp、每次 flush但它让父进程Trae / IDE能实时看到提权后独立控制台里的进度。下一篇证据链建立后开始治理“第一次能过、第二次就挂”的问题第 5 篇弹窗、状态与等待。
企业数字化 ERP 产品动态
相关推荐
工控上位机框架选型:自研造轮子还是开源拼积木?十年现场经验讲透取舍 做了快十年工控上位机,大大小小的软件框架搭过不下十套。早期年轻气盛,总觉得开源组件这也不顺手那也不合用,非要从零撸一套自研框架,觉得全链路可控才叫专业;后来踩了无数工期延期、维护崩盘的坑才慢慢明白࿱… · 2026/11/6 5:19:57
【freertos】 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 5:19:57
Java 基础综合练习:经典编程题详解 Java 基础语法学完后,很多人会陷入“看得懂、写不出”的困境:知识点都认识,真正拿到需求却不知从何下手。本文整理了九道经典综合练习题,涵盖条件判断、循环、数组、方法封装和随机数应用,每题都给出需求与完整可运行代… · 2026/11/6 5:19:57
Qt飞机大战源码拆解:从工程结构到二次开发实战 简介:这份资源是面向Qt初学者与游戏开发入门者的「飞机大战」完整项目源码包,围绕Qt框架的图形渲染、事件驱动与资源管理等核心能力展开,可用于课程设计、练手项目或Qt技术学习参考。压缩包共223个文件,约6.21MB,以105… · 2026/11/6 12:57:20
JSP农贸交易系统开发与优化实践 1. 项目概述 这个JSP农贸产品交易系统是一个典型的B2B电子商务平台,专为农产品流通领域设计。我在2018年曾参与过类似项目的全流程开发,深知这类系统对农产品供需匹配的关键作用。系统采用经典的JSPServlet技术栈,配合MySQL数据库,… · 2026/11/6 12:57:20
企业数字化转型中的设备管理系统实践与价值 1. 设备管理在企业数字化转型中的核心价值 现代企业的设备资产管理正面临前所未有的挑战。随着数字化转型浪潮席卷各行各业,传统的设备管理方式已经无法满足企业高效运营的需求。以制造业为例,一家中型工厂通常拥有上千台设备资产,从生产设备… · 2026/11/6 12:55:19
电商抽奖系统订单流程设计与优化实践 1. 抽奖活动订单流程设计概述抽奖活动作为电商平台常见的营销手段,其订单流程设计直接关系到用户体验和系统稳定性。一个完善的抽奖订单系统需要解决三个核心问题:库存管理、参与次数控制和数据一致性。传统设计往往将这些功能耦合在单一表中,… · 2026/11/6 12:54:15
HBase与Neo4j集成架构:大规模图数据处理实战 1. HBase与Neo4j集成方案概述 在大规模图数据处理领域,HBase和Neo4j的结合堪称"天作之合"。我曾在某社交网络分析项目中采用这种架构,成功处理了超过10亿节点和50亿边的图数据。HBase负责存储原始数据和高吞吐量的读写操作,而Neo4j… · 2026/11/6 12:51:10
金蝶云星空V7.3生产模块:基础数据与系统参数的协同控制逻辑 简介:本资源是金蝶云星空V7.3制造领域生产管理模块的官方培训课件,面向制造业企业ERP实施顾问、生产计划员、车间管理人员及金蝶系统运维人员,聚焦解决生产订单全周期管控、物料齐套分析、领退补料协同、在制过程追溯等核心业务痛点。课件为单… · 2026/11/6 12:51:10
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23