上周有个做测试的朋友问我自动化是不是只能死磕选择器页面一改类名就崩盘我跟他说完视觉AI自动化测试这整套思路之后他愣了一下然后问能不能把完整步骤整理出来。所以有了这篇东西。先定义清楚。这里说的视觉AI自动化测试不依赖传统DOM选择器而是靠视觉模型理解屏幕截图、识别页面元素再执行点击、输入、滑动和断言。适合那些DOM结构不稳定、Canvas/WebGL重、跨端页面多或者你实在不想维护一坨>mkdir vai-demo cd vai-demo npm init -y npm install vai/sdk装完之后先写一段最基础的启动代码验证浏览器能被正常拉起const { VAI } require(vai/sdk); (async () { const ai await VAI.launch({ browser: { headless: false }, vision: { type: remote, endpoint: process.env.VISION_ENDPOINT, token: process.env.VISION_TOKEN, model: visual-v1, }, }); await ai.navigate(https://example.com); await ai.screenshot(./output/first-page.png); await ai.close(); })();跑之前把VISION_ENDPOINT和VISION_TOKEN配到环境变量里。如果你是本地推理模型vision.type改成local并配好模型目录或服务地址。第一次跑会下载浏览器内核时间取决于网络。下载完如果看到窗口弹出、截图生成恭喜环境这关过了。2.3 第一次运行时我碰到过的环境问题我不会只给你讲顺利情况。最常见的问题有三个。第一个是浏览器内核下载失败多半是下载源不稳定可以手动下载对应版本并设置环境变量指向本地浏览器程序。第二个是远程视觉服务超时本地网络要通过代理访问外网服务结果代理配置不对。这时候先去掉代码里的网络重试直接跑一个最小截图用例确认是不是网络层的问题。第三个是权限问题在 Linux 容器里跑 headless 浏览器经常缺依赖库报错会提示缺libnss3、libatk之类的系统库按提示用系统包管理器装上就行。注意不要把视觉识别的密钥写死在代码里一是容易泄露二是CI和多环境切换时会让你痛苦到怀疑人生。用环境变量管理本地建一个.env正式环境走密钥管理系统。3. 第一个视觉AI测试用例要怎么设计3.1 三个核心动作就够入门导航、操作、断言视觉AI的API设计通常很收敛不像传统框架有几百个方法。你真正常用的可能就这几个navigate(url)打开页面。action(描述)执行点击、输入、拖动等操作。waitFor(描述)等待某个界面元素或文案出现。assertText(期望内容)断言页面上出现了某段文字。screenshot(path)截图存档。action不是输入一串命令就完事。它的核心是把自然语言描述交给视觉模型去理解界面再决定在屏幕什么位置执行什么操作。所以“描述”的质量直接决定用例稳不稳。拿登录来说不要只写“点击输入框”。更好的写法是“点击页面中间的用户名输入框”。带位置、带颜色、带上下文模型就不容易搞混。3.2 写一个真实业务场景登录流程下面是一段比较完整的登录用例我会把它拆开讲。const { VAI } require(vai/sdk); async function runLoginCase() { const ai await VAI.launch({ browser: { headless: true } }); try { await ai.navigate(http://localhost:8080/login); await ai.waitFor(登录按钮, { timeout: 10000 }); await ai.action(点击账号输入框); await ai.action(输入 test_account); await ai.action(点击密码输入框); await ai.action(输入 Test123); await ai.action(点击登录按钮); await ai.waitFor(欢迎回来test_account, { timeout: 8000 }); await ai.assertText(待办事项数量 3); await ai.screenshot(./output/login-success.png); } catch (err) { await ai.screenshot(./output/login-failure.png); throw err; } finally { await ai.close(); } } runLoginCase().catch(console.error);这里有几个容易被新手忽略的点。登录按钮一开始是灰色的按钮没加载完就点会提示找不到。所以waitFor不是摆设。第二个点是输入框聚焦问题。有些前端框架在输入前需要点击一下让组件进入编辑态直接输入会漏字符。第三点断言不能太细。比如“待办事项数量 3”这种带数值的如果业务上数量本来就是动态的用例会经常性失败。要做到“验证用户已登录”而不是“验证业务数据完全正确”把数值断言交给接口层。3.3 失败截图和证据留存不是可有可无做视觉AI测试最忌讳失败时报个TimeoutError就完事。因为视觉模型的判断过程是个黑盒没有截图连排查都无从下手。我会在catch里强制保存failure.png同时把整个会话的录制视频也存一份如果SDK支持。截图能看当前页面长什么样录像能回放操作过程。很多时候你以为代码点错了实际上页面根本没加载出来或者是弹窗挡住了。这些凭一行报错信息根本看不出来。提示截图文件不要乱命名。我习惯按“用例名-步骤-时间戳”存方便自动归档。排查的时候按时间排序一眼就能看到是哪一步开始歪的。4. 实操中决定稳定性的几个关键参数4.1 描述词怎么写得又准又稳视觉AI识别元素靠的是“看到什么”。你喂给它的描述越接近人的肉眼判断越稳定。我给团队定的描述规范是“位置 外观 功能”三段式。位置右上角、页面中央、侧边栏底部。位置信息帮助模型缩小搜索范围。外观蓝色按钮、带图标的输入框。外观信息帮助区分长得像的元素。功能登录、提交表单、展开筛选。功能信息帮助模型理解意图。反例是“点击那个框”这种描述太泛。正例是“点击右上角蓝色的保存按钮”。但描述也不是越多越好。如果你的页面里同时有两个蓝色按钮外观反而变成干扰项这时候要补上下文比如“点击弹窗里的蓝色确认按钮”。4.2 等待、超时与重试视觉AI的命门传统自动化的等待突变是waitForSelector靠DOM事件驱动很快。视觉AI不一样它要先截图、再推理等于“看了一眼”所以每次判断都有几百毫秒到一两秒的成本。超时设置得太短性能慢时误报设置得太长用例跑起来像放慢动作。我的经验值参数推荐范围说明元素等待超时5~15秒短了容易闪断长了浪费时间每次轮询间隔1~3秒间隔太短会疯狂调用视觉服务成本飙升动作间停顿300~800ms给前端动画留缓冲失败重试次数1~2次超过两次的重试意义不大不如直接报错这里要特别强调一个成本问题。视觉AI服务是按识别次数计费的轮询间隔设置成200ms一个超时用例可能在十几秒里调用几十次识别服务费用比想象的高。我见过有人在生产环境把轮询间隔调到500ms账单翻了好几倍。省钱的正确做法是配合传统waitForSelector做第一层判断DOM元素出现后再用视觉AI做交互既稳又便宜。4.3 多浏览器、不同视口和移动端适配视觉AI“所见即所得”但不同设备渲染出的“所見”本来就不一样。桌面浏览器宽屏上和手机竖屏上同一个按钮的位置可能完全不同。这可能导致同一段描述在一个设备上有效另一个设备上失效。例如“点击右上角的用户头像”在PC端成立在窄屏手机上右上角可能是菜单按钮。处理这个问题的思路不是写一堆分支而是建模到模板里比如移动端用“点击顶部导航栏的菜单图标”PC端用“点击右上角用户头像”。另外一个坑是高清屏。devicePixelRatio为2的屏幕上截图分辨率会翻倍。视觉模型通常能处理好但如果你把截图直接当像素图去裁剪或比对会出偏差。真机测试时尽量固定视口宽高把系统字体大小统一减少视觉环境变量的干扰。5. 常见问题与排查技巧实录5.1 元素找不到或总是点偏这是视觉AI被吐槽最多的问题。先判断是“真的没渲染”还是“模型没认出来”。打开页面截图人眼先看。如果人眼都找不到那是页面加载、权限、动态渲染的问题如果人眼能看到模型找不到那就优化描述词或换一个模型版本。还有一个常见原因元素被弹层、Toast、悬浮导航遮挡。视觉模型能看到但它以为“看得到就能点”实际上点击被拦截。解决办法是在动作描述里增加“关闭弹窗后再点击”的步骤或者先做一次点击页面空白处关闭遮挡层。我在项目中总结了一张排查表现象常见原因排查方向元素找不到页面没加载完成加waitFor确认静态资源是否阻塞元素找到但点偏页面布局变化、元素被遮挡截图检查遮挡改用相对位置描述识别结果时好时坏视觉模型版本不稳定固定模型版本减少模型灰度同一个用例本地过、CI挂容器缺字体、分辨率不同统一容器镜像和视口参数5.2 页面频繁变动导致断言翻车断言是视觉AI测试里最容易“薛定谔”的部分。文字一变断言就失败。我遇到过的情况是页面有一个购物车图标数字实时变化。我写了“购物车数量 2”结果做活动改成3用例挂了。后来改成“购物车图标存在”立刻稳定。这背后是一个原则视觉AI应该验证“用户在意的状态”而不是验证“每一个像素值”。用户在意的是有没有登录成功而不是某个恩人数字精确等于几。所以把精确断言切成两层视觉层只做存在性判断数值层留给接口或数据库断言。5.3 在CI上跑用例时好时坏CI环境比本地更复杂。常见问题包括无头浏览器缺字体中文渲染成方块视觉模型识别崩溃。容器CPU配额低视觉推理超时。并行跑多个用例视觉服务被限流。解决方案也比较成熟。容器镜像里预装字体和控制语言环境变量减少“乱码”概率给视觉服务加限流和队列同一时间不并发太多请求CI上把超时调整为本地两倍避免因为排队导致误报。注意别在CI里把所有用例一股脑并行。视觉AI是“重型武器”并发高了之后开销非线性增长最终导致整体执行时间反而更长。合理分片比盲目并发靠谱。5.4 锁一个排查用的调试工具日常调试时我不会只在命令行看输出。我会先把代码里的headless设为false让浏览器窗口弹出来实时观察页面状态。同时加一段“慢动作”参数让每一步在页面上高亮显示当前操作的区域像看回放一样。很多SDK都提供了录制功能可以把手工操作录成原始视频。排查问题的时候视频比截图好用得多。因为你能看到光标是怎么移动的、页面在点击之后发生了什么视觉模型判断错了哪里。6. 从Demo到工程化的落地心得6.1 用例组织与数据准备Demo跑通只是开始。真正落到项目里第一件事是组织用例。我会按“业务价值”而不是“页面对象”来组织。比如“登录注册”“下单流程”“支付流程”作为主流程每个主流程再拆步骤公共操作尽量下沉到公共函数里。这样用例的可读性和复用性都高。数据准备上视觉AI用例尽量用独立账号、独立数据不要复用生产数据。因为视觉画面中如果出现其他人的数据断言很容易被干扰。我在一个项目里吃过亏测试环境的一行待办数据被另一个同事改了我的用例“待办列表显示出10条”直接失败查了半天才发现是数据污染。从那以后所有视觉AI用例执行前先初始化专属数据。6.2 与现有测试框架的集成你不一定需要抛弃现有的测试框架。反过来视觉AI SDK通常都能嵌入Jest、Mocha这类测试运行器里跑。一个比较顺滑的集成方式是保持原有用例结构把视觉AI的动作封装成自定义断言或工具函数。这样小组内其他同事不需要学新框架只需要调用你封装好的方法。const { expect } require(chai); function expectLoginSuccess(ai) { return ai.waitFor(个人工作台, { timeout: 10000 }); } describe(登录, function () { this.timeout(60000); it(用户能正常登录, async () { const ai await VAI.launch({ browser: { headless: true } }); try { await ai.navigate(http://localhost:8080/login); await ai.action(点击账号输入框); await ai.action(输入 test_account); await ai.action(点击密码输入框); await ai.action(输入 Test123); await ai.action(点击登录按钮); await expectLoginSuccess(ai); } finally { await ai.close(); } }); });这样测试报告、CI插件、失败重试都能沿用既有体系不用重复造轮子。6.3 不是所有场景都适合视觉AI混合策略才是正解这个建议可能看起来在拆自己的台但越早想清楚越好。视觉AI确实强但它慢、贵、且对环境的敏感度高于传统方案。一个合理的分布大概是测试类型推荐方式原因稳定的静态表单传统选择器快、稳定、可维护复杂组件、Canvas视觉AI传统方式无法覆盖核心E2E主流程视觉AI接近用户视角抗页面改动接口级用例不要写UI用API测试成本和速度更优我自己的实践中一套系统会保留大部分传统选择器用例来做快速回归另外抽出10%~20%的“关键主流程”用视觉AI来做深度保障。两条腿走路既不牺牲速度又能覆盖传统方案够不到的地方。拆到最后你会发现视觉AI自动化测试不只是在换一个工具它改变的是一整个对测试用例建模的方式。过去我们写“这个按钮的类名叫什么”现在写“这个按钮长什么样、在哪里、用户会怎么点它”。前者的维护靠代码同步后者的维护靠视觉稳定性和产品一致性。一个视觉模型能稳定识别长期不丑不乱的界面前端改动对测试的影响反而比过去小得多。我在实际项目里最深的体会是引入视觉AI最需要转变的不是Node.js环境也不是SDK配置而是团队对“稳定”的理解。传统自动化追求的是选择器不崩视觉AI追求的是界面语义稳定。产品设计越规范、越统一视觉AI越省心产品按钮天天换位置、改颜色再强的模型也得跟着折腾。所以我会建议团队把视觉AI的稳定性一半放在测试代码里另一半放在产品设计规范上。这样才是真正把它用好而不是换了一种方式继续维护脆弱用例。