风控台是我们做的一个测试质量平台,想解决三个老问题:测试盲区靠手感、自动化脚本维护累、Bug 定位慢。思路是把它做成一个闭环——识别风险、自动执行、根因定位、数据反馈,串起来自己转。这篇主要讲技术怎么落地的。

简单说,架构上分前端看板、后端服务和一个独立的 AI 执行服务三部分,数据底座复用了公司现成的测试平台。具体怎么实现的,下面按模块展开。

一、风险热力图:评分模型

核心是一个可配置的评分公式:

风险分 = Σ(缺陷加权分 + 趋势分 + 迭代频率分) × 业务核心度权重

缺陷加权分是重点,用了时间衰减而不是"取最近 N 个迭代"。硬边界有个问题:一个 Bug 昨天还在统计范围里、今天就不算了,风险分会突然掉一截。改成指数衰减就平滑了:

# 每个 Bug 的权重随时间衰减,半衰期 30 天(约一个迭代周期)
weight = severity_score * (0.5 ** (days_ago / 30))
# severity_score: P0=10, P1=5, P2=3, P3=1 ...
defect_score = sum(weight for bug in bugs)

好处是模块就算没有新 Bug,风险分每天也会自己往下走,不用人管。有个例外:解决方案是"延期处理"的遗留 Bug 不衰减、始终全权重,因为它是没还的技术债。

趋势分看的是最近一个周期 Bug 数相对上一周期的增量,涨了就加分、平了不动、降了也不倒扣。迭代频率分统计模块近期关联的测试任务数,改得越勤风险越高。三个分加起来,再乘业务核心度(核心模块权重更高)。

三个维度是可以单独开关的,配置里关掉某个维度它就不参与求和。最后各模块的原始分再做一次归一化,映射到 0~100,热力图上按分段染色(红/橙/黄/绿),一眼就能看出该盯哪几个。

数据治理这块单独说一句,因为它决定了分数准不准。模块归类靠一张"产品模块主数据表"动态匹配,模块名、子模块、关键词都在表里维护,改配置就生效、不用动代码;匹配不上的 Bug 和任务会自动写进"未匹配清单",提醒补关键词,避免风险分因为漏匹配而虚低。

二、AI 用例执行:最核心的部分

架构:本地浏览器 + 远程大模型

浏览器要在测试同学本地开(看得到执行过程、复用登录态),但本地不方便接大模型;大模型是集中部署的。用 MCP 协议把两边连起来:

本地起 Playwright MCP 服务(如 localhost:3001)
      ↓ 把这个地址传给远程 Agent
远程 kiro-server(大模型)通过 MCP 反向操作本地浏览器

这样本地零大模型依赖、服务器零浏览器依赖,多人还能共用一个 AI 服务。

用例驱动,不做自主探索

AI 不自己决定测什么,而是拉已经评审过的人工用例,AI 只负责读懂步骤、驱动浏览器执行。流程是:热力图输出高风险模块 → 从测试平台拉该模块最近迭代的测试任务和关联用例 → 逐条喂给 AI 执行 → 回写结果。这样质量可控、结果可追溯。

拉用例这一步有点绕:测试平台里用例是挂在测试任务下的,任务又归属某个迭代。所以要先按"产品 + 已完成状态 + 常规迭代类型 + 标题里的模块名"层层过滤出目标任务,再取它关联的冒烟/功能用例。冒烟模式只跑冒烟用例快速验核心流程,全量模式才带上功能用例。

预导航:省 token

登录、找菜单这些如果全丢给大模型,token 烧得快还不稳。做法是先用普通代码调 MCP 把登录和进入目标页做完,大模型接手时看到的第一个页面就已经在对的位置:

# 交给大模型前,先用代码把环境准备好
mcp.navigate(login_url)
mcp.fill(username_selector, account)
mcp.fill(password_selector, password)
mcp.click(login_button)
mcp.click(target_module_menu)   # 直接进目标模块
# 之后才把控制权交给 AI Agent 执行用例步骤

LLM 输出不稳定:四级降级解析

让大模型输出 JSON,总有一次会加段废话或格式抖一下。解析失败就整个挂掉是不行的,所以做了四级兜底:

def parse_result(raw):
    # 1. 短消息直接解析
    try: return json.loads(raw)
    except: pass
    # 2. 从末尾反向找 JSON,括号平衡法抠出完整对象
    obj = extract_json_by_brace_balance(raw)
    if obj: return obj
    # 3. 从 markdown 代码块里找 ```json ... ```
    obj = extract_from_codeblock(raw)
    if obj: return obj
    # 4. 全失败,从执行日志重建结果(解析步骤 + 匹配截图)
    return rebuild_from_log(raw)

原则就一句:永远有兜底,绝不因为 AI 抖一下就全盘失败。第四级的日志重建尤其关键——哪怕大模型完全没按格式输出,我们也能从它的执行轨迹里,按用例步骤数对齐出每一步的操作、截图和结果,不至于一条用例白跑。

让 AI 点得准、别空转

光有解析兜底还不够,执行过程本身也要约束。我们在系统提示词里固化了几条硬规则:页面快照(snapshot)拿到后必须立即操作,禁止连着截好几次快照空转;点击要优先点带 cursor=pointer 的父容器 ref,而不是里面的纯文字节点,不然经常点空;某一步失败就立即停、不再往下跑,避免脏数据;整条用例控制在 20 轮交互内完成,超了大概率是绕进死胡同了。页面上有俩同名元素(比如两个"AIGC")时,靠周边的菜单关键词判断哪个是左侧导航、哪个是底部 tab。这些规则每一条背后基本都对应一个踩过的坑。

结果回写

执行完三级级联写回测试平台:主记录(用例整体结果)→ 用例日志 → 每一步的检查点,截图以 base64 存成附件。截图路径是从 Playwright MCP 最新的输出目录里递归找出来的。失败用例还会自动建 Bug、带上截图和失败原因并触发工作流,"失败即提单"闭上,闭环才真正转得起来。

三、Bug 侦探:规则打底 + AI 补长尾

失败用例的根因分析没一上来就调 AI。很多失败特征很明显,先用正则分类,零成本:

RULES = [
    (r"401|403|超时|timeout|connection", "环境问题"),
    (r"找不到元素|element not found|not visible", "UI 变更"),
    (r"数据为空|no data|空列表", "数据问题"),
]
# 规则命中不了的长尾,才交给大模型分析

高频的用规则、长尾的用 AI,成本和效果就平衡了。分类结果会连同 AI 的分析一起回写测试平台,下次同类失败就有了参考。数据源直接取执行时落下的检查点日志,不用额外埋点。

特别叮嘱

真要照着做,下面这几个坑基本躲不开,提前说一声:

  1. 别信大模型每次都按格式输出。 JSON 解析一定要做多级兜底,否则十条用例总有一两条因为它多说了句话就整条白跑。
  2. 密钥千万别写死在代码里。 我们早期图快把测试平台的密钥硬编码了,是内部工具的历史遗留,正式用之前务必挪到环境变量。
  3. 调试代码记得清。 执行引擎里留过一段"只跑前两条用例"的调试逻辑,忘了删就上线,会以为覆盖全了其实只测了个开头。
  4. 字段 ID 别散在代码各处。 一堆测试平台字段 ID 直接写在业务逻辑里,可读性极差、改一处漏一处,一开始就该抽成配置或常量。
  5. 风险分依赖数据质量,先治理再上分。 Bug 和任务如果没标模块,风险分会因为漏匹配而虚低,看着安全其实是盲区。未匹配清单这类兜底机制要早点建。

小结

技术上最值得说的三点:风险分用时间衰减让它能自己"退烧";AI 执行用本地浏览器加远程大模型解耦;跟 LLM 打交道靠多级降级保证健壮性。AI 替掉的是重复执行,真正的活儿还是在风险建模和用例设计上。


也在折腾测试 AI 化的话,欢迎交流。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐