做了这么多年Android自动化测试我越来越觉得传统那套“定位器”思路有点畸形。你老老实实写了两千行用例结果客户端发了个新版本只改了页面里几个控件的resource-id回归就开始大面积飘红。每次修脚本都像是在给上一个开发者的代码还债。这也是我第一次看到UI-TARS这个项目时比较兴奋的原因。它给Android自动化测试提供的是一条完全不同的路不依赖XML控件树、不写XPath、不维护定位器只靠“看屏幕”来理解界面、执行操作。模型把当前截图当输入直接输出下一步动作的坐标和语义描述本质上是一个视觉驱动的GUI智能体。一篇博文很难装下所有细节所以我想把这一段时间里实际跑通UI-TARS、用它做Android自动化测试的整套思路、踩坑和可复现方案完整写出来。无论你是刚接触视觉自动化、还是已经在维护Appium用例的老手这篇都能帮你省下不少试错时间。1. 传统Android自动化测试的问题到底出在哪1.1 控件定位那一套为什么越来越脆弱先说个同事前两天跟我吐槽的事。他们的项目组用某主流框架维护一套登录流程用例一共也就三十多条但每次发版都要花半天时间修定位器。按钮换个id、弹窗加个蒙层、列表多套一层布局脚本就找不到目标了。为什么因为这些框架的核心思路是把UI抽象成“一棵控件树”脚本先拿到这棵树再用resource-id、className、XPath这些路径表达式去树上找节点。这套思路有两个天然的软肋。第一它要求应用的视图层级对测试工具可见一旦碰到不可见的自定义绘制控件、WebView渲染内容或者跨进程界面拿到的树本身就是残缺的后面所有定位都失去意义。第二它把测试用例和页面结构强耦合你写的是“用户的点击行为”实际记录的却是“某个控件在树里的坐标路径”页面结构一变用例就失效。我见过最离谱的一个案例客户端为了性能优化把一个高频页面的FrameLayout换成了ComposeView结果底下三百多条用例一夜之间全挂了。开发只改了一行声明测试这边要改几百个定位器。这个账怎么算都不划算。1.2 视觉驱动的思路怎么绕开这些坑人是怎么测试App的拿起手机看一眼屏幕找到按钮点下去。整个过程根本不关心这个按钮在代码里叫什么id也不关心它的父布局是LinearLayout还是ConstraintLayout只要它在屏幕上长得像一个按钮用户就知道该点它。UI-TARS走的正是这条路。它的输入是当前屏幕的截图加上你给它的任务描述输出是自然语言梳理的推理过程和具体的动作坐标。模型内部把整张图当成一个整体来理解而不需要一棵结构树。也就是说只要人眼能看出“这是个确认按钮”UI-TARS就能在图上找到它并把坐标输出出来。控件树拿不到的东西截图里全都有。这不是什么玄学。它本质上是一个多模态大模型在海量真实GUI交互数据上训练过见多识广之后它不光认识“登录按钮”还理解“这种弹窗的关闭按钮一般出现在右上角”——这些都不是靠XML路径能表达的知识。对我这种长期被控件树折磨的人来说视觉驱动带来的最大改变是用例编写从“面向结构编程”变成了“面向意图编程”你要测什么就让它做什么剩下的事模型自己想办法。2. UI-TARS核心原理一台会“自己看屏幕”的机器2.1 它跟OCR加坐标的方案不是一回事很多人一听“视觉驱动”第一反应是“这不就是OCR识别文字然后再找坐标吗”。我在跟团队分享的时候专门把这两件事掰开讲过。OCR方案的做法是识别出屏幕上所有的文字和位置脚本拿到“登录”两个字出现在坐标(500, 1200)就点过去。听起来好像也行但它有几个很蠢的缺陷。一是它只认识文字不认识图形。图标按钮、滑动手势、轮播卡片、纯图形的返回箭头它全都识别不了。二是因为它依赖文字到坐标的映射一旦界面改成图标加文字、或者动态布局把按钮挪了位置映射表就废了。三是OCR本身没有“理解”它不会因为“页面底部通常有主操作按钮”而猜到那个绿色圆角矩形就是你要点的确认键。UI-TARS是把整张截图作为一个整体喂给视觉语言模型它识别的不是孤立的文字而是画面里的语义结构哪些组件是可点的、弹窗和底层页面之间的层级关系、那个被红色遮罩盖住的页面意味着什么。一个很直观的差别是OCR方案只会告诉你“这里有文字‘跳过’”而UI-TARS会在推理过程里写“当前弹出广告遮罩右上角有关闭按钮点击关闭广告后再执行登录”。前者是识别后者是理解这中间差了整整一个层级。2.2 感知、推理、记忆、行动四件事把UI-TARS的完整工作逻辑拆到最底层其实就是四件事感知、推理、记忆、行动。感知阶段模型接收当前屏幕截图把视觉信息压缩成高维特征。推理阶段模型结合用户的任务描述思考当前界面处于什么状态、离目标还差几步并输出一段可读的分析文本比如“当前处于注册页需要先填写手机号手机号输入框位于上部”。记忆阶段模型会参考过去几轮交互的历史状态和它自己的动作结果避免每一帧都从零开始理解。行动阶段模型输出动作类型和坐标比如点击(320, 560)或者滑动(200, 800, 200, 400)。我之前看很多智能体项目的实现会把感知和行动拆成两个模型一个负责看懂图片一个负责决策。UI-TARS的做法是端到端一个模型全干了。这样做的优势很直接感知和理解的动作共享同一套参数模型在看到画面特征的同时就知道这类画面下一步通常应该怎么操作推理损耗小、一致性强。2.3 对Android系统层的对接方式模型本身跑在什么平台上不关键关键是它怎么跟Android设备通信。通常的链路是测试执行器通过adb从设备拉取当前屏幕截图传到模型服务端做推理拿到动作坐标之后再通过adb把点击、滑动、输入事件注入回设备。整个过程不需要在手机上装任何Agent不需要改App源码更不需要root。哪怕那台设备是专用测试机用usb线连着电脑就够了。一个小细节值得注意模型输出的坐标是相对于“它看到的那张截图”的如果截图在送进模型前做过缩放那么坐标必须做对应的反向映射。所以我习惯固定截图的短边分辨率比如统一缩放到768像素再送进模型这样既能保证模型识别效果又能简化坐标换算逻辑。真机分辨率从1080p到2K都有统一输入尺度能省掉很多莫名其妙的坐标偏差调试。3. 搭一套能跑的UI-TARS Android测试环境3.1 先决定用哪种部署方式UI-TARS的环境搭建第一步不是装依赖而是想清楚你要用哪种方式接入。我把自己试过的方案整理成了一张表方便你按团队情况来选择。部署方式适合场景硬件要求复杂度官方桌面客户端快速体验、个人探索测试消费级显卡即可最低下载即用本地模型服务二次开发、接入自研测试框架需要独立GPU显存16GB起步中等云端API调用团队共享、CI流水线集成无本地硬件要求低但要考虑网络延迟如果你只是想验证一下“视觉驱动自动化”到底靠不靠谱我建议直接用官方桌面客户端连上一台Android设备花十分钟跑一个任务试试。但如果你跟我一样想把UI-TARS接进自己的测试框架做成脚本可控的自动化能力那还是走本地模型服务更灵活。下面部分我以本地模型服务为例一步一步说清楚怎么搭。3.2 模型、显卡与依赖清单先说硬件底线。官方提供的模型权重里有几个不同尺寸的版本可以选。我实际测试下来如果是跑常规手机页面的操作任务中尺寸的量化版模型就已经能胜任大部分场景显存占用在16GB左右推理速度也能接受。如果你手头只有一台Mac或者轻薄本就不要指望本地推理了老老实实走云端API反而省心。依赖方面核心是这几样PyTorch或对应的推理框架、Transformers库、一个模型服务组件以及adb工具链。我的建议是直接用vLLM这类高性能推理框架来起服务它对于视觉语言模型的推理加速效果明显而且提供了OpenAI兼容的HTTP接口后续用Python请求非常方便。装好之后把模型权重拉下来然后启动一个服务端进程对外监听一个本地端口整个模型端的准备工作就算完成了。提示如果手头显存比较紧张优先选择量化版本或者把输入截图的分辨率上限调低一些基本都能跑起来。不要一上来就追求最大最全的模型参数版本。3.3 把Android设备接入这个体系模型服务起来之后另一头就是Android设备。工欲善其事必先利其器我踩过的第一个坑就是设备连接。开发机上得先装好platform-tools用usb线连上手机之后执行adb devices确认设备状态是device而不是unauthorized。如果你用的是无线连接先usb连一次执行adb tcpip 5555授权之后就可以拔线通过adb connect来连接了这对测试机集中管理很有用。连上设备之后我一般会先做两件事。第一确认屏幕分辨率执行adb shell wm size把宽高记录下来后面做坐标映射要用。第二关掉系统里可能干扰操作的弹窗比如权限申请、系统更新提示、省电模式弹窗这些在自动化过程中随时可能跳出来霸占屏幕。我见过一次测试跑到一半一个“系统空间不足”的弹窗让整个任务链全部走偏模型对着弹窗愣了三个回合。对于测试专用设备建议在系统设置里把这些通知都关掉或者用命令行的方式统一禁用。4. 把测试用例交给它从自然语言到自动执行4.1 用例设计的思路变化用UI-TARS做测试首先要接受一个观点用例的形态变了。传统用例长这样点击输入框A输入用户名点击输入框B输入密码点击登录按钮断言首页出现用户名。每一步都是精确到控件的事件序列。UI-TARS的用例长这样任务描述——“打开App用用户名test和密码123456登录登录成功后确认首页显示用户名”。它不是一个步骤清单而是一个目标描述。模型自己负责把目标拆解成可执行的步骤并且根据中途遇到的各种状况动态调整。这个变化对测试人员来说其实是一个思维转变。你不再是那个写死每一步的人而是变成一个“定义验收标准的人”。我给团队定的一个简单写法是任务描述里必须包含三要素——起点状态、要执行的业务动作、成功的判定依据。比如“当前在登录页输入用户名和密码后点击登录最终首页出现欢迎语”就是一个合格的描述。如果缺少“成功判定依据”模型执行完了你不知道它到底成功没有整个自动化就跑了个寂寞。4.2 Python封装一个测试执行器实际编码层面我写了一个精简的测试执行器核心就一个循环截图、推理、执行、判断终止。伪代码如下你可以直接作为骨架来改import base64 import subprocess import time import requests MODEL_URL http://localhost:8000/v1/chat/completions def take_screenshot(device_id, path/tmp/screen.png): subprocess.run([adb, -s, device_id, exec-out, screencap, -p], stdoutopen(path, wb)) with open(path, rb) as f: return base64.b64encode(f.read()).decode() def push_action(device_id, action): act_type action.get(act_type) if act_type click: x, y int(action[x]), int(action[y]) subprocess.run([adb, -s, device_id, shell, input, tap, str(x), str(y)]) elif act_type swipe: subprocess.run([adb, -s, device_id, shell, input, swipe, str(action[x1]), str(action[y1]), str(action[x2]), str(action[y2]), str(action.get(duration, 300))]) elif act_type input: text action[text].replace( , %s) subprocess.run([adb, -s, device_id, shell, input, text, text]) elif act_type back: subprocess.run([adb, -s, device_id, shell, input, keyevent, KEYCODE_BACK]) elif act_type finish: return True return False def run_task(device_id, task_desc, max_steps20): history [] for step in range(max_steps): image_b64 take_screenshot(device_id) messages [ {role: system, content: 你是测试执行智能体根据任务描述操作当前屏幕}, {role: user, content: task_desc} ] for h in history[-6:]: messages.append({role: assistant, content: h[reasoning]}) messages.append({role: user, content: h[observation]}) payload { model: ui-tars, messages: messages, max_tokens: 1024, temperature: 0.1 } resp requests.post(MODEL_URL, jsonpayload).json() content resp[choices][0][message][content] action parse_model_output(content) # 从模型输出中解析动作结构 history.append({reasoning: content, observation: f执行动作{action}}) print(f[step {step1}] {content}) if push_action(device_id, action): return True time.sleep(1) return False代码里最关键的是parse_model_output这个函数。模型输出的是一段带结构的文本你需要从里面解析出动作类型和坐标。不同版本模型给出的输出格式会有差异我一般是先把原始输出打印出来看一眼再写对应的解析表达式。这块是整条链路里最需要个性化调整的地方不要指望开箱即用。4.3 实操跑通一个Android登录页面的回归理论讲完给你看一个我这边的真实操作场景。模拟项目X是一个内部业务App登录页在V2版本里改过一次版原本横向排列的“手机号”和“密码”输入框改成了纵向卡片式布局登录按钮也换了个位置。传统脚本那套基于固定坐标的用例当场就废了。我在UI-TARS上定义一个任务描述“启动某App在手机号输入框输入13800000000在密码框输入abc123点击登录最终首页显示用户昵称‘测试账号A’。”跑的时候模型先定位到输入框点击唤起焦点然后我用input命令注入文本。中间它遇到过一次系统弹出的安全键盘选项模型没有慌——它的推理文本里有一句“检测到输入法切换提示点击右上角的完成按钮关闭”然后继续操作。最终登录成功首页昵称匹配上了。整个过程用了不到十步没有写任何定位器没有改过一行用例而开发那边V3版本又改了登录页的布局这条用例依然原样跑通。这个“原样跑通”在传统框架里几乎是不可能的体验也是我认这套方案的根本原因。4.4 多个用例的批量和结果管理单条用例跑通只是第一步真正要做成能用的测试工具必须有批量执行和结果收集能力。我的做法是把每条用例定义成一个字典包含任务描述、期望结果和超时步数然后循环调用run_task。批量执行的时候有个坑必须提醒你每一条用例之间App要处于一个确定的起点状态。如果上一条用例跑完留在了某个二级页面下一条用例就会迷失。我在执行器里固定了“每条用例开始前先杀掉App进程再冷启动”的逻辑这样虽然效率低一点但能保证起点可控。批跑完之后我再统一拉取每一条用例的最终截图人工过一眼或者用图像比对判断是否符合预期。最终产物是一个简单的结果列表包含通过、失败和超时三种状态。注意UI-TARS可以很聪明地处理各种动态页面但“聪明”的前提是它能看到一个干净的起点界面。测试用例之间不隔离再聪明的模型也会被上一条用例的残留状态搞糊涂。5. 常见问题与排查实录5.1 高频问题速查表把这段时间遇上的问题汇总一下做个速查表按出现频率排序。这可能是全文里最有保存价值的部分。问题现象常见原因解决方法模型总是点偏差那么几十像素截图缩放后坐标没做反向映射记录缩放比例动作坐标先乘回原始分辨率再注入输入中文变成乱码或失败adb input text不支持中文用剪贴板方式注入或改用adb shell ime相关方案任务执行到一半停住不动出现预料外的弹窗或页面加载过长给执行器加超时重启逻辑或任务描述里加“遇到弹窗先关闭”执行速度太慢一步要好几分钟模型尺寸过大、输入分辨率过高换量化小模型、降低截图输入分辨率、控制推理并发模型连续执行相同动作形成死循环页面无响应动作生效但没有界面变化加入动作去重连续N次相同坐标点击则终止并报失败设备连接不稳定截图会偶尔黑屏usb连接松动或设备休眠检查线缆设置设备永不休眠或改用无线连接前两个是坐标问题里最常见的多说两句。我刚接UI-TARS那会儿模型输出坐标始终跟实际点按位置差一截排查了很久才发现是截图在预处理时被缩放到了模型要求的尺寸而注入动作的时候直接用了缩放后的坐标。解决方式也不复杂把截图缩放前的宽高和缩放后的宽高比例存下来动作坐标按比例乘回去就行。5.2 坐标偏移与多分辨率问题怎么根治坐标问题看似简单实际上在批量跑多台设备的时候会变成灾难。你手上有三台测试机分辨率分别是1080p、1440p和一个带鱼屏比例的奇葩分辨率同一份任务描述跑出来的坐标在这台设备上是对的换一台就偏了。我的处理思路是模型输入统一以短边为准缩放后返回的坐标是以缩放后的图为准的所以我往外层封装一个ScreenMapper它会根据设备原始分辨率和实际送入模型的尺寸把坐标做一次线性映射。以一台2440×1080的机器为例短边是1080模型输入要求短边768那么缩放因子就是768/1080≈0.711动作坐标x乘回去就是2440/1080y直接乘1/0.711。定义好这个映射关系后面不管接多少台新设备只要注册一下分辨率就行不用改模型调用逻辑。另外一个隐蔽的坑是状态栏高度。默认的screencap截出来的是包含系统状态栏的完整屏幕而某些应用页面出于沉浸式设计的考虑内容会延伸到状态栏后面。模型理解的“屏幕”和App内容区域之间可能存在偏差。我的做法是在任务描述里明确“忽略状态栏区域”或者用wm overscan之类的命令把测试机的状态栏隐藏掉尽量让模型看到的是一个干净的App内容区。5.3 执行速度的优化经验速度是视觉自动化方案绕不开的软肋。传统框架点击一次控件只要几十毫秒UI-TARS这边光是一轮推理就要一两秒做完一个十步的任务通常要半分钟到一分钟。如果你幻想用它来代替全量回归目前阶段还不现实但如果是冒烟测试和探索性测试这个速度完全能接受。我自己在速度优化上做了三件事。第一降低无效的截图频率。不是每一个动作都需要重新截图推理比如连续输入文本时输入完再统一截图判断下一下动作比每输入一个字符截一次图快得多。第二用流式输出来解析动作模型输出“思考过程”和“动作结果”是可以边生成边解析的不用等它把整段文本吐完。第三开启动画时长缩放通过adb shell settings put global window_animation_scale 0之类的命令关闭过渡动画既省时间又减少动画中间帧对模型的干扰。还有一个心得是任务描述越具体执行速度越快。模型不需要花额外的时间猜测“登录”到底是什么意思你直接告诉它按钮在哪、账号密码分别是什么、成功长什么样它就能直接输出动作。模糊的任务描述会让模型多好几轮“打开页面看看”的试探行为。6. 到底哪些场景适合用它6.1 适合与不适合的场景用了一段时间之后我对这套方案的边界有了比较清晰的认识它不是一个要取代Appium或者Espresso的东西而是补充了现有工具无能为力的那部分场景。我按自己的经验总结成一张适用性表场景适配度原因冒烟测试、主流程巡检高用例量少、路径固定自然语言描述一次到位页面结构经常变化的项目高视觉驱动不受控件树改动影响H5、WebView、混合页面高这些页面控件树残缺但截图永远是完整的多语言、多分辨率兼容性验证高一次任务描述多台设备执行模型自动适应精确到毫秒的性能测试低AI推理的随机性和速度限制不适合白盒逻辑、接口层测试低那是另一类工具的地盘全量回归、上万条用例的CI低速度和稳定性目前撑不住这个量级最终我推荐的位置是**把UI-TARS放在“探索测试”和“冒烟巡检”这两块作为现有自动化框架的补充。**它跟你现有的Appium用例不冲突反而能帮你覆盖那些Appium搞不定的页面和无人维护的角落。6.2 与现有框架结合的一种落地方式具体怎么结合我这边有一个已经跑通的组合方案。传统框架继续负责那些页面结构稳定的核心功能模块保证它在CI里稳定执行。UI-TARS则负责两个事情一是每周跑一遍全部主要页面的遍历探索把异常截图和卡死状态记录下来二是每次发版前先跑一遍核心主流程的冒烟用自然语言描述的任务来快速发现“哪个页面崩了”。前者是广度覆盖后者是速度反馈。两个体系跑出来的结果都汇总到同一个报告平台里。这样既不推翻现有投入又能吃到视觉驱动带来的红利。如果你所在的团队正被控件树问题折磨我的建议很简单先挑一条最脆弱的用例用UI-TARS重写一遍看看效果再决定要不要扩大投入。最后说一点个人体会。跑了这么多轮我认为UI-TARS接过的不只是“写定位器”的活儿而是把测试设计从“操作步骤”提升到了“业务意图”的层面。后面你再写用例的时候会发现脑子里的视角变了——你不是在思考这个按钮怎么点而是在思考这个功能到底该验证什么。这个转变是它带给我最大的收获。