自愈定位要解决什么问题,以及一个先要拆穿的说法
Web UI 自动化测试最脆弱的部分不是断言逻辑,而是元素定位——id、class、DOM 层级只要改版就可能失效,脚本报错的不是业务逻辑,而是"这个按钮的选择器找不到了"。**自愈定位(Self-Healing Locators)**要解决的正是这个问题:定位失败时,系统尝试自动找到"看起来应该是同一个元素"的新定位方式,而不是让测试直接红掉、等人工去修选择器。
这个领域最老牌、star 数最高的开源项目是 Healenium(healenium/healenium-web,201 star,最近一次提交在 2026-09-07,仍在积极维护)。它的 README 里有一句话:"leverages machine learning to allow Selenium tests to self-heal"。这篇文章第一件事就是直接读源码验证这句话——结论是:这句话在营销意义上不算错,但在技术意义上是误导的。 Healenium 的核心自愈机制不是机器学习,是一套加权打分的启发式公式,权重是硬编码的浮点数常量,训练数据、模型推理这些机器学习的基本要素在这套公式里完全不存在。
先说清楚这篇要回答的三个问题:Healenium 这套"号称机器学习"的启发式打分具体怎么算分,天花板在哪?它 2026 年新增的 AI 能力具体是什么形态,为什么代码里直接把它锁在付费墙后面?相比之下,真正基于 LLM 做语义自愈的开源项目——MarketSquare/robotframework-selfhealing-agents——用什么架构实现"用元素在做什么而不是元素在哪"来重新定位?
Healenium 的打分公式:树相似度 + Levenshtein 距离,没有任何机器学习成分
Healenium 的自愈流程分两层:SelfHealingEngine(healenium-web 主仓库)在测试运行时截获定位失败,把当前页面的 DOM 解析成 Node 树(通过 JsoupHTMLParser),再交给独立发布的 tree-comparing(Maven 坐标 com.epam.healenium:tree-comparing,healenium-web 的 pom.xml 里锁定版本 0.4.14)做树匹配打分。
匹配的核心逻辑在 PathFinder.findScoresToNodes():先用 LCSPathDistance(最长公共子序列)比较"失败元素的历史路径"和"当前页面所有叶子节点路径",找出结构上最相似的候选路径,再对候选路径上的每个节点调用 HeuristicNodeDistance.distance() 打分。这个打分函数直接反编译字节码验证过(Maven Central 上的 tree-comparing-0.4.14.jar,常量池里的浮点数跟源码完全一致),公式是这样的:
private static final double POINTS_FOR_TAG = 100.0D;
private static final double POINTS_FOR_ID = 50.0D;
private static final double POINTS_FOR_CLASS = 40.0D;
private static final double POINTS_FOR_VALUE = 30.0D;
private static final double POINTS_FOR_OTHER_ATTRIBUTE = 30.0D;
// maximumScore = 350.0
double score = LCSDistance / curPathHeight * 100.0; // 路径结构相似度部分
if (tag相同) score += 100.0;
if (id存在) score += 50.0 * levenshteinScore(id1, id2, 0.3);
score += 30.0 * levenshteinScore(innerText1, innerText2, 0.3);
score += classNamesIntersect比例 * 40.0;
score += otherAttributes逐项Levenshtein均值 * 30.0;
return score / maximumScore; // 归一化到 [0, 1]calculateLevenshteinScore() 内部用的是 Apache Commons Text 的 LevenshteinDistance,把两个字符串的编辑距离转换成一个 [0,1] 的相似度值,阈值参数(0.3 或 0.75)决定超过多少编辑距离直接判定为不相似。这整套公式里没有任何"学习"的成分——权重(100、50、40、30)是写死的常量,不是从数据里拟合出来的参数;相似度算法是字符串编辑距离,不是词向量或语义嵌入。一个元素的 id 从 login-btn 改成 signin-btn,Levenshtein 距离小,打分高,被判定为"同一个元素";但如果改成语义完全一样但字符串完全不同的 submit-auth,编辑距离拉满,打分骤降,即使这在人类眼里明显是"同一个登录按钮"。这正是启发式打分的天花板:它衡量的是字符串和结构的表面相似度,不是元素功能的语义相似度——这也是为什么规划里提到的"LLM 语义匹配自愈:用'这个元素在做什么'而不是'这个元素在哪'重新定位"是一个真实的技术缺口,不是营销话术编出来的需求。
最终分数由 HealingService 用配置里的 score-cap(application.conf 默认 .6,测试配置里是 .55)做阈值过滤,超过阈值的候选节点按分数降序排列,逐个尝试转换成 CSS 选择器或 XPath,用 driver.findElements() 验证是否能唯一定位到一个元素——第一个验证成功的就是自愈结果。这是一套完全确定性的规则引擎,跑得快、可解释、不需要任何外部 API 调用,但边界也很清楚:只要变化的东西恰好落在"标签、id、class、文本、属性"这几个可比较维度之外——比如整个组件换了实现方式但语义不变——这套公式就无从判断。
2026 年新增的 AI 接口:一条被付费墙直接挡住的路径
RestClient.java 里有一个不属于上面这套打分逻辑的独立方法:
public String getXpathSelector(Node node, String sessionId) {
HttpRequest request = new HttpRequest(HttpMethod.POST, "/selectors/xpath");
...
HttpResponse response = aiServiceExecute(request);
if (HTTP_NOT_FOUND == response.getStatus()) {
throw new RuntimeException("[Get Xpath Selector] Compatibility error. You must have a paid hlm-ai service.");
}
...
}这个方法把 DOM 节点信息 POST 到一个独立的 aiServiceUrl(配置项是 hlm.ai.url,application.conf 里默认指向 http://localhost:6565),跟打分引擎完全解耦——HealingService.createXPathFromElement() 只在 useXPath(engine) 判断为真(即 selector-type 配置为 xpath)时才会调用它,作为 CSS 选择器构造之外的备选路径。这段代码本身证实了两件事:一是 Healenium 团队自己也承认纯规则打分不够用,专门开了一条"让 AI 服务生成 XPath"的通道;二是这条通道被显式挡在付费墙后面——收到 404 直接抛异常,报错文案直白到没有任何修饰:"You must have a paid hlm-ai service"。这个 hlm-ai 服务本身是闭源的商业化组件,不在开源仓库范围内,具体用什么模型、怎么做语义理解,外部完全无法验证。
这个细节比"某个高星工具的 AI 宣传是否属实"这类判断本身更有价值:它说明即便是这个领域最老牌、维护最久的开源项目,自己都认为纯规则打分已经撑不住了,需要引入语义理解能力——但那部分能力选择了闭源商业化,而不是开源共建。 这跟后面文章 11 会看到的模式(高星项目的"智能"宣传往往包装了非 LLM 的机制)不是同一种情况:Healenium 不是虚假宣传,它是诚实地承认了局限,然后把补丁做成了付费功能。
真正的 LLM 自愈:MarketSquare/robotframework-selfhealing-agents 的编排-定位双智能体架构
MarketSquare/robotframework-selfhealing-agents(27 star,Apache-2.0,2025-04 创建,最近提交 2026-02,MarketSquare 是 Robot Framework 生态下的官方组织)是这个领域少见的、源码里能直接确认调用真实 LLM API 的开源实现。它是 Robot Framework 的一个 Listener 插件,用两层智能体架构处理定位失败:
第一层:OrchestratorAgent,基于 PydanticAI 构建,职责是判断"这次失败是不是定位器问题"。它的判断逻辑本身也委托给了 LLM 调用链下游的 locator_agent.is_failed_locator_error()——比如 Selenium 场景下检查报错文本是否匹配 "with locator" ... "not found" 之类的模式——如果不是定位失败(比如是断言失败或网络超时),直接返回 NoHealingNeededResponse,不浪费 token 去跑修复流程。判断为定位失败后,才把请求路由给一个 pydantic-ai Agent,用 output_type=[self._get_healed_locators] 把"修复定位器"注册成一个可调用工具,走真实的 LLM tool-call 循环。
第二层:BaseLocatorAgent(及其 SeleniumLocatorAgent/BrowserLocatorAgent 子类)是真正做语义定位的部分,支持两种工作模式,由配置项 use_llm_for_locator_generation(默认 True)切换:
- 纯 LLM 生成模式:把失败时的 DOM 树、错误信息、失败的定位器字符串、已经试过但仍然失败的定位器列表,一起塞进 prompt,直接让模型输出 3 个新的定位器候选。系统提示词写得很直白:"Using the elements in the DOM at failure time, suggest 3 new locators... Make sure you do not suggest a locator that is on that list."——这是纯语言模型的语义推理,没有任何规则打分介入。
- DOM 工具生成 + LLM 选择模式:先用确定性代码(
_dom_utility.get_locator_proposals())基于 DOM 结构枚举候选定位器,再让一个独立的selection_agent从候选列表里选出"最合适"的一个。这条路径是"确定性生成 + LLM 判断"的组合,跟纯 LLM 生成模式相比降低了幻觉出无效选择器的风险,但语义理解的空间也更小——LLM 只能在已枚举的候选里选,选不出候选集合之外的答案。
这两种模式的取舍,恰好对应规划里"自愈成功率和误判率的权衡"这个问题的一个具体答案:纯 LLM 生成模式覆盖面更广(模型可以想出候选枚举遗漏的定位方式),但更容易产生语法正确却选不到元素的幻觉输出;DOM 工具生成模式更保守,可靠性更高,但天花板被"确定性枚举能找到什么"卡住了。 项目选择把这个开关暴露给用户,而不是替用户做决定,这个设计本身就承认了两条路径各有代价,没有一条能同时拿到"高覆盖率"和"低误判率"。
响应校验层还有一处工程细节值得记录:generation_agent.output_validator 会在拿到模型输出后再跑一层确定性过滤——_sort_locators() 用 is_locator_unique() 把"能唯一定位到一个元素"的候选排到前面,_filter_clickable_locators() 针对 click/tap/select 一类关键字的失败场景,专门过滤出真正可点击的元素。如果最终没有任何候选通过校验,代码会抛 ModelRetry,触发 pydantic-ai 内建的重试机制而不是直接返回一个可能错误的定位器。这跟单纯"模型说什么就是什么"的调用方式不同——LLM 的语义判断结果,最终还是要经过一层确定性代码校验才会被采信,这是把"语义理解"和"结构正确性"分成两个独立职责,而不是指望一次模型调用同时把两件事都做对。
配置层(SelfhealingAgents/utils/cfg.py)确认了这是真正的多 provider 架构:orchestrator_agent_provider/locator_agent_provider 都支持 openai/azure,默认模型是 gpt-4o-mini,还有独立的 request_limit(默认 5)和 total_tokens_limit(默认 6000)做成本硬约束——这套硬约束本身就是对"LLM 自愈会不会失控烧 token"这个疑虑的直接回应:每次自愈请求的 token 消耗有明确上限,超出直接终止而不是无限重试。
自愈成功率和误判率的权衡,卡在哪
把两个项目的机制并排看,规划里"什么时候自愈是在掩盖真实的 UI 回归 bug"这个问题有一个具体的答案:无论是启发式打分还是 LLM 语义判断,自愈机制本身都无法区分"这是一次无害的实现细节调整"和"这是一次真正的功能性 UI 回归"——它们判断的都是"新页面上有没有一个东西看起来像旧的那个元素",而不是"这次改动是不是符合产品预期"。Healenium 的打分公式对一次纯粹的 id 重命名和一次误删按钮后又加了个视觉相似但功能不同的新按钮,可能给出相似的高分;LLM 语义判断在面对"页面上新增了一个文案相似但逻辑完全不同的按钮"时,同样可能被表面语义相似度误导。
这意味着自愈机制的合理边界不是"提高判断准确率到接近 100%",而是把自愈结果当成一个需要留痕、需要人工复核的信号,而不是一个可以完全信任、直接让测试变绿的自动修复。robotframework-selfhealing-agents 的报告系统(reports/report_generator.py 及其一系列 report_types)专门生成 healed_files_report/diff_files_report——这个设计本身就是在承认"自愈成功"不等于"没有问题",而是把每一次自愈都记录成一条需要回顾的变更,供人工判断这次修复背后是否藏着一次真实的 UI 回归。这跟 Healenium 的 backlight-healing 配置项(默认 true,在测试报告里高亮标记哪些元素是被自愈找回来的)是同一个思路——两个技术路线完全不同的项目,在"自愈结果必须可追溯、可复核"这一点上做出了同样的工程选择,这比任何单一的打分公式或 prompt 设计都更能说明这个领域的共识边界在哪。
总结
- Healenium README 宣称的"leverages machine learning"是营销意义上的夸大——核心自愈机制(
HeuristicNodeDistance.distance())是一套硬编码权重(TAG=100、ID=50、CLASS=40、VALUE=30)的加权打分公式,相似度计算用的是 Levenshtein 编辑距离,跟机器学习的训练/推理范式没有关系;已通过反编译 Maven Central 上tree-comparing-0.4.14.jar的字节码常量池验证源码真实性。 - Healenium 2026 年新增的
RestClient.getXpathSelector()是一条独立的 AI 生成 XPath 通道,代码里对 404 响应直接抛出"You must have a paid hlm-ai service"异常——即便是这个领域最老牌的开源项目,也承认纯规则打分撑不住语义理解的需求,但选择把这部分能力做成闭源付费服务,而非开源共建。 MarketSquare/robotframework-selfhealing-agents是真正基于 LLM 的开源自愈实现,用编排智能体(判断是否需要自愈)+ 定位智能体(生成/选择新定位器)的两层架构,支持"纯 LLM 生成"和"DOM 枚举 + LLM 选择"两种模式,二者在覆盖面和误判率之间做出了不同取舍,项目把这个选择权交给用户而非内置默认答案。- 该项目的 LLM 输出并非直接采信——
output_validator层会用确定性代码校验候选定位器的唯一性和可点击性,校验失败时触发ModelRetry重试而非直接返回可能错误的结果,把语义判断和结构正确性验证拆成了两个独立职责。 - 无论是启发式打分还是 LLM 语义判断,自愈机制本身都无法分辨"无害的实现调整"和"真正的功能回归"——两个技术路线完全不同的项目分别用
backlight-healing高亮标记和结构化的 healed/diff 报告,选择了同一种应对方式:把自愈结果当作需要留痕和人工复核的信号,而不是可完全信任的自动修复。
欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页