Playwright小项目:跑100次量出flaky率,产出物直接进作品集

举报
霍格沃兹软件测试开发 发表于 2026/10/13 16:11:22 2026/10/13
【摘要】 聊到 UI 自动化,对面问了句很轻的话:你写的那些用例,怎么知道它稳不稳?你张嘴就是"跑了一遍,全绿"。追问跟着就到:"跑了几次?有没有重试?重试才过的占多少?“三个问句,一个都接不上。因为绿是一个瞬间的状态,稳是一段分布上的概率——手里没有跑过 N 次的记录,这题就没法答,只能重复那句"我这边跑是绿的”。这不是抬杠,是这轮工具演进下新人最该补的一块。据第三方发布追踪(gitwatchman...

聊到 UI 自动化,对面问了句很轻的话:你写的那些用例,怎么知道它稳不稳?你张嘴就是"跑了一遍,全绿"。追问跟着就到:"跑了几次?有没有重试?重试才过的占多少?“三个问句,一个都接不上。因为绿是一个瞬间的状态,稳是一段分布上的概率——手里没有跑过 N 次的记录,这题就没法答,只能重复那句"我这边跑是绿的”。

这不是抬杠,是这轮工具演进下新人最该补的一块。据第三方发布追踪(gitwatchman、stuffinsider、libraries.io,核实于 2026-10-02),Playwright 在 2026 年已经推进到 v1.6x 一线——这是第三方口径、不是官方核实,也不牵扯任何特性效果数字。它只说明一件事:版本迭代到这个频率,"会用某个工具"的半衰期极短,而"能回答这段自动化可信几分"这件事,换 runner、换版本都还得有人做。会用工具不值钱,能量化用例稳不稳才值钱。

四步就能走完:写一小批有抖动风险的用例 → 同一批连跑 N 次 → 算首过率、flaky 率、失败分布 → 产出一页带口径的稳定性报告。

第一步:写一小批用例,别挑那种永远绿的

五到八条就够,但每条都要有真实抖动风险:一个搜索联想框(要等结果列表异步回来)、一个加入购物车(要等状态回写)、一个头像上传(要等后台任务完成再刷新)、一个结算页(元素多、选择器最容易跟着改版抖)。别拿"打开首页断言标题对不对"凑数——它一百次能绿一百次,量出来的数只会骗你自己。这一步练的是选靶子:知道哪里会抖,比会写多少条 spec 重要。

第二步:同一批用例连跑 N 次,每轮只记三态

Playwright 这类 runner 基本都提供把同一批用例重复执行 N 遍的参数(具体名字去官方 CLI 文档确认一次,版本会变),CI 里挂上就能跑;一时接不上 CI,就在本地连跑一轮把结果抄下来。关键是记录口径:每条用例每轮只有三种落点——首跑就过(first_pass)、首跑挂了重试才过(flaky)、重试仍然挂(hard_fail)。把这三态落成一张表或一串 JSON,你后面所有指标都从这三态里算,不用重跑。

三个口径先钉死,不然数字会打架

同一个"通过",三个人能算出三个数——这就是聊稳不稳之前必须先把口径写在纸上的原因。首过率 = 首跑就过的轮次 ÷ 总轮次,它才代表"你写的用例本身有多可信";flaky 率 = 重试才过的轮次 ÷ 总轮次,它代表"有多少不稳定被重试掩盖了";两者相加,就是你开头那句"全绿"里的水分。再补一个不稳定分 = flaky 率 + 硬失败率,也就是"这一轮首跑不值得信"的比例,它是你排治理优先级的排序键,不是拿来汇报好看的成绩。三个口径里我最建议你盯首过率:它是唯一一个不会因为多按一次重试就自己变好的数。口径先说死,后面一半争论都能省掉。

接回旧知识:这就是把"跑一遍"换成"跑统计"

别被 flaky 这词唬住,它和你写过的接口断言是同一套骨架,只是判定对象变了。做接口测试时你对的是"这一次响应对不对"——状态码、字段结构、错误码;现在你对的是"一百次里对了几次"。你早就会的那些动作全用得上:一个 for 循环、一个 dict 装结果、collections 里的 Counter 分类计数、把统计丢进表格看一眼。要补的只有三件小事:定三态口径、把每条用例的分布算出来、把失败按性质分开数。这也是这个小项目适合新人第一周就做的原因——它不逼你啃一套陌生范式,只把你已经会的"写断言"往前推一格,推到"给断言打分"。

第三步:一条命令算出首过率、flaky 率和失败分布

纯标准库、样例内置、不联网、不读外部文件,复制即跑——这一段就是你面试能当场打开摊在桌上的东西:

# flaky_report.py —— 纯标准库(random/collections),样例内置,`python flaky_report.py` 直接跑通
import random
from collections import Counter, defaultdict

random.seed(20261002)  # 固定随机种子:同一份样例每次跑出同一张报告,可复现

ROUNDS = 100   # 同一批 UI 用例连跑 100 次
RETRY = 1      # 失败后允许重试一次(重试才过 = flaky)

# 档①:一小批 UI 用例(对应你真实项目里的 spec)
# fail_p = 这条用例首跑挂掉的概率;ftype_w = 挂掉时归因权重(locator 定位类 / assert 断言类 / wait 等待类)
CASES = {
    "login_form.spec":     {"fail_p": 0.05, "ftype_w": {"locator": 0.2, "assert": 0.2, "wait": 0.6}},
    "search_suggest.spec": {"fail_p": 0.20, "ftype_w": {"locator": 0.3, "assert": 0.1, "wait": 0.6}},
    "cart_add.spec":       {"fail_p": 0.08, "ftype_w": {"locator": 0.5, "assert": 0.3, "wait": 0.2}},
    "checkout_flow.spec":  {"fail_p": 0.25, "ftype_w": {"locator": 0.2, "assert": 0.5, "wait": 0.3}},
    "profile_avatar.spec": {"fail_p": 0.45, "ftype_w": {"locator": 0.6, "assert": 0.2, "wait": 0.2}},
}

FTYPES = ["locator", "assert", "wait"]


def pick_ftype(w):
    r, acc = random.random(), 0.0
    for k, p in w.items():
        acc += p
        if r <= acc:
            return k
    return "wait"


# 档②:连跑 ROUNDS 轮,每轮记录首过 / 重试才过(flaky) / 重试仍挂(硬失败)
hit = defaultdict(Counter)          # hit[case]["first_pass"|"flaky"|"hard_fail"]
ftype = defaultdict(Counter)        # ftype[case] 只统计硬失败的归因

for name, c in CASES.items():
    for _ in range(ROUNDS):
        if random.random() >= c["fail_p"]:
            hit[name]["first_pass"] += 1
            continue
        if random.random() < 0.8:     # 等待类抖动重试大概率能过(演示口径)
            hit[name]["flaky"] += 1
        else:
            hit[name]["hard_fail"] += 1
            ftype[name][pick_ftype(c["ftype_w"])] += 1

print(f"=== 用例稳定性报告(样例轨迹 / 连跑 {ROUNDS} 次 / 允许重试 {RETRY} 次)===\n")
print(f"{'用例':<22}{'首过率':>8}{'flaky率':>9}{'硬失败率':>10}  不稳定分  失败分布(定位/断言/等待)")

rows = []
for name in CASES:
    h = hit[name]
    fp = h["first_pass"] / ROUNDS
    fl = h["flaky"] / ROUNDS
    hf = h["hard_fail"] / ROUNDS
    unstable = fl + hf          # 不稳定分:首跑不可信的比例
    dist = "/".join(str(ftype[name][t]) for t in FTYPES)
    rows.append((name, fp, fl, hf, unstable, dist))
    print(f"{name:<22}{fp:>7.0%}{fl:>8.0%}{hf:>9.0%}{unstable:>10.0%}   {dist}")

first_pass_all = sum(hit[n]["first_pass"] for n in CASES) / (ROUNDS * len(CASES))
flaky_all = sum(hit[n]["flaky"] for n in CASES) / (ROUNDS * len(CASES))
print(f"\n套件级:首过率={first_pass_all:.0%}  flaky率={flaky_all:.0%}"
      f"  (表面绿灯率={first_pass_all + flaky_all:.0%},含重试才过的水分)")

worst = max(rows, key=lambda r: r[4])
print(f"最不稳定:{worst[0]}  不稳定分 {worst[4]:.0%}  → 优先治理")

print("\n=== 放行判定 ===")
gates = []
if first_pass_all < 0.90:
    gates.append(f"套件首过率 {first_pass_all:.0%} < 90%,先补等待策略再谈全绿")
if flaky_all > 0.05:
    gates.append(f"套件 flaky 率 {flaky_all:.0%} > 5%,重试次数不是越多越好")
if worst[4] > 0.30:
    gates.append(f"{worst[0]} 不稳定分 {worst[4]:.0%} > 30%,隔离到单独 job 观察")
if ftype[worst[0]]["wait"] / max(sum(ftype[worst[0]].values()), 1) >= 0.5:
    gates.append(f"{worst[0]} 失败集中在等待类,先改断言写法再考虑加超时")

print("HOLD(先修不稳定,不放行『全绿』结论)" if gates else "PASS(可报全绿)")
for g in gates:
    print("  - " + g)

为什么这么写、踩了什么坑: 第一版我把指标定成"最终通不通过",跑完报出来一个很好看的数——接近九成的用例最后是绿的。差点就把这版贴进作品集,后来才反应过来:那里面一大半是靠重试捞回来的,我把 flaky 洗成了稳定。改成三态分开记之后,画面完全反过来,首跑就过的只有不到八成,首跑挂掉的上百次里有七成多被重试悄悄吃掉。第二坑是重试次数必须写死:改成允许重试两次,flaky 率立刻变漂亮——你优化的是指标,不是用例。第三坑最值钱:失败不分类,你永远只会加重试。脚本把最不稳定的那条单独点了名,它的硬失败几乎全堆在定位类,这种抖加多少超时都没用,得回去改选择器;只有集中在等待类的,才有资格谈重试和超时。所以判定环节我没只放阈值,而是按失败性质分流——这也是报告里那句 HOLD 的真正含义:不是"这次没跑绿",是"这批用例还不足以支撑一次放行判断"。顺手加固定随机种子,是因为作品集里的东西最怕"我这儿能跑"。

第四步:交一页带口径的《用例稳定性报告》

脚本跑通不是终点,那页纸才是能带走的东西。一页里写清:样本量与重试策略、三态定义、每条用例的首过率/flaky 率/硬失败率/不稳定分、失败分布按定位类-断言类-等待类各占多少、点名最不稳定那条、最后一行放行判定。再配两句容易被追的坦白——“这一百轮是我自己造的样例轨迹,不是任何真实项目的 CI 记录”,“90%、5%、30% 三条线是演示拍的,接到真实项目要按 CI 时长和历史抖动重标”。有口径的数字才叫证据。能主动说清这两句,比报一个大数更像一个做过的人。

接上 CI,让这份报告每天自己长一遍

小项目要变成长习惯,得让它自动化:把三态记录落成每条用例每次执行的一个 JSON 行(runner 自带的 JSON 报告器基本都能出这种格式),让统计脚本读目录而不是读你手抄的表;再把"连跑 N 轮"挂成夜间任务,早上第一件事是看首过率有没有掉。这一步真正的价值不在省事,而在把"我觉得这条用例挺稳"变成"系统每天替我记一次账"。一个月的记录攒下来,你第一次有了属于自己的抖动基线,也第一次能在别人说"这条偶尔会红"时接上一句:它过去三十天红了四天,全在定位类,上周改完选择器已经首跑就过了——这句话的分量,比任何"熟悉 Playwright"都重。

会说"我会 Playwright"和能交一份 flaky 报告,差在哪

对照维度 只会说会用 Playwright 交一份能跑的稳定性报告
被问"稳不稳" 答"全绿",被追一句就断 报得出首过率、flaky 率、硬失败率三个口径
被问"怎么发现的" 靠肉眼看 CI 红红绿绿 一条命令连跑 N 轮,失败按定位/断言/等待分流
被问"然后你做了什么" 加了三次重试,现在"稳了" 拿 HOLD 判定说清哪条隔离观察、哪条必须改选择器
工具再换代时 参数表一更新就得重背 三态口径与报告照搬,换 runner 一样能跑

这一步往就业上怎么看,以及它的边界

抬一层看:据公开的招聘信息定性来看,AI 与自动化方向的测试岗一直在招,别只盯"点测"两个字——量化稳定性这类活儿,恰好长在这条方向上。这只是方向性判断,本文不引任何规模或报酬类的数。项目本身也有边界,说清楚反而加分:一百轮执行记录、各条用例的不稳定分、失败分布和那句 HOLD 判定,都是本文构造的演示数据,跑的是随机模拟而不是任何真实被测系统,也不指涉任何真实企业的面试原题。想把这条线从"会用工具"练成能当场摊开的产出物,学社 AI 测试作品集实战做的就是这类演练。

全绿是一瞬间的状态,稳不跑得一百次才算得出来。

今晚就把你最不放心那条 UI 用例连跑 20 次,记下三态算个首过率——数字比争论好使。

本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。