ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI自动测试实践:用视觉定位与OCR替代脆弱DOM定位

AI自动测试实践:用视觉定位与OCR替代脆弱DOM定位

1. 内容整体设计与思路拆解

1.1 为什么传统自动化测试越来越“难以为继”

做测试这行,很多人都有一个相似的感受:最早学自动化时,觉得无非是录制回放、写脚本、跑断言,套路固定,工作一段时间之后就能驾驭。但随着产品迭代越来越快,前端框架越来越复杂,传统的UI自动化脚本开始频繁“扑街”。最典型的情况是:页面上一个按钮的class从btn-primary改成btn-secondary,或者某个弹窗的DOM结构整体重构了一遍,脚本就挂了。调试脚本的时间甚至比手工测试还长,项目组就开始怀疑自动化的价值。

我做过一个比较有代表性的项目:某后台管理系统的表单流程,页面上有十几个输入框、下拉框、日期选择器,再加上各种联动校验。一开始用传统方式定位元素,XPath写了几百条,跑起来也确实能执行。但产品每周迭代两次,需求变动频繁。两个月后,用例维护成本已经高到让人头疼——每周光修locator就要花半天。后来我就想,能不能让测试脚本自己“看懂”页面,不再依赖那些脆弱的DOM定位方式?

这个思路其实就是“AI自动测试”的出发点。它并不是让AI完全替代测试人员,而是把测试过程中最耗时、最容易碎的环节——元素定位、页面理解、断言判断、脚本维护——交给AI去推理和决策。也就是:传统自动化靠的是“指令”,告诉机器去点哪里、填什么;AI自动化靠的是“理解”,让机器看到页面后自己决定该干什么。

1.2 “AI自动测试”到底解决什么问题

把需求拆开看,AI自动测试主要解决四个核心问题。

第一个是定位脆弱性问题。传统自动化依赖id、class、XPath,前端稍微一动就失效。AI可以基于视觉特征、文本内容、空间位置来定位目标,类似于人找按钮时看的不是HTML源码,而是按钮长得像什么、在页面的什么位置。

第二个是页面变化探测问题。手动写断言时,我们要指定“文字是什么”“颜色是什么”,一旦UI调整,断言就失败了。AI可以学习页面的正常状态,当页面出现视觉回归时自动识别异常区域,而不是靠固定像素做diff。

第三个是测试数据与场景生成问题。传统测试需要人工准备数据和步骤,AI可以根据业务规则自动生成边界测试数据、异常输入组合,甚至自动发现未预期到的页面状态。

第四个是智能等待与自动重试问题。页面异步加载、接口慢、动画延迟,这些都容易造成测试不稳定。AI可以学习页面加载特征,判断当前是“还在加载”还是“已经报错”,替代固定的sleep和WebDriverWait策略。

这四个问题对应到实际项目中,正好是测试人员投入精力最多的部分。而AI自动测试的工程落地,本质上就是把人的“看一眼就知道有没有问题”的能力,转化为可执行、可重复的测试逻辑。

1.3 适合谁参考这套方案

如果你是刚接触测试自动化的新人,这篇文章可以帮你建立对AI测试的整体认知,理解为什么“截图对比+视觉定位”这种方式能大幅降低脚本维护成本。如果你是有一定经验的测试开发工程师,文中的工程架构、模型选型和踩坑记录可以直接复用到你的项目中。即使你所在团队不做AI算法,也可以先引入视觉回归那部分能力,落地成本并不高。

我下面分享的内容,是基于我自己在某个跨平台Web项目中逐步摸索出来的“视觉驱动型自动测试”方案。整个方案不依赖特定的昂贵商业平台,用的是常见的开源技术栈和轻量级模型。这样讲,大家照着做也能复现个七八成。

2. 核心细节解析与实操要点

2.1 核心思路:让测试脚本长“眼睛”

AI自动测试和传统自动化最大的区别,就是增加了“感知层”。传统自动化里,脚本获取页面信息的方式是通过DOM树读取属性;AI自动测试里,脚本先“看”到页面的截图,然后通过视觉算法识别出可操作元素。

这个感知层具体分三层:

  • 图像输入层:由浏览器或移动端设备截取当前页面截图,作为视觉模型的输入。
  • 目标检测层:从截图中识别出按钮、输入框、下拉框、文字标签等UI元素,并输出它们在图像中的坐标。
  • 语义理解层:识别元素上的文字内容,结合OCR(光学字符识别)或者多模态模型输出,让脚本知道“这个按钮是保存按钮,那个输入框是用户名输入框”。

我实际使用中,最顺手的方式是“目标检测模型+OCR”的组合。目标检测模型负责找到“哪里可以点、哪里可以输入”,OCR负责告诉脚本“这里写了什么字”。两者结合,基本能做到人眼级别的元素识别。

用生活化的类比来解释:目标检测模型相当于你看一张页面截图时,眼睛能扫到有哪些控件;OCR相当于你把这些控件上的文字读出来,然后判断它们各自的功能。过去脚本是通过查看网页源码找控件,现在脚本直接“看”图,方式完全变了,但效果更贴近人的操作直觉。

2.2 为什么选择视觉方案代替传统定位方案

有人可能会问:用视觉方案会不会比DOM定位更慢、准确率更低?如果做的是大规模回归测试,视觉方案的整体收益反而更高。

有两个关键原因支撑这个结论。

第一,DOM定位在页面重构面前几乎没有容错能力。前端组件一旦升级、改版,哪怕只是改了样式类名,脚本就得跟着改;而视觉方案定位的是页面的“视觉效果”,只要用户看到的布局和样式没有根本性变化,脚本依然能识别出按钮的位置。也就是说,视觉定位对前端内部结构的变化免疫。这一点在实际项目中极其好用,因为前端重构的频率远高于视觉改版的频率。

第二,视觉方案天然贴近业务验收视角。我们从用户角度观察页面时,关心的是“页面正不正常、按钮对不对、文案对不对”,而视觉方案直接在这个层次上做校验,跳过了中间层的耦合。

当然,视觉方案也不是万能药。它的准确率依赖模型训练的质量和场景泛化能力。在固定业务系统里训练出的模型,换一套风格迥异的页面后,识别效果可能明显下降。所以实际工程中,我倾向于做成“视觉优先,DOM兜底”的混合架构:优先用视觉定位,如果视觉定位置信度低,再回退到DOM属性进行二次定位,最终把两种信息融合出最可信的坐标。

2.3 识别模型选型与实现要点

在实际落地时,模型怎么选?这里我分享一下轻量级方案的实践经验,不涉及大规模自研模型训练,适合绝大多数测试团队:

  • 目标检测模型:可以选用开源的目标检测框架,比如基于卷积神经网络的轻量级模型,专门检测按钮、输入框、卡片、弹窗等常见UI组件。训练数据可以自己标注几百张页面截图就够起步。
  • OCR引擎:采用开源OCR库或者端侧文字识别能力,识别速度很快,精度中等,配合截图预处理(灰度化、增强对比度)效果更好。
  • 可选升级路径:如果团队算力充足,可以进一步接入多模态视觉语言模型,直接输入截图输出结构化UI理解,比如“页面左上角是搜索框,右侧是用户列表”,这样自动化脚本几乎不需要写死任何元素信息。

这里有一个容易被忽视的关键细节:图像输入的分辨率一致性。不同分辨率下运行测试脚本,识别模型输出的坐标会有差异。我在项目中统一把页面截图缩放到固定宽度(比如1280像素),再输入模型,坐标输出也是基于缩放后的尺寸。然后通过比例换算回真实点击位置,基本可以解决分辨率不一致的问题。

另外一个实操要点是:UI元素识别出来后,点击的坐标不应该是包围盒的中心点,而应该是基于目标类型的锚点。比如识别出复选框,点击点应该偏向左上角的小方框;识别出下拉框,点击点应该偏向右边的展开箭头。这个偏移量不处理好,会出现“元素识别到了,但点击没效果”的问题。

3. 实操过程与核心环节实现

3.1 项目背景与结构规划

为了讲清楚整体实现流程,我以某个模拟后台管理系统为例——这是一个典型的CRUD系统,功能包括登录、列表查询、表单填写、数据提交。整个系统不算复杂,但覆盖了UI自动测试最常见的基础场景。

我规划的AI自动测试框架分四个模块:

  1. 采集模块:负责浏览器驱动、页面截图、页面状态信息收集。
  2. 感知模块:负责对截图进行目标检测和OCR文字识别。
  3. 决策模块:负责把感知结果转化为测试操作(点击、输入、断言)。
  4. 执行与报告模块:负责执行操作、记录结果、标记异常区域。

3.2 环境准备与依赖方案

技术栈选型我偏向轻量、易上手:Python作为主开发语言,接口测试和UI测试都能复用;浏览器自动化选用常见驱动框架;视觉部分用OpenCV做图像预处理,配合轻量级OCR和自定义训练的目标检测模型。

下面是环境准备的核心步骤,我在实际操作中一般分三步走:

  1. 创建独立的Python虚拟环境,避免项目依赖冲突。
  2. 安装自动化测试基础库、图像处理库、OCR库和推理所需的基础依赖。
  3. 编写一个简单的截图采集脚本,验证浏览器驱动是否正常启动、页面是否能截取到完整图像。

我记得第一次跑通整个链路时,心里最大的感受是:其实AI自动测试的工程门槛并不在于模型有多深,而在于各部分之间的衔接是否顺畅。截图、识别、定位、操作,这四个环节只要有一个地方阻塞,整个流程就推不动。所以第一步一定不要急着上模型,先把“能截图、能保存、能回放截图”这条链路跑顺。

3.3 关键代码实现:视觉感知模块

下面重点展示视觉感知模块的实现,这是整个AI自动测试的核心,也是和传统测试脚本差异最大的地方。

import cv2 import numpy as np from dataclasses import dataclass # 定义UI元素的数据结构 @dataclass class UIElement: element_type: str # button / input / label / checkbox... text: str # OCR识别的文字内容 bbox: tuple # (x_min, y_min, x_max, y_max) confidence: float # 模型置信度 class VisualPerception: """ 视觉感知模块:输入页面截图,输出可操作的UI元素列表 """ def __init__(self, det_model, ocr_engine, target_width=1280): self.det_model = det_model # 目标检测模型 self.ocr_engine = ocr_engine # OCR引擎 self.target_width = target_width # 缩放目标宽度 def preprocess(self, screenshot_path): """ 图像预处理:缩放、去噪、增强对比度 """ img = cv2.imread(screenshot_path) h, w = img.shape[:2] scale_ratio = self.target_width / w new_h = int(h * scale_ratio) resized = cv2.resize(img, (self.target_width, new_h)) # 转灰度,增强对比度,有利于OCR识别 gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) return resized, gray, scale_ratio def detect_elements(self, screenshot_path): """ 目标检测:识别页面中的UI组件 """ img, gray, scale = self.preprocess(screenshot_path) detections = self.det_model.infer(gray) elements = [] for det in detections: bbox = det["bbox"] label = det["label"] conf = det["confidence"] # 裁出该区域,交给OCR识别文字 x1, y1, x2, y2 = bbox region = img[y1:y2, x1:x2] text = self.ocr_engine.recognize(region) elements.append( UIElement( element_type=label, text=text.strip(), bbox=bbox, confidence=conf ) ) return elements

这段代码的核心逻辑很清晰:先把截图统一处理成固定宽度,解决不同分辨率下的比例问题;然后做目标检测,把检测到的区域裁出来做OCR,最终给出一条包含“类型+文字+坐标”的元素信息。这个结构的好处在于,下游决策模块可以像读地图一样使用这些信息。

特别是灰度化+直方图均衡化这一步,对OCR识别效果影响巨大。没有这个预处理时,浅色背景上的浅色文字识别率明显偏低;加了这个预处理之后,识别准确率能提升不少。这是我在实际调试中反复对比后确定的方案。

3.4 决策模块:如何让脚本“看懂”然后执行操作

感知模块输出了一系列UI元素,接下来需要决策模块把它们和测试用例关联起来。

在登录场景中,测试脚本希望完成的操作是:输入用户名、输入密码、点击登录按钮。传统脚本要写find_element_by_id("username")之类的代码,而AI脚本只需要描述“我要在文本为’用户名’的输入框里输入内容”。

决策模块的伪代码如下:

class AITestAgent: def __init__(self, perception: VisualPerception): self.perception = perception def find_element_by_text(self, screenshot_path, text, element_type=None): """ 根据可见文本查找UI元素 """ elements = self.perception.detect_elements(screenshot_path) for ele in elements: if element_type and ele.element_type != element_type: continue if text in ele.text: return ele return None def input_text(self, driver, element, content): """ 点击输入框并输入内容 """ x1, y1, x2, y2 = element.bbox click_x = x1 + (x2 - x1) * 0.5 click_y = y1 + (y2 - y1) * 0.5 # 实际点击位置经过缩放比例还原 driver.click_at(int(click_x), int(click_y)) driver.type_text(content) def test_login(self, driver, username, password): screenshot = driver.capture_screenshot() user_input = self.find_element_by_text(screenshot, "账号") pwd_input = self.find_element_by_text(screenshot, "密码") login_btn = self.find_element_by_text(screenshot, "登 录", element_type="button") # 存在性断言 if user_input is None or pwd_input is None or login_btn is None: return {"status": "FAIL", "reason": "关键元素未找到"} # 执行操作 self.input_text(driver, user_input, username) self.input_text(driver, pwd_input, password) driver.click_element(login_btn) return {"status": "PASS"}

这里有个很关键的设计思路:测试用例里不写任何与前端实现相关的selector,只写“人话”。比如“点击登录按钮”“在密码框输入内容”。这样即使整个前端框架从一个技术栈换成另一个,测试脚本都不需要修改。我正是靠着这个特性,在一次前端页面全面重构中把回归测试的维护时间压缩到了最低。

当然,这种完全依赖视觉的方式也有局限。比如按钮如果没有任何文字,只有一个小图标,OCR识别不了,那就识别不出来。针对这种情况,我的方案是把目标检测模型的另一个输出——组件类型和位置结构——做成备用定位依据,比如“页面右下角的蓝色按钮”。这属于体验优化层面的细节,项目落地初期可以先忽略。

3.5 核心难点:智能等待与动态页面适配

UI自动测试中,异步加载是最大的稳定性杀手。传统的“固定等待2秒”方式既笨拙又不稳定,更合理的方案是让脚本具备“感知页面状态是否就绪”的能力。

我在项目中实现了一套轻量级的页面就绪判定算法:

  • 截取当前页面图像,与上一个状态图像做区域差异对比。如果差异区域变化超过阈值,说明页面还在加载或更新中。
  • 结合网络请求状态判断:如果浏览器仍有未完成的请求,则继续等待。
  • 综合分析:连续N次截图差异度低于阈值,且关键元素(比如登录按钮)已被检测模型稳定检出,就判定页面就绪。
def wait_for_page_ready(driver, perception, timeout=10): """ 智能等待页面就绪 """ start_time = time.time() last_diff = 0.0 stable_count = 0 while time.time() - start_time < timeout: current_img = driver.capture_screenshot() if last_img is not None: diff = compute_image_diff(last_img, current_img) last_diff = diff if diff < 0.01: stable_count += 1 else: stable_count = 0 # 页面连续稳定且目标元素已出现,视为就绪 if stable_count >= 2: key_elements = perception.detect_elements(current_img) if len(key_elements) > 0: return True last_img = current_img time.sleep(0.5) return False

这个方案的妙处在于,它不依赖具体的前端实现,也不等待固定的时间,而是观察页面自身的变化规律。对于既有动态数据图表又有表单校验的复杂页面,这种等待策略比传统方式稳得多。

3.6 断言与校验:让AI自动判断“页面是否正常”

自动测试的最后一环是断言。传统做法是判断标题、判断某个元素是否存在、判断URL是否变化。在AI自动测试的框架下,我推荐做两种断言:

第一,语义断言。页面上的结果文本是否符合预期。例如登录失败提示应该是“用户名或密码错误”,而不是“系统异常”“network error”之类。利用OCR识别结果直接做文本匹配,能快速拦截错误。

第二,视觉断言。页面整体是否出现了明显异常区域。比如用户注册成功后,弹窗提示区域是否正常展示在预期位置。视觉断言的实现方式是:用目标检测模型识别出弹窗区域,与正常状态下的区域位置做比对,如果位置偏移过大,则判断为异常。

视觉断言的工程实现里要特别注意误报问题。我经历过这样一个教训:业务系统页面中有一块天气插件的动态区域,内容每秒变化,导致任何基于像素的视觉断言都产生大量误报。后来我用一个“掩码区域”功能解决:在配置中把动态区域标记为“忽略区域”,再次对比时跳过该区域,误报立刻下降。

另外,语义断言和视觉断言是互补关系。语义断言擅长判断“文字内容对不对”,视觉断言擅长判断“页面布局对不对”。实际项目中我会用视觉断言做全局健康巡检,用语义断言做关键业务流程的结果校验,两者结合起来,覆盖能力远超传统基于DOM的校验。

4. 常见问题与排查技巧实录

4.1 OCR识别不准确,怎么办

这是AI自动测试里出现频率最高的一个问题。常见的诱因包括:中文字体较复杂、背景干扰、文字颜色和背景相近、图标按钮不包含文字。排查思路可以按三步走:

第一步,检查预处理链路。原始截图是否经过了缩放?灰度化后对比度是否增强?如果原始截图上文字清晰但预处理后变模糊,问题就出在缩放或去噪过度。

第二步,检查OCR的识别参数。不同识别引擎对“单行文字、多行文字、竖排文字”的参数处理不同,需要根据页面排版调整识别模式。

第三步,建立OCR容错机制。不要把OCR识别的文字作为100%准确的信息源,而是在匹配时加入模糊匹配逻辑。比如用编辑距离判断“删除”和“删 除”是否指向同一按钮。我踩过的坑是:页面上“保存”按钮的文字区域很小,OCR频繁识别失败,后来我把该按钮的BBox扩大到周边8像素,并做了一次二值化处理,识别率才稳定下来。

4.2 不同分辨率下坐标偏移,点击落不到目标

这种问题在团队多人并行测试时尤其明显。开发用的4K屏幕、测试用的笔记本、CI服务器用的虚拟显示器,三者分辨率不一致时,同一个页面在不同环境里视觉布局差异巨大。

解决办法分两层。第一层是图像输入统一缩放:所有页面截图统一缩放到同一宽度,这样至少保证模型输入的分布一致。第二层是点击坐标还原:模型输出的坐标基于缩放后的图像,点击前必须乘回缩放比例,换算成屏幕坐标。如果做了这两层还出现偏移,就需要检查页面的自适应布局是否导致元素在缩放前后位置成非线性变化。我在项目中遇到过左侧导航栏有折叠动画,动画前后主体区域坐标差了几十像素,这属于动态布局问题,很难通过静态缩放解决。最终的解法是:在点击前强制收起导航栏,并等待动画结束后再截图识别。

4.3 页面弹窗与遮罩层干扰,识别到但点不动

视觉识别到了按钮,但点击就是没有反应,这是另一个高频问题。多数情况是:页面上存在一个透明的遮罩层或者隐藏的弹窗,覆盖在目标按钮上,人眼看不出来,但点击事件被拦截了。

排查思路是先看“点击坐标对应的顶层元素是什么”,而不是直接改识别模型。我在项目中加了一个点击前探测机制:在计划点击的坐标处,读取浏览器返回的元素信息,如果不能匹配到可点击元素,就认为是遮罩层干扰,此时尝试按ESC键关闭弹窗或等待遮罩层消失,再重新识别。这个机制解决了很多难以定位的诡异问题。

另外补充一个细节:弹窗关闭动画期间,遮罩层还会保留几百毫秒。这个阶段点击任何按钮都可能被吞掉。所以遇到“识别到了但点不动”时,最实用的临时办法不是调模型,而是先等0.5秒再点一次。我最终把这类处理统一收敛到了智能等待模块里,让脚本自动适应动画延迟。

4.4 误报率太高,视觉断言不够稳定

视觉断言误报的来源主要有三类:动态刷新区域、非必要的交互动画、字体渲染差异。字体渲染差异容易跨平台出现,同一份页面在Windows和macOS上字体渲染不同,导致单像素级别差异。这个靠把比对阈值调大多少都难完美解决,因为有些组件动画变化幅度很大,一眼看上去是页面正常变化,但像素差异已经超阈值。

我的建议是不要追求消除误报,而是控制误报的“影响范围”。在视觉断言比对结果中,把异常区域用矩形框标出,附带一张“正常版本”和“当前版本”的对比图。测试人员只需要扫一眼对比图就能快速确认是否是误报。这种做法比单纯的“通过/失败”标准更适合AI自动化测试的落地节奏。

4.5 模型训练数据从哪来

很多人问,目标检测模型要训练,标注数据从哪来?我的经验是:从项目自身的回归用例中产生。

每跑一轮测试,系统会截取大量页面截图;每发现一次页面更新,也会触发新的截图。把这些截图收集起来,配合开源标注工具进行标注,一个业务系统跑一个月,基本能积累几百张高质量的标注图片。对于UI组件检测来说,几百张图片已经足以支撑一个可用的初版模型。后续再在测试过程中持续收集误判案例,优化标注,模型会越用越准。

这里我的体会是:AI测试模型的准确率不是一步到位的,它更像一个持续迭代的过程。初始模型的准确率能达到70%到80%就可以投入使用,剩余的通过设计“人工介入确认”的流程来兜底。我在项目中设置了“低置信度入库”机制:模型置信度低于0.6的识别结果不直接执行,而是先进入待确认队列,人工确认后回填到训练集。这个机制让模型每周都有小幅提升。

5. 从实际项目中总结的经验

在我个人实际操作中,AI自动测试和传统自动化不是替代关系,更像是协作关系。传统自动化的优势是精确、快速、可预期,适合那些需求稳定、页面结构极少变更的核心流程;AI自动测试的优势是鲁棒、理解力强、维护成本低,适合UI频繁迭代、组件复用度高、回归范围广的业务场景。

我建议团队落地时不要一开始就做非常复杂的多模态模型,而是先选一个业务流程相对固定的模块做试点。搭建视觉感知链路、跑通用例、观察误报率,用两周时间评估这套方案在团队中的实际收益。试点通过后,再逐步扩大覆盖范围,把最有价值的回归场景优先迁移过来。

还有两个细节值得特别提醒:

第一,截图质量决定一切。AI自动测试对真实运行环境要求比传统测试高很多,如果CI服务器用的是低分辨率虚拟显示器,截图本身模糊,后面的识别无从谈起。建议统一使用固定分辨率的虚拟显示器,并关闭硬件加速,保证截图稳定性。

第二,把“人类确认”环节保留在关键节点。无论AI识别能力多强,核心交易流程(支付、权限变更、数据删除)都建议在执行最终提交前增加人工二次确认。这不是不信任AI,而是工程上对高风险操作的兜底策略。

如果你现在正因为脚本频繁失效而对UI自动化感到头疼,不妨按这篇文章里的思路,先试着把“截图—识别—定位—执行”这条最小链路搭起来。我个人体会是,花一个周末就能跑通第一个AI驱动的测试脚本,而它带来的思路转变,远比初始的准确率重要。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进