资讯动态

移动端自动化测试:角色级模型拆分实战与踩坑总结

发布时间:2026/10/20 14:28:13 来源:尧图企业网站定制
移动端自动化测试这块我前前后后折腾了快三年从最早的 Appium 脚本堆到后来的图像识别方案踩过的坑能写满一个笔记本。去年开始把大模型接进测试链路之后有个问题一直卡着我单模型跑全流程准确率上不去成本还下不来。直到我把模型按角色拆开整个链路才真正跑通。这篇就聊聊 DroidRun/Mobilerun 这套方案里角色级模型拆分到底怎么做的为什么这么拆以及拆完之后哪些地方会踩坑。如果你正在做移动端 UI 自动化或者已经把 LLM 接进了测试流程但效果不理想这篇内容应该能帮你省掉不少试错时间。我会从整体设计思路讲到具体实现再到问题排查尽量把每个决策背后的逻辑说清楚让你看完能直接照着搭一套。1. 为什么单模型跑移动端自动化会翻车1.1 移动端场景的特殊性在哪移动端自动化和 Web 端最大的区别在于状态空间是爆炸的。Web 页面再复杂DOM 结构相对稳定元素定位有 id、class、xpath 这些锚点。移动端不一样同一个按钮在不同机型上可能叫不同的 accessibility label弹窗出现时机受网络、权限、系统版本影响页面层级还经常被原生控件和 WebView 混着用。我拿一个真实的登录流程举例。打开 App等启动页消失检测是否已有登录态没有就点登录按钮输入账号密码处理可能弹出的权限请求点击提交等待首页加载。这一串操作里光是等待启动页消失这一步不同机型上耗时能从 1.5 秒到 8 秒不等。如果用单模型来做决策它得同时理解当前屏幕状态、判断下一步动作、生成操作指令还要处理异常分支。一个模型扛这么多职责注意力被稀释准确率自然上不去。更麻烦的是上下文长度。移动端每一步操作都要把当前屏幕的 UI 树或者截图喂给模型一个完整流程下来几十步操作上下文轻松超过模型的窗口限制。你可能会说用滑动窗口截断但截断之后模型就丢了历史状态容易重复操作或者漏掉关键分支。1.2 单模型方案的三个硬伤我把单模型方案的问题总结成三条都是实测出来的。第一职责耦合导致 prompt 臃肿。你要让一个模型既做屏幕理解又做动作决策还得生成具体的点击坐标或者元素选择器prompt 里得塞进 UI 树格式说明、动作空间定义、历史操作记录、异常处理规则。我最早那版 prompt 有 2000 多 token模型经常顾此失彼理解了屏幕却选错动作。第二错误会累积放大。单模型方案里如果某一步屏幕理解错了比如把取消按钮识别成了确认后续所有操作都建立在错误状态上。而且模型自己很难发现这个错误因为它没有独立的校验环节。第三成本和延迟不可控。用一个参数量大的模型跑全流程每步都要调用token 消耗飞快。我算过一笔账一个 20 步的测试用例单模型方案下平均消耗 15000 token 左右按当时的 API 价格跑一千次就是一笔不小的开销。延迟也高每步响应 3 到 5 秒一个用例跑完要一分多钟。1.3 角色拆分的核心思路角色级模型拆分的本质是把一个大而全的任务拆成几个职责单一的子任务每个子任务用最适合它的模型来做。这跟软件工程里的单一职责原则是一个道理只不过对象从函数变成了模型。在 DroidRun/Mobilerun 这套方案里我把角色拆成了四类感知角色负责理解当前屏幕决策角色负责判断下一步做什么执行角色负责把决策转成具体操作校验角色负责确认操作结果是否符合预期。每个角色用不同的模型、不同的 prompt、不同的上下文策略。这么拆的好处很直接。感知角色只需要关注屏幕内容prompt 可以精简到 500 token 以内决策角色拿到的是结构化的屏幕描述不用处理原始 UI 树执行角色做的是确定性的坐标计算或者元素匹配甚至可以用小模型或者规则引擎校验角色独立运行能及时发现前面环节的错误。提示角色拆分不是拆得越细越好。我试过拆成六个角色结果角色间通信开销反而成了瓶颈。四个角色是我实测下来比较平衡的点既能隔离职责又不会让链路太复杂。2. 四个角色的职责边界与模型选型2.1 感知角色把屏幕变成结构化描述感知角色的输入是原始屏幕数据可能是 UI 树 XML也可能是截图输出是一段结构化的自然语言描述比如当前页面是登录页包含账号输入框、密码输入框、登录按钮登录按钮处于可点击状态。这个角色的核心挑战是信息压缩。UI 树动辄几千个节点直接喂给决策模型不现实。感知角色要做的是提取关键元素过滤掉装饰性节点把层级结构扁平化成列表。模型选型上我用的是中等参数量的多模态模型。为什么不用最大的因为感知任务相对标准化不需要太强的推理能力中等模型足够而且速度快、成本低。实测下来感知角色用 7B 到 13B 级别的模型配合精心设计的 prompt准确率能到 92% 以上再往上加参数量提升不到 3 个百分点性价比不划算。prompt 设计上有个技巧给模型一个固定的输出模板。我定义的模板是这样的页面类型[登录页/首页/列表页/详情页/弹窗] 关键元素 - [元素类型] [元素文本] [元素状态] [位置描述] 可操作项[可点击/可输入的元素列表] 异常提示[是否有弹窗、错误提示、加载状态]固定模板的好处是决策角色拿到的输入格式统一不用做额外的解析。而且模板本身约束了模型的输出空间减少了幻觉。2.2 决策角色基于状态选动作决策角色拿到感知角色的输出结合历史操作记录判断下一步该做什么。它的输出是一个动作指令比如点击登录按钮或者在账号输入框输入 testexample.com。这个角色是整条链路里最需要推理能力的所以我用的是参数量最大的模型。决策质量直接决定测试能不能跑通这里不能省。决策角色的 prompt 里要包含几样东西当前页面描述、历史操作序列、当前测试目标、可用动作空间。历史操作序列不能太长我一般保留最近 5 步再早的用一句话摘要代替比如已完成登录信息填写。动作空间的定义很关键。我定义的动作类型有点击、输入、滑动、等待、返回、断言。每种动作有固定的参数格式。决策角色只能从这个集合里选不能自由发挥。这样做的原因是自由生成的动作指令执行角色没法可靠地解析而且容易产生无效操作。有个细节值得说决策角色要能输出等待动作。移动端很多场景需要等加载如果模型不会等待就会在页面还没渲染完的时候瞎点。我在 prompt 里明确告诉模型如果检测到加载状态优先输出等待动作并指定等待时长。2.3 执行角色把指令变成操作执行角色负责把决策角色的抽象指令转成具体的设备操作。比如点击登录按钮要变成在坐标 (540, 1200) 执行 tap或者通过 accessibility id 定位元素并点击。这个角色其实不一定需要大模型。如果感知角色已经输出了元素的位置信息执行角色用规则引擎就能完成。我现在的方案里执行角色优先用元素定位定位失败才回退到坐标点击坐标从感知角色输出的位置描述里解析。为什么执行角色要独立出来因为执行层有很多设备相关的细节比如不同平台的坐标系统不一样iOS 的逻辑坐标和 Android 的物理坐标需要转换输入法弹出会遮挡元素需要先收起键盘。这些细节如果混在决策角色的 prompt 里会严重干扰决策质量。独立出来之后执行角色可以用代码实现这些逻辑稳定可靠。2.4 校验角色独立确认结果校验角色是四个角色里最容易被忽略但最重要的一个。它的职责是执行完一个动作后重新感知屏幕判断操作结果是否符合预期。举个例子决策角色说点击登录按钮执行角色点了校验角色要判断页面是否跳转了是否出现了错误提示是否还在登录页如果还在登录页且没有错误提示可能是点击没生效需要重试。校验角色我用的是和感知角色同级别的模型因为它的核心任务也是屏幕理解只是多了一个对比逻辑。prompt 里我会给它预期结果和实际屏幕描述让它输出符合预期、不符合预期但可恢复、不符合预期且需终止三种结论。独立校验的价值在于打断错误累积。单模型方案里错误会一路传下去校验角色能在每一步之后做一次检查及时发现问题。我实测下来加了校验角色之后测试用例的通过率从 68% 提升到了 89%。3. 角色间通信与上下文管理3.1 角色间的数据格式约定四个角色之间要通信就得有统一的数据格式。我用的是 JSON因为结构清晰解析方便而且模型生成 JSON 的准确率比生成其他格式高。感知角色的输出格式{ page_type: login, elements: [ {type: input, text: 账号, state: editable, position: center-top}, {type: input, text: 密码, state: editable, position: center-middle}, {type: button, text: 登录, state: clickable, position: center-bottom} ], abnormal: null }决策角色的输出格式{ action: click, target: 登录按钮, reason: 当前在登录页账号密码已填写下一步应提交, expected_result: 跳转到首页 }执行角色的输出格式{ status: success, method: accessibility_id, detail: clicked element with id btn_login }校验角色的输出格式{ result: match, confidence: 0.95, note: 页面已跳转至首页符合预期 }这套格式我迭代了七八版才稳定下来。早期版本里元素位置用的是绝对坐标结果换机型就失效了。后来改成相对位置描述由执行角色根据当前屏幕尺寸换算兼容性好很多。3.2 上下文窗口的分配策略四个角色各自维护自己的上下文不共享。这是角色拆分的一个关键设计。感知角色每次只处理当前屏幕不需要历史上下文窗口占用很小。决策角色需要历史操作记录但只保留最近 5 步加一个摘要。执行角色基本不需要上下文它只处理当前指令。校验角色需要预期结果和当前屏幕也不需要长历史。这样分配下来每个角色的上下文窗口占用都很低不会出现截断问题。而且因为不共享上下文一个角色的上下文污染不会影响其他角色。注意决策角色的历史记录摘要很重要。我一开始不做摘要直接把所有历史操作塞进去结果 10 步之后上下文就爆了。后来改成保留最近 5 步详细记录更早的用一句话概括比如已完成登录流程上下文占用降了 60%决策质量没下降。3.3 角色调用的编排逻辑四个角色不是简单的串行调用中间有分支和循环。我用一个编排器来管理整个流程伪代码大概是这样def run_test_case(goal, max_steps30): history [] for step in range(max_steps): screen capture_screen() perception perception_role(screen) if check_goal_reached(perception, goal): return success decision decision_role(perception, history, goal) if decision.action wait: sleep(decision.duration) continue execution execution_role(decision) if execution.status ! success: handle_execution_failure(execution) continue new_screen capture_screen() verification verification_role(decision.expected_result, new_screen) if verification.result match: history.append(summarize(decision, execution)) elif verification.result recoverable: history.append(f操作未达预期已重试) else: return failed return timeout这个编排逻辑里有几个设计点值得说。目标检测放在决策之前每步都检查是否已经达成目标避免多余操作。等待动作单独处理不进入执行和校验环节。执行失败和校验失败分开处理执行失败通常是技术问题重试即可校验失败可能是决策错误需要记录并调整策略。4. 实操搭建从零跑通一条测试用例4.1 环境准备与依赖安装先说环境。我用的是一台 Linux 开发机装了 Android SDK 和 adbiOS 那边用了一台 Mac mini 做真机测试。Python 环境是 3.10主要依赖有pip install openai uiautomator2 pillow numpyuiautomator2用来抓 Android 的 UI 树和执行操作iOS 那边用的是facebook-wda。图像处理用pillow主要是截图裁剪和压缩。模型调用这块我用的是兼容 OpenAI 接口的 SDK方便切换不同厂商的模型。感知和校验角色用中等模型决策角色用大模型执行角色不用模型。配置上有个坑要注意adb 的默认超时时间太短抓 UI 树经常超时。我在~/.android/avd的配置里把adb timeout调到了 30 秒稳定很多。4.2 感知角色的实现细节感知角色的核心是把 UI 树转成结构化描述。Android 的 UI 树是 XML节点属性有text、resource-id、class、clickable、bounds等。我写了个解析器提取关键节点过滤掉bounds面积小于屏幕 1% 的装饰性节点。def parse_ui_tree(xml_str, screen_size): tree ET.fromstring(xml_str) elements [] for node in tree.iter(): bounds parse_bounds(node.get(bounds)) area_ratio (bounds[2]-bounds[0]) * (bounds[3]-bounds[1]) / (screen_size[0]*screen_size[1]) if area_ratio 0.01: continue if not node.get(text) and not node.get(resource-id): continue elements.append({ type: classify_element(node), text: node.get(text, ), id: node.get(resource-id, ), clickable: node.get(clickable) true, bounds: bounds }) return elements解析完之后把元素列表和截图一起喂给感知模型。为什么要给截图因为有些元素 UI 树里没有比如 WebView 里的内容或者游戏画面。多模态模型能同时利用两种信息准确率更高。感知角色的 prompt 我调了很多版最后稳定下来的版本大概 400 token。关键是指令要明确告诉模型输出 JSON字段有哪些每个字段的取值范围。模型对格式约束的遵循度比自由发挥高得多。4.3 决策角色的 prompt 工程决策角色的 prompt 是整条链路里最复杂的我拆成了四个部分角色定义、当前状态、历史记录、动作空间。角色定义部分告诉模型它是移动端测试的决策者目标是什么。当前状态就是感知角色的输出。历史记录是最近 5 步的操作摘要。动作空间定义了可选动作和参数格式。有个技巧很管用在 prompt 里给几个 few-shot 示例。我放了三个示例分别对应正常操作、需要等待、遇到弹窗三种场景。加了示例之后决策准确率提升了大概 15 个百分点。另一个技巧是让模型输出推理过程。我在 prompt 里要求模型先输出reason字段再输出action。这样模型会先想清楚为什么这么做再给动作比直接输出动作准确率高。虽然多消耗一些 token但值得。4.4 执行角色的坐标换算执行角色看起来简单其实坐标换算有不少门道。Android 的 UI 树里bounds是物理像素但adb tap用的也是物理像素所以直接用就行。iOS 不一样wda的 UI 树里bounds是逻辑坐标但点击用的是逻辑坐标也不用换算。真正需要换算的是截图坐标和 UI 树坐标不一致的情况。我遇到过一个问题截图是 1080x1920但 UI 树里的 bounds 是 720x1280因为设备设置了显示缩放。这时候如果直接用截图里的坐标去点击就会点偏。解决办法是记录截图和 UI 树的缩放比例执行点击时统一换算到 UI 树坐标系。def convert_coordinate(x, y, screenshot_size, ui_tree_size): scale_x ui_tree_size[0] / screenshot_size[0] scale_y ui_tree_size[1] / screenshot_size[1] return int(x * scale_x), int(y * scale_y)这个坑我踩了整整两天一开始以为是模型识别错了元素后来才发现是坐标系不一致。4.5 校验角色的对比逻辑校验角色的输入是预期结果和实际屏幕描述。预期结果是决策角色输出的比如跳转到首页。实际屏幕描述是重新感知得到的。对比逻辑我分了三层。第一层是页面类型对比如果预期是首页实际感知出来也是首页直接判定符合。第二层是关键元素对比如果页面类型对不上但预期结果里提到的关键元素出现了也算符合。第三层是异常检测如果屏幕上有错误提示、弹窗、加载失败等异常判定为不符合。三层对比的优先级是从严到宽。第一层最严格第二层放宽第三层是兜底。这样设计的原因是移动端页面变化多严格对比容易误判但完全宽松又会漏掉错误。提示校验角色的 confidence 字段很有用。当 confidence 低于 0.7 时我会让编排器多抓一次屏幕重新校验避免因为截图时机不对导致的误判。5. 实测数据与效果对比5.1 单模型 vs 角色拆分的准确率对比我拿同一批测试用例分别用单模型方案和角色拆分方案跑了一遍。测试用例覆盖登录、搜索、下单、支付四个流程每个流程 25 条用例总共 100 条。方案通过率平均步数平均耗时平均 token 消耗单模型68%18.372s15200角色拆分89%14.748s9800通过率提升 21 个百分点耗时降低 33%token 消耗降低 35%。这个结果比我预期的好。通过率提升主要来自校验角色的纠错耗时降低是因为感知和执行角色用了更快的模型token 降低是因为每个角色的 prompt 都精简了。5.2 不同模型组合的效果角色拆分的一个好处是每个角色可以独立选模型。我试了几种组合结果如下感知模型决策模型校验模型通过率平均耗时中等大中等89%48s中等大大91%62s大大大92%85s小大中等81%38s可以看到决策模型用大模型是必须的换成小模型通过率掉 8 个百分点。感知和校验用中等模型足够换成大模型提升有限但耗时增加明显。最下面那行是小模型做感知通过率掉到 81%说明感知角色也不能太弱。我最终选的组合是中等感知 大决策 中等校验性价比最高。5.3 角色拆分的边界在哪角色拆分不是万能的。我试过拆成六个角色增加了规划角色和异常处理角色结果通过率反而降到了 84%。原因是角色间通信开销增加而且规划角色和决策角色的职责有重叠经常互相矛盾。四个角色是我实测下来的平衡点。再少职责耦合再多通信开销大。当然这个数字跟具体场景有关如果你的测试流程特别复杂可能需要五个角色但我不建议超过六个。另一个边界是简单场景不需要拆分。如果测试用例只有三四步比如打开页面检查一个元素存在单模型方案更快更简单。角色拆分的价值在复杂流程里才体现得出来。6. 常见问题与排查技巧6.1 感知角色识别错误怎么排查感知角色出错的表现是输出的页面描述和实际屏幕不符。比如实际是登录页它说是首页。排查步骤我总结成三步。第一步检查输入数据。把 UI 树 XML 和截图单独拿出来看确认数据本身没问题。我遇到过 adb 抓取时机不对抓到的是上一个页面的 UI 树这种就是输入数据的问题。第二步检查 prompt。把感知角色的完整 prompt 打印出来看看有没有格式错误或者指令歧义。有一次我改 prompt 的时候漏了个引号模型理解错了输出格式导致解析失败。第三步单独测试模型。把输入数据单独喂给模型看输出是什么。如果单独测试正确但集成到链路里出错那就是数据传递的问题。6.2 决策角色选错动作怎么优化决策角色选错动作是最常见的问题。表现是当前应该点登录它却去点注册。优化方向有三个。一是补充 few-shot 示例。把出错的场景做成示例加进 prompt让模型学会这种情况该怎么处理。我每次遇到新的错误模式都会加一个示例prompt 慢慢就完善了。二是调整历史记录的粒度。有时候模型选错动作是因为历史记录里缺少关键信息。比如它不知道账号已经填过了就重复填。我在历史记录里明确标注了账号已填写问题就解决了。三是检查动作空间定义。如果动作空间里有歧义模型会选错。比如点击和轻触如果都定义了模型可能分不清。我后来把动作空间精简到六个歧义少了很多。6.3 执行失败的重试策略执行失败的原因很多元素没找到、坐标点偏了、页面还没加载完。不同的原因要用不同的重试策略。失败原因重试策略重试次数元素未找到等待 1 秒后重新感知3坐标点击无效改用元素定位2页面未加载等待 2 秒后重试5输入法遮挡收起键盘后重试2重试策略要跟校验角色配合。执行失败后先重试重试还失败就交给校验角色判断是否需要终止。我一开始不做区分所有失败都重试三次结果有些明显不可恢复的失败也重试浪费时间。6.4 上下文超长的处理虽然角色拆分之后上下文占用降低了但决策角色的历史记录还是会随着步数增加而变长。我的处理策略是滑动窗口加摘要。保留最近 5 步的详细记录更早的合并成一句话摘要。摘要的生成不用模型用规则就行。比如连续三步都是输入操作摘要成已完成表单填写。这样上下文长度基本恒定不会随步数增长。如果测试用例特别长超过 50 步我会在中间插入一个检查点把当前状态存下来后续如果出错可以从检查点恢复不用从头跑。6.5 模型 API 调用的稳定性模型 API 调用偶尔会超时或者返回错误。我的处理是加重试和降级。重试三次每次间隔 1 秒。如果还失败降级到备用模型。备用模型可以是同级别的另一个厂商也可以是稍弱一点的模型。降级之后要记录因为降级可能影响准确率。我在日志里标记了降级的步骤事后分析的时候能看出来哪些失败是降级导致的。注意API 调用的超时时间要设置合理。太短了容易误判超时太长了卡住整个流程。我设置的是 30 秒实测下来大部分调用在 5 秒内返回30 秒足够覆盖网络波动。7. 角色拆分的扩展思路7.1 多设备并行时的角色分配单设备跑通了之后我试了多设备并行。四台设备同时跑每台设备一套角色实例。这时候有个新问题模型 API 的并发限制。我的解决办法是角色分级调度。决策角色的调用优先级最高保证及时响应感知和校验角色的调用可以排队稍微延迟不影响整体。执行角色不用模型不受影响。另外多设备并行时感知角色的截图要加设备标识避免混淆。我在每台设备上跑一个独立的 agent 进程通过消息队列和编排器通信。7.2 跨平台适配的注意事项Android 和 iOS 的 UI 树结构不一样感知角色的解析器要分开写。Android 用uiautomator2iOS 用wda。两者的元素属性名不同Android 是resource-idiOS 是name。决策角色和校验角色可以复用因为它们处理的是结构化描述不关心底层平台。执行角色要分开因为操作 API 不同。跨平台适配的工作量主要在感知和执行两个角色决策和校验基本不用改。这也是角色拆分的一个好处平台相关的逻辑被隔离在特定角色里不会污染整个链路。7.3 后续可以优化的方向现在这套方案还有几个可以优化的点。一是感知角色的缓存如果连续两步屏幕没变化可以复用上一步的感知结果省一次模型调用。二是决策角色的动作预测根据历史操作预测下一步可能做什么提前准备。三是校验角色的主动学习把误判的案例收集起来定期更新 prompt。这些优化我还在试验阶段等跑稳定了再单独写一篇分享。这套角色级模型拆分的方案我从去年年底开始搭中间推倒重来了两次现在算是比较稳定了。核心体会就一句话不要让一个模型干所有事把职责拆开每个角色用最合适的模型和 prompt。拆分的粒度要试四个角色是我的平衡点你可以根据自己的场景调整。执行角色的坐标换算和校验角色的独立对比是我踩坑最多的地方希望你看完能少走点弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑