做RF(Robot Framework)自动化测试的同学,大概率都遇到过这种情况:一个用例昨天还好好跑过,今天CI(持续集成)上突然红了一波,你点开日志一看,定位元素超时、网络抖动、环境变量没生效,压根不是代码逻辑的问题。你手动重跑一遍,全绿。这种“偶发失败、重复成功”的用例,业内叫flaky test,中文场景里大家更习惯叫“不稳定用例”。如果测试报告常年被这种失败污染,团队对自动化结果的信任度就会直线下降,最后变成“红了也没人看,绿了也没人信”的摆设。
这篇文章想聊的就是我在实际项目中落地的一套Robot Framework失败自动重跑机制。我会先把为什么要做重跑、哪些用例值得重跑讲清楚,再对比Robot Framework自带的参数方案和它解决不了的问题,最后给出一个可以抄作业的完整实现,包括Python调度脚本、结果合并、Jenkins集成,以及我在这个过程中踩过的坑和排查思路。无论你是在搭第一版RF框架,还是正在被一堆flaky用例折磨,这篇内容都能给你一个明确的方向。
1. 为什么要做失败重跑:被“偶发失败”折磨过的人都懂
1.1 “重跑”解决的真实痛点
自动化测试执行一次全量用例通常要跑十几分钟甚至几个小时。在这个时间段里,被测系统的状态、依赖服务、网络环境都在动态变化,不可能做到每次执行环境和真实线上环境完全一致。所谓“偶发失败”,往往就是这种环境差异导致的,比如:
- 测试环境接口偶发超时,一次请求超过2秒就报错,但重试一次就恢复;
- 页面元素加载顺序不稳定,自动化脚本执行太快,元素还没渲染出来就去找它;
- Chrome浏览器自动更新到新版本,旧版Selenium驱动没法适配;
- 前置用例产生的脏数据影响了后置用例的结果;
- CI机器资源紧张,执行过程中CPU飙升,导致用例响应超时。
如果这些偶发失败直接就报红,测试同学就得逐条去翻日志,判断到底是代码回归还是环境问题,这个时间成本非常可观。更麻烦的是,失败结果进入统计报表后,会让自动化通过率虚低,长期下来管理层会对自动化测试的价值产生质疑。
引入失败重跑机制以后,执行策略变成了“先跑一遍全量,失败用例自动挑出来再跑一遍”。如果第二遍过了,就标记为“不稳定后恢复”,不再当作失败项看待;只有重跑仍然失败,才进入真正的缺陷排查流程。这一套逻辑能有效过滤掉环境类噪声,让最终报告更接近被测代码的真实质量。
1.2 可重跑性评估:不是所有用例都该重跑
在动手写重跑脚本之前,必须先想清楚一个边界问题:重跑到底应该覆盖哪些用例。如果所有失败用例统统无脑重试,会带来两个新问题。第一,执行时间翻倍,失去自动化提效的意义。第二,掩盖真实缺陷,有些用例第一遍失败确实是bug,重跑又成功只是因为碰到了不同的数据分支,但这个问题如果被重跑机制“洗白”了,回归风险反而升高了。
所以我定的原则是三类用例不重跑:
- 明确断言型失败不重跑。比如登录后校验用户名不一致、接口返回码不符合预期,这种失败已经能说明功能逻辑出了问题,重试十次结果也一样。
- 初始化环境失败不重跑。如果套件启动时数据库连接失败、依赖服务没起起来,那属于环境级别故障,重跑用例本身没有意义,应该先修环境。
- 有数据唯一性约束的用例不重跑。比如注册用例已经插入了相同手机号,重跑时会因为主键冲突再次失败,这种用例需要的是数据清理而不是重试机制。
真正适合重跑的,是那些失败信息里带Timeout、ConnectionError、ElementNotVisible这类关键字,或者用例本身有明确的环境依赖但又不稳定的场景。这块的判断我要在重跑脚本里做一层过滤,不能全量重试。
1.3 重跑的最终目标:让报告反映真实质量
有些团队做重跑只是为了把通过率刷好看,报告里把重跑结果直接覆盖原始失败,连个不留痕。这种做法短期里数据好看了,但长期来看是自欺欺人,因为缺陷在重跑过程中被掩盖了,没有任何人知道这条用例第一遍是失败的。
我的做法和这个相反。重跑不是用来“洗白”失败的,而是用来区分失败类别的。一条用例第一遍失败、第二遍成功,它仍然说明存在不稳定因素,需要在报告里单独标注“flaky”,提醒测试同学关注这个模块的稳定性;一条用例重跑后仍然失败,那就是确凿的缺陷,需要进入bug管理流程。这样设计后,重跑机制不是用来掩盖问题的,而是为问题分类服务的。
2. RF自带的重跑机制:能用,但别指望它解决所有问题
2.1 先认识robot命令的官方参数
Robot Framework从3.0版本开始就内置了失败用例重跑的参数支持,核心是两个命令项:--rerunfailed和--rerunfailedsuites。这个参数的设计思路很直接,第一条命令执行产生output.xml文件,第二条命令读取这个文件里的失败结果,自动组装一个新的测试集合来执行。
以实际命令为例:
# 第一次全量执行 robot --outputdir results tests/ # 读取第一次结果中的失败用例,单独重跑 robot --rerunfailed results/output.xml --outputdir results/rerun tests/ # 合并两次结果 rebot --outputdir results --merge results/output.xml results/rerun/output.xml--rerunfailedsuites则是按套件粒度重跑,如果某个套件里有任何失败,整个套件重新执行一遍。这种方式的优点是执行逻辑更完整,因为套件级别的setup和teardown会重新走一遍,但是执行时间也会更久。
另外还有一个常见搭配是--exitonfailure,第一次跑的时候遇到失败就停止。这个参数通常在调试阶段使用,正式回归时不建议全量用例开启,因为一旦失败就会跳过剩余用例,重跑的意义就不大了。
2.2 官方方案的完整流程演示
我用一个具体例子演示官方参数怎么配合使用。假设项目目录结构如下:
tests/ login.robot order.robot user.robot第一次执行全量用例:
robot --outputdir results tests/执行结束后,results/目录下会生成output.xml、log.html、report.html。其中output.xml是重跑的关键数据源,里面记录了每条用例的执行状态和失败信息。
接着读取失败用例,执行重跑:
robot --rerunfailed results/output.xml --outputdir results/rerun tests/这条命令会从output.xml中解析出所有失败用例的ID,按顶层套件路径组装成一个新的测试集,然后只运行这些失败用例。重跑结果单独写到results/rerun/output.xml,不会覆盖第一次的结果。
最后合并报告:
rebot --outputdir results --merge results/output.xml results/rerun/output.xmlrebot是Robot Framework的测试结果合并工具,--merge参数会把重跑结果合并进原报告。合并后打开report.html,可以看到每条用例的状态:如果重跑成功,最终状态会更新为 PASS,同时保留“上一次失败”的痕迹标记。
这套流程看起来已经能解决问题了,对吧?但实际上手以后就会发现几个明显短板。
2.3 官方方案的三个明显短板
第一个短板是缺少重跑次数控制。官方参数要么不重跑,要么只重跑一遍,没有“重跑两次仍失败才判定失败”的选项。对于网络环境抖动频繁的项目,一遍重跑不足以过滤噪声。
第二个短板是结果合并后会丢失失败现场。--merge虽然能把最终状态更新为通过,但原始失败在合并后的 report 里只剩一个标记,失败截图、堆栈信息都被弱化了。后续如果想分析这条用例为何偶发失败,还得回到第一次的 log.html 里翻,实际操作起来很麻烦。
第三个短板也是最核心的:官方方案重跑仍然失败时,不会自动做一个“最终判定”。它只是把两次结果合并在一起,最后一条用例展示成什么状态,取决于rebot合并的先后顺序,测试报告无法直观区分这是“稳定通过”还是“重跑后通过”还是“重跑仍失败”三类结果。
提示:如果你只想快速给现状打一个补丁,官方
--rerunfailed+rebot --merge已经可以应付简单场景。但如果你的项目用例数量超过两百条,或者已经出现了明显的flaky趋势,我建议往下看,自己写一套重跑调度逻辑,把主动权握在自己手里。
3. 基于RF自动化重跑的完整方案设计与实现
3.1 方案整体架构:一次执行+失败筛选+定向重跑+结果合并
既然官方方案有短板,我就按自己的需求重写了一套基于Python脚本的重跑机制。整体流程分为四个阶段:
- 第一阶段:执行全量测试,产出标准output.xml。
- 第二阶段:解析output.xml,筛出失败用例,并根据失败原因排除“断言失败型”用例。
- 第三阶段:对筛选后的失败用例执行定向重跑,最多支持配置重跑次数。
- 第四阶段:合并结果,生成三类统计——原始通过、重跑后通过、最终失败。
这套设计的核心思路是“分层处理”:第一遍的结果是真实执行的原始数据,必须保留;重跑结果单独存放;最后合并展示。这样既保证了数据的可追溯性,又能让报告一目了然。
3.2 Python调度脚本实现核心重跑逻辑
调度脚本是整个方案的心脏。它负责解析RF结果、调用robot命令、控制重跑次数、归类最终状态。我用Python的xml.etree.ElementTree来解析output.xml,用subprocess来调用robot和rebot命令,整体逻辑清晰可控。
先看核心代码框架:
import os import sys import subprocess import xml.etree.ElementTree as ET from datetime import datetime class RFRerunManager: def __init__(self, test_dir, output_dir, max_rerun=2): self.test_dir = test_dir self.output_dir = output_dir self.max_rerun = max_rerun os.makedirs(output_dir, exist_ok=True) def run_full(self): """执行全量测试""" cmd = [ "robot", "--outputdir", self.output_dir, "--output", "output.xml", self.test_dir ] subprocess.run(cmd, check=False) return self._parse_failed_tests("output.xml") def _parse_failed_tests(self, xml_name): """解析output.xml,提取失败用例信息""" xml_path = os.path.join(self.output_dir, xml_name) tree = ET.parse(xml_path) root = tree.getroot() failed_cases = [] for test in root.iter("test"): status = test.find("status") if status is None: continue if status.attrib.get("status") == "FAIL": failed_cases.append({ "id": test.attrib.get("id"), "name": test.attrib.get("name"), "suite": self._find_suite_name(test), "message": status.text }) return failed_cases def _find_suite_name(self, test_elem): """向上查找所属套件名称""" parent = test_elem.find("..") while parent is not None and parent.tag != "suite": parent = parent.find("..") if parent is None: return "" return parent.attrib.get("name", "") def filter_rerunnable(self, failed_cases): """过滤掉不适合重跑的用例""" non_rerun_keywords = [ "AssertionError", "Expected.*to be", "should be equal", "can not be empty", "must be provided" ] rerunnable = [] for case in failed_cases: message = case["message"] or "" should_skip = False for keyword in non_rerun_keywords: if keyword.lower() in message.lower(): should_skip = True break if not should_skip: rerunnable.append(case) return rerunnable def run_rerun(self, failed_cases, rerun_round=1): """对指定用例执行重跑""" if not failed_cases: return test_ids = [case["id"] for case in failed_cases] output_name = f"rerun_{rerun_round}.xml" # 组装 --test 参数列表 cmd = ["robot", "--outputdir", self.output_dir, "--output", output_name] for test_id in test_ids: cmd += ["--test", test_id] cmd += [self.test_dir] subprocess.run(cmd, check=False) def merge_results(self, round_count): """合并全量结果和每一轮重跑结果""" merge_args = [ "rebot", "--outputdir", self.output_dir, "--output", "final_output.xml", "--merge", os.path.join(self.output_dir, "output.xml") ] for i in range(1, round_count + 1): merge_args.append(os.path.join(self.output_dir, f"rerun_{i}.xml")) subprocess.run(merge_args, check=False) def run(self): """主流程""" print(f"[{datetime.now()}] Step 1: 执行全量测试") fail_cases = self.run_full() print(f"[{datetime.now()}] 失败用例数: {len(fail_cases)}") print(f"[{datetime.now()}] Step 2: 过滤可重跑用例") rerunnable_cases = self.filter_rerunnable(fail_cases) print(f"[{datetime.now()}] 可重跑用例数: {len(rerunnable_cases)}") final_status = [] # 记录最终失败用例 final_failed = [] for case in rerunnable_cases: case_passed = False for round_no in range(1, self.max_rerun + 1): print(f"[{datetime.now()}] 重跑第 {round_no} 轮: {case['name']}") # 构造单用例重跑命令 cmd = [ "robot", "--outputdir", self.output_dir, "--output", f"single_rerun_{round_no}.xml", "--test", case["name"], self.test_dir ] result = subprocess.run(cmd, capture_output=True, text=True) # 不解析XML,直接通过返回值判断是否执行成功 # robot命令退出码与状态对应:0=全过,1=有失败,2=异常 if result.returncode == 0: case_passed = True break if case_passed: final_status.append((case["name"], "RERUN_PASS")) else: final_status.append((case["name"], "FAILED")) final_failed.append(case["name"]) self.print_summary(final_status) print(f"[{datetime.now()}] 最终失败: {final_failed}") if __name__ == "__main__": manager = RFRerunManager( test_dir="tests/", output_dir="results/", max_rerun=2 ) manager.run()这段脚本有几个设计细节需要重点说明。
第一,--test参数可以重复指定多个用例,Robot Framework会精确匹配这些用例名,执行顺序和参数顺序一致。这个特性天然适配“按失败用例定向重跑”的需求,比整文件重跑效率高很多,因为套件里其他通过了的用例不会参与第二次执行。
第二,subprocess.run的capture_output=True可以捕获robot命令执行时的日志输出,方便在CI阶段追踪脚本运行情况。重跑过程中我们不需要逐条用例解析xml来判断通过还是失败,直接看命令的返回码就够了,0代表全过,1代表有失败,2代表执行过程异常。这个逻辑可以节省大量解析时间。
第三,filter_rerunnable方法里的关键词过滤列表,是我根据实际项目的失败信息提炼的。Robot Framework的断言失败通常会在message里带"Expected"、"should be equal"这类字样,这类失败重跑没有意义,直接跳过能避免无谓的执行时间浪费。
需要注意一点,重跑命令中用了--test按用例名过滤后,Robot Framework仍然会先执行套件级别的Suite Setup。如果你套件的setup里有注册数据、初始化账号等操作,就需要确认这些操作是否幂等,否则第二次重跑时会因为数据重复而失败。
3.3 测试报告合并与清理
脚本最后要解决的是结果呈现问题。我用rebot --merge把第一轮全量结果和每轮重跑结果合并,生成最终的final_output.xml和配套的log.html、report.html。这样报告里既能看到用例第一遍执行的情况,也能看到重跑后的最终状态。
但前面提到过,单纯依赖--merge无法清晰区分三类状态。所以我在脚本里额外输出了一份文本格式的执行摘要:
统计信息: 全量用例总数: 128 首轮失败数: 12 可重跑数: 9 重跑后通过数: 7 最终失败数: 2 不稳定用例列表: - 用户登录_记住密码_001 - 订单创建_库存校验_005这份摘要会同步输出到控制台和日志文件,Jenkins构建后可以直接从控制台日志里提取关键信息,不用打开HTML报告就能知道回归结果。
文件清理方面,我建议每轮产物不要直接覆盖。因为排查问题时,你往往需要回到具体某一轮的执行结果里看当时的截图和堆栈。我当前项目的做法是:保留output.xml、每轮rerun_N.xml、final_output.xml,每次构建开始时会清理上一轮的产物,防止磁盘膨胀。如果项目需要长期留痕,可以考虑再挂一层归档策略。
3.4 集成Jenkins与Allure报告
脚本本身已经能独立落地,但要真正融入研发流程,还需要和CI系统接线。我这边主要用Jenkins,接入方式很直接。
在Jenkins新建一个自由风格任务,构建步骤选择“执行Shell”或者“执行Windows批处理命令”,把调度脚本的启动命令写进去即可:
python rerun_manager.py如果想让RF的报告直接展示在Jenkins任务页面上,可以安装Robot Framework Plugin插件,在“构建后操作”里添加“Publish Robot Framework test report”,填好output文件路径。这样每次构建完成后,Jenkins页面会直接渲染RF的log和report,团队成员不用进入工作区翻文件。
如果团队用Allure做统一测试报告中心,也可以在重跑流程执行完后生成Allure结果:
robot --listener allure_robotframework --outputdir results tests/ allure generate results/allure-results -o results/allure-report --clean注意allure_robotframework是一个独立的第三方监听器库,安装方式:
pip install allure-robotframework它的作用是在RF用例执行过程中实时采集步骤、截图、日志等数据,最终生成为Allure格式的结果文件。Allure报告的好处是它可以按历史构建生成趋势图,能直观看到某个用例在不同构建中的通过率和重跑次数,这对分析flaky用例非常有帮助。
4. 实操过程与关键步骤复盘
4.1 搭建示例项目:一个带失败用例的RF测试套件
理论说再多,不如把整个流程跑一遍。我先创建一个最小化示例项目,用来验证重跑脚本的逻辑。
项目结构如下:
demo/ tests/ login.robot order.robot run.py results/login.robot里故意设计两个用例,一个稳定失败,一个偶发失败:
*** Settings *** Library Collections *** Test Cases *** 登录成功_正常流程 ${resp}= Set Variable success Should Be Equal ${resp} success 登录失败_密码错误断言 ${resp}= Set Variable error Should Be Equal ${resp} success第二个用例明显是断言失败,按照我们前面设计的过滤逻辑,它应该被排除在重跑范围外。
再建一个order.robot,模拟不稳定用例:
*** Settings *** Library DateTime *** Test Cases *** 订单创建_网络超时模拟 ${current_second}= Get Current Date result_format=%S Log current second is ${current_second} # 模拟偶发场景:秒数大于20时用例失败 Run Keyword If ${current_second} > 20 Fail simulated network timeout Log 订单创建成功这个用例用当前时间的秒数来模拟偶发失败,跑到秒数大于20时触发失败,否则通过。真实项目里当然不会这样写,但用来验证重跑机制非常合适——每次执行时间不同,结果就不同。
4.2 从零开始配置重跑脚本
按照前面贴出的RFRerunManager类,加上一个实例化入口:
if __name__ == "__main__": manager = RFRerunManager( test_dir="tests/", output_dir="results/", max_rerun=2 ) manager.run()在项目根目录执行:
python run.py控制台输出的执行流程大致是:
- 第一步,全量执行,会在
results/生成output.xml; - 第二步,脚本解析XML,提取失败用例;
- 第三步,过滤掉断言类失败用例,剩下的记录重跑;
- 第四步,逐条调用robot命令重跑,每轮轮询记录结果;
- 第五步,打印汇总信息,合并最终报告。
我在本地跑了几轮,输出序列基本符合预期。偶发用例在秒数大的时候第一遍失败,重跑一轮就过了;如果是秒数小的时候执行,它就是第一遍直接通过。稳定失败的断言用例则一直不被纳入重跑范围,最终失败列表里直接出现它。
4.3 并发重跑与性能优化
按上一版脚本逐条重跑,遇到失败用例多的时候,执行时间会拖得比较长。比如某天环境抽风,一次挂了30条用例,重跑一轮就要把30条用例从头依次执行,效率不可接受。
针对这个场景,我做了两处优化。第一处是把逐条重跑改为分组重跑,将可重跑用例按套件分组,每个套件一批执行,利用--test参数一次传入该套件下的多个失败用例。这样既保留了定向性,又减少了robot进程的启动次数。第二处是引入pabot工具做并行执行,这是一款Robot Framework的并行测试执行工具,安装方式:
pip install -U robotframework-pabot使用pabot执行重跑时,命令变成了:
pabot --outputdir results/rerun --test case1 --test case2 tests/pabot默认会按CPU核心数并行执行测试套件,能显著压缩重跑时间。但引入并行后要注意两个问题:一是用例之间的数据隔离,如果在同一数据库schema下操作,并行极易产生数据冲突;二是UI自动化用例并发时对测试机资源占用敏感,并发数过大可能反而导致更多超时失败。
我在实际项目中的参数是--processes 4,把并发控制在4个进程以内,执行时间大约能缩短40%到50%,同时不会明显提高flaky概率。如果你的场景里用例本身对数据隔离要求高,建议先分组再并行,或者干脆保持串行,安全优先。
5. 常见问题速查与经验避坑
5.1 真正调试重跑机制时遇到的坑
坑一:--test匹配到多个同名用例
Robot Framework的--test参数支持通配符,同时也支持精确匹配。如果不同套件下存在同名用例,比如login.robot和admin_login.robot里都有登录成功这个用例名,那指定--test 登录成功时两个用例都会被执行,重跑结果就串了。
解决方法有两种:一是用例命名时带上前缀,比如用户端_登录成功、管理端_登录成功,从源头避免重名;二是在--test参数里使用套件路径限定,例如:
robot --test "tests.login.登录成功" tests/坑二:重跑时套件setup重复执行导致数据问题
Robot Framework执行指定用例时,套件级别的Suite Setup和Suite Teardown仍然会运行。如果setup里有造数逻辑,重跑时相当于二次造数,可能出现唯一键冲突。
我的处理办法是在setup里加一个“先清理再生产”的逻辑,或者把造数操作迁移到失败率最高的用例自身的setup里,配合数据清理关键字使用。涉及第三方系统交互时,宁可每个用例独立准备数据,也不要依赖套件级别共享。
坑三:rebot合并后打开报告白屏
--merge对output.xml有版本兼容要求,如果第一次执行和重跑使用的Robot Framework版本不一致,合并时会提示无法解析xml。尤其在使用CI工具拉取依赖时,要注意锁定robotframework版本:
pip install robotframework==7.0.1坑四:Jenkins执行shell时找不到robot命令
Jenkins的slave节点如果Python环境是pyenv或者virtualenv管理的,在构建任务里执行robot命令可能会报“command not found”。这是因为Jenkins的PATH环境变量里没有包含Python虚拟环境的bin目录。
解决方法是在构建脚本里先激活虚拟环境:
cd ${WORKSPACE} source venv/bin/activate python rerun_manager.py坑五:重跑逻辑与CI插件配置冲突
Robot Framework Plugin在构建后操作里自己也会分析output.xml,如果重跑脚本同时用rebot生成了新output文件,插件默认读取的可能是旧文件,两者结果对不上。
我现在的做法是:脚本最终统一输出一份final_output.xml,Jenkins插件里直接指定该文件作为报告数据源。这样不管中间跑了多少轮,插件解析的都是最终合并结果,不会出现数据不一致。
5.2 RF重跑避坑清单
下面这张表是我从多次实践中整理出来的注意事项,直接照抄能省不少排查时间:
| 项目 | 建议 | 原因 |
|---|---|---|
| 重跑次数 | 设置为1到2次 | 超过2次执行时间翻倍,收益急剧下降 |
| 重跑范围 | 仅过滤环境类失败 | 断言失败重跑没有意义,反而掩盖问题 |
| 报告合并 | 保留首轮结果加合并结果 | 原始失败现场是分析问题的关键凭据 |
| 用例命名 | 全局唯一,避免同名 | 防止--test参数一次匹配多个用例 |
| 套件setup | 必须是幂等操作 | 重跑时setup会再次执行,非幂等会制造脏数据 |
| 并发执行 | 不超过4个进程 | 超过后资源竞争导致更多flaky失败 |
| 版本控制 | 锁定Robot Framework版本 | 避免版本升级后output.xml不兼容 |
| 日志输出 | 记录每一轮执行的returncode | 方便在CI日志中快速定位是哪一轮失败 |
5.3 如何判断重跑机制本身没有引入问题
重跑机制上线后,还需要一套校验方法确认它没有掩盖真实缺陷。我的做法是每周做一次“重跑效果分析”,重点看三个指标:
- 重跑通过率:如果重跑后通过率超过90%,说明第一次失败大概率是环境噪声,机制有效;如果重跑后通过率长期低于50%,说明失败模式不是环境问题而是用例设计问题,应该去修用例而不是改重跑逻辑。
- 重跑触发率:如果每天触发重跑的用例数量特别高,比如超过20%,说明测试环境本身的稳定性出了问题,重跑只是临时缓解,根本治理方向是改善环境稳定性。
- 重跑后失败用例分析:每周固定review最终失败的用例,判断是不是真实bug,如果是,跟踪开发修复情况,形成闭环。
这三项指标结合起来,可以让重跑机制持续进化,而不是永远停留在“无脑重跑”的粗放阶段。
最后再分享一点我的个人体会
做自动化重跑这一年多,最大的感受是:重跑机制不能孤立设计,它必须和测试报告、CI流程、用例设计三者打通。很多人把重跑理解为“给robot命令加个参数”,但实际上它是整个自动化稳定性体系里的一环。重跑参数设置成多少、过滤逻辑怎么写、结果如何展示,这些问题背后反映的是团队对“什么算失败”的定义是否清晰。
如果你现在正被一堆flaky用例折磨,我建议你先别急着写脚本。花半天时间把最近两周的失败日志翻一遍,给失败原因分个类,你会发现大部分“偶发失败”其实都有规律可循,只是以前没人统计。做完分类再去设计重跑策略,你的方案一定会比直接抄网上的脚本更贴合自身项目。另外,重跑只是兜底手段,核心还是要把用例设计得足够稳定。如果重跑通过率长期偏高,不要庆幸,反而该警惕环境债是不是越欠越多了。