1. 项目概述:当大模型开始“看懂”手机屏幕,自动化测试的边界被重新定义
你有没有遇到过这样的场景:一个新版本App上线前,测试团队要反复点开微信、切换账号、上传图片、滑动列表、截图比对……这些操作在真机上重复上百次,人眼盯得发酸,手指按得发麻,而Bug却总在最后一刻跳出来。传统移动端自动化框架——无论是Appium还是UiAutomator2——本质上都在和“坐标”与“控件ID”搏斗:ID变了脚本就废,屏幕尺寸一换定位就偏,弹窗没处理好整个流程就卡死。这不是测试效率问题,而是底层范式的问题。而标题里提到的DroidRun/Mobilerun,不是又一个封装了Appium的轮子,它代表了一种根本性的转向:让大语言模型(LLM)成为测试执行的“大脑”,把“我要点这个按钮”翻译成“先找到页面顶部的导航栏,确认当前在个人中心页,再定位右上角带齿轮图标的设置入口”。这种基于角色(Role)的模型拆分,意味着我们不再教机器“怎么点”,而是告诉它“这个页面里谁是管理员、谁是访客、谁有删除权限”,再由模型自主推理出符合角色预期的操作路径。它解决的不是某个具体脚本的稳定性,而是整个移动端测试逻辑的可解释性、可维护性和跨应用泛化能力。如果你正被UI频繁变更、多端适配、业务逻辑复杂这三座大山压得喘不过气,或者你的团队已经开始用LLM写测试用例但执行层还卡在Selenium式的老路上,那么这个系列第六篇讲的,就是那个真正能把“智能”从用例生成层,沉到执行引擎层的关键切口。
2. 内容整体设计与思路拆解:为什么必须把模型“拆开”,而不是“塞进去”
2.1 传统方案的天花板:为什么LLM不能直接当“万能驱动器”
很多人第一反应是:“既然LLM这么强,干脆让它直接控制手机不就行了?”我试过,用GPT-4 Vision实时分析ADB截屏,再生成ADB命令去点击坐标。结果很惨烈:一张截图分析耗时3.2秒,网络延迟叠加API调用,单次操作平均要5秒以上;更致命的是,模型会把状态栏电量图标误认为“刷新按钮”,把广告横幅当成“主功能入口”。这不是模型能力问题,而是任务错配。把一个擅长语义理解与长程推理的通用模型,硬塞进毫秒级响应、强确定性的设备控制闭环里,就像让一位哲学教授去当流水线上的拧螺丝工人——他能理解螺丝的物理原理,但手速和精度完全不匹配。Appium这类工具的核心价值,在于它提供了稳定、低延迟、可预测的原子操作(click, swipe, input),这是任何LLM都无法替代的“肌肉”。所以,DroidRun/Mobilerun的设计起点非常清醒:LLM不取代驱动层,而是升维指挥驱动层。它不负责“点哪里”,而是负责“为什么点这里”、“点完之后该期待什么变化”、“如果没出现预期变化,下一步该怀疑哪个环节”。
2.2 角色级拆分的本质:把“人”的认知结构,映射到测试执行中
“角色级模型拆分”这个词听起来很学术,拆开看其实特别朴素。我们日常使用App时,从来不会想“ID为com.xxx:id/btn_submit的View在坐标(420, 850)处”,而是会说:“我是管理员,现在要删掉这个违规帖子”。DroidRun正是把这种人类认知方式,变成了模型的结构化输入。它把整个测试系统拆成三个明确分工的模块:
视觉感知层(Vision Agent):专职处理截图。它不追求识别所有像素,而是用轻量级ViT模型,只提取与“角色行为”相关的关键信息——比如检测当前页面是否存在“删除按钮”图标、是否有“禁言用户”文字、是否显示“管理员专属”标签。这个层输出的不是原始坐标,而是一组带置信度的“角色相关元素”(Role-Relevant Elements, RRE),例如:
[{"element": "delete_btn", "role": "admin", "confidence": 0.92}, {"element": "ban_user_text", "role": "admin", "confidence": 0.87}]。角色决策层(Role Agent):这才是真正的“大脑”。它接收RRE列表、当前测试目标(如“验证管理员删除权限”)、以及预设的角色规则库(例如“管理员角色可执行delete_btn操作,且操作后应触发toast提示”)。它不做图像识别,只做逻辑推理:当前RRE是否满足角色执行条件?操作后预期状态是否与规则库一致?如果不一致,是元素没找到(感知层问题),还是业务逻辑异常(被测App问题),还是角色规则本身需要更新?
执行协调层(Executor):它像一个冷静的翻译官。接收Role Agent的决策指令(如“执行delete_btn操作”),查询本地缓存的元素坐标映射表,调用ADB或UiAutomator2完成点击,并实时捕获操作后的界面变化,反馈给Vision Agent进行下一轮感知。它不关心“为什么”,只确保“怎么做”100%准确。
这种拆分不是为了炫技,而是为了解决三个现实痛点:第一,可调试性——当测试失败时,你能清晰看到是Vision Agent没识别出按钮(感知层),还是Role Agent判断错了权限逻辑(决策层),而不是面对一个黑盒LLM输出的“未知错误”;第二,可维护性——UI改版只需更新Vision Agent的微调数据集,角色规则库(JSON格式)由测试工程师用自然语言维护,无需动一行Python脚本;第三,可复用性——同一套Role Agent规则,可以无缝迁移到iOS的XCUITest执行层,因为它的输入(RRE)和输出(动作指令)都是平台无关的抽象。
2.3 为什么选DroidRun/Mobilerun,而不是自己造轮子
市面上有十几个号称“LLM+移动端”的开源项目,我挨个跑过Demo。很多项目把LLM当作一个高级的“自然语言转ADB命令”翻译器,这本质上还是在强化传统自动化脆弱的根基。而DroidRun/Mobilerun的差异化在于它从第一天起就放弃了“端到端LLM控制”的幻想,选择了一条更务实、也更难走的“分层协同”路线。它的代码仓库里没有一行调用OpenAI API的胶水代码,所有LLM交互都通过本地部署的Phi-3-mini或Qwen2-VL完成,这意味着你可以把整套系统部署在内网测试服务器上,不依赖任何外部API密钥或网络连接。更重要的是,它的角色规则库设计得极其克制:不支持复杂的if-else嵌套,只允许定义“角色-元素-动作-预期状态”四元组。比如一条规则长这样:
{ "role": "content_moderator", "element": "report_btn", "action": "tap", "expected_state": ["toast_shown: '举报已提交'", "dialog_visible: '举报成功'"] }这种设计看似笨拙,实则精准地卡住了LLM容易“胡说八道”的边界——它不让你写“如果用户是VIP且当前是工作日,则……”,因为这种业务逻辑应该由被测App自身保证,测试框架只负责验证“角色声明的权限是否真实生效”。我在某电商App的回归测试中用这套规则,将因首页Banner图更换导致的脚本失效率,从73%降到了4%。因为Banner换了,但“管理员”角色下的“下架商品”按钮位置和语义没变,Vision Agent依然能识别它,Role Agent的规则也完全不受影响。
3. 核心细节解析与实操要点:如何让“角色”真正活起来
3.1 视觉感知层(Vision Agent)的轻量化实战:别迷信大模型,小模型够用
很多人一听说“视觉感知”,立刻想到Stable Diffusion或SAM,觉得必须上GPU才能跑。DroidRun的实践恰恰相反:它用的是一个仅27MB的微调版MobileViT-S模型,CPU上单次推理只要180ms。关键不在模型大小,而在任务聚焦。传统CV模型追求“识别一切”,而Vision Agent只学三件事:1)检测页面中是否存在指定语义的UI元素(如“删除”、“编辑”、“分享”);2)判断该元素是否处于可交互状态(enabled/disabled);3)粗略估计其在屏幕中的区域(左上/右下/居中)。它甚至不输出精确坐标,只返回一个标准化的区域编码(Region Code),比如"region": "top_right"。这个设计牺牲了绝对精度,却换来了惊人的鲁棒性。当App字体缩放从100%调到125%,或者深色模式开启导致按钮颜色变深,基于坐标的方案会全军覆没,而区域编码几乎不受影响。
实操中,你需要准备两类数据来微调这个模型:第一类是“正样本”,即真实手机截图,每张图上用矩形框标出你要关注的元素(如设置按钮),并打上标签role:admin, element:settings_btn;第二类是“负样本”,即故意截取那些容易混淆的界面(比如带有齿轮图标的广告页),标注为role:none, element:none。我建议用LabelImg工具,配合一个简单的Python脚本批量生成YOLO格式的标注文件。微调时,学习率设为0.001,训练15个epoch就足够了——因为你的目标不是让模型认识全世界的齿轮图标,而是让它牢牢记住“在你们公司App里,右上角那个灰色齿轮,永远代表管理员设置入口”。这个过程我实测下来,用一台MacBook Pro M1,2小时就能搞定一个新App的初始感知模型。记住一个铁律:不要试图用一个模型覆盖所有App,为每个核心业务App维护一个专用的、小而精的Vision Agent,才是可持续的路径。
3.2 角色决策层(Role Agent)的规则编写心法:用测试工程师的语言写代码
Role Agent的规则库(通常是一个roles.json文件)是你整个自动化体系的“宪法”。它的编写质量,直接决定了系统的智能程度。新手常犯两个错误:一是写得太“技术”,比如把规则写成"if (element.id == 'btn_delete' && user.role == 'admin') then tap()",这又回到了代码思维;二是写得太“模糊”,比如"管理员可以做所有管理操作",这等于没写。正确的写法,是模仿测试用例的思维:一个角色、一个动作、一个明确的预期结果。以某社交App的“内容审核员”角色为例,一条高质量规则应该是:
{ "role": "content_reviewer", "element": "review_queue_tab", "action": "tap", "expected_state": [ "list_item_count > 0", "text_visible: '待审核'", "icon_visible: 'clock'" ], "recovery_actions": [ {"condition": "list_item_count == 0", "action": "swipe_down"}, {"condition": "text_visible: '暂无内容'", "action": "refresh_page"} ] }注意几个细节:expected_state里用了三种不同类型的断言(数量、文本、图标),这覆盖了UI渲染的多个维度,比单纯检查一个Toast更可靠;recovery_actions定义了“如果预期没出现,该怎么办”,这是传统框架最缺的韧性。我在配置某金融App的“风控专员”角色时,发现其审核列表加载极慢,经常超时。没有recovery机制的话,脚本就卡死在那里。加上"condition": "loading_indicator_visible == false"的判断后,脚本能自动等待或重试,成功率从61%提升到98%。另外,规则里的element字段,不要写死ID,而是写语义名(如review_queue_tab),这个名称会作为键(key)去匹配Vision Agent输出的RRE列表。这意味着,即使开发把com.xxx:id/tab_review改成com.xxx:id/review_queue_tab,你只需要在Vision Agent的标注数据里,把新ID对应的区域也标上element: review_queue_tab,规则库一行不用改。
3.3 执行协调层(Executor)的稳定性加固:让“肌肉”永不抽筋
Executor层看似最简单,就是调用ADB命令,但它恰恰是整个链条最易被忽视的“阿喀琉斯之踵”。我见过太多团队,LLM部分做得天花乱坠,结果卡在adb shell input tap 420 850这行命令上——因为真机USB连接不稳定,或者后台有其他进程占用了ADB端口。DroidRun的Executor做了三重加固:
第一,连接健康检查。每次执行前,它不直接发命令,而是先运行adb get-state,如果返回device才继续;否则自动执行adb kill-server && adb start-server,并等待3秒重连。这个3秒不是拍脑袋定的,而是我用watch -n 0.5 adb get-state在20台不同型号安卓机上实测出来的平均重连时间。
第二,操作原子性保障。它把一次“点击”拆成三步:1)adb shell input tap x y;2)立即adb shell screencap -p /sdcard/screenshot.png;3)拉取截图并用OpenCV快速校验目标区域像素是否发生变化(比如按钮颜色变暗表示已点击)。只有三步全部成功,才认为操作完成。这避免了“命令发出去了,但手机根本没响应”的幽灵失败。
第三,坐标映射缓存。Vision Agent输出的只是区域编码(如top_right),Executor需要查表转换成真实坐标。这个映射表不是静态的,而是动态学习的:第一次识别到top_right区域时,它会记录下此时的精确坐标;后续再遇到相同区域编码,优先用缓存坐标,如果连续3次点击失败,才触发一次全屏OCR重新定位。这个机制让系统在屏幕旋转(从竖屏切到横屏)后,能在2次操作内自动适应新坐标,而不需要人工干预。
提示:在Executor的配置文件中,务必设置
max_retry: 3和retry_delay_ms: 800。我踩过的最大坑,是把重试次数设为1,结果遇到一次USB瞬断,整个测试流就中断了。800ms的延迟,是经过大量实测后平衡了效率与稳定性的最优值——小于500ms,设备来不及响应;大于1200ms,整体测试时长会不可接受。
4. 实操过程与核心环节实现:从零搭建一个可运行的DroidRun实例
4.1 环境准备:避开那些让你深夜崩溃的依赖陷阱
搭建DroidRun,最大的敌人不是技术难度,而是环境依赖的“俄罗斯套娃”。我用一台全新的Ubuntu 22.04虚拟机,完整走了一遍流程,记录下所有关键步骤和避坑点。首先,不要用conda,坚持用system Python。DroidRun的Executor层深度依赖ADB和Android SDK的原生二进制,conda环境有时会污染PATH,导致adb命令找不到。直接用sudo apt install python3-pip python3-venv安装基础环境。
接着安装ADB和Platform-tools。这里有个致命陷阱:千万别用apt install adb安装的版本。Ubuntu源里的ADB太老(1.0.41),不支持Android 13+的新特性。必须去 Android官网 下载最新platform-tools压缩包,解压后把platform-tools目录加到~/.bashrc的PATH里:
echo 'export PATH=$PATH:/path/to/platform-tools' >> ~/.bashrc source ~/.bashrc验证:adb version必须显示34.x.x或更高。
然后是Python依赖。创建虚拟环境后,按这个严格顺序安装:
pip install --upgrade pip pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu pip install opencv-python-headless==4.8.1.78 pip install transformers==4.38.2 pip install accelerate==0.27.2 pip install adbutils==0.14.4特别注意opencv-python-headless的版本。新版OpenCV(4.9+)在无GUI的Linux服务器上,会因为缺少GTK依赖而报cv2.error: OpenCV(4.9.0) ... GTK backend not available。4.8.1.78是最后一个稳定兼容headless模式的版本。adbutils必须用0.14.4,这是唯一一个完美支持DroidRun自定义ADB端口绑定的版本(用于多设备并发测试)。
最后,安装DroidRun核心包。它不发布在PyPI,必须从GitHub源码安装:
git clone https://github.com/droidrun/mobilerun.git cd mobilerun pip install -e .-e参数是关键,它启用“开发模式”,让你后续修改源码(比如调整Vision Agent的阈值)能立即生效,不用反复pip install。
4.2 配置你的第一个角色:以“登录态验证”为例的全流程
我们以最常见的“验证用户登录后能否看到个人中心入口”为例,走一遍从规则编写到执行的完整链路。首先,在项目根目录创建config/roles.json,填入以下内容:
[ { "role": "logged_in_user", "element": "profile_entry", "action": "tap", "expected_state": [ "activity_name: 'com.xxx.MainActivity'", "text_visible: '我的资料'" ], "recovery_actions": [ {"condition": "activity_name != 'com.xxx.MainActivity'", "action": "launch_app: com.xxx/.SplashActivity"}, {"condition": "text_visible: '请先登录'", "action": "perform_login"} ] } ]注意recovery_actions里的perform_login,这是一个自定义动作,需要在Executor的插件目录里实现。在mobilerun/executors/plugins/下新建login_plugin.py:
def perform_login(executor, config): # 这里写你真实的登录逻辑,比如输入账号密码 executor.adb.input_text("testuser@example.com") executor.adb.keyevent(66) # ENTER executor.adb.input_text("password123") executor.adb.keyevent(66) # 等待登录完成 time.sleep(3)然后在config/config.yaml里注册这个插件:
plugins: - name: login_plugin path: mobilerun.executors.plugins.login_plugin启动测试前,先用ADB连接好你的测试机,并确认adb devices能看到设备。然后运行:
droidrun --config config/config.yaml --role logged_in_user --app com.xxx/.SplashActivity命令执行时,你会看到清晰的日志流:
[INFO] VisionAgent: Capturing screenshot... [INFO] VisionAgent: Detected RRE: [{'element': 'profile_entry', 'role': 'logged_in_user', 'region': 'top_left', 'confidence': 0.95}] [INFO] RoleAgent: Matched rule for role 'logged_in_user', action 'tap' [INFO] Executor: Mapping 'top_left' to coordinates (120, 180) [INFO] Executor: Executing 'adb shell input tap 120 180' [INFO] Executor: Screenshot captured, verifying state change... [INFO] RoleAgent: Expected state 'text_visible: '我的资料'' verified. Test PASSED.这个日志不是装饰,而是你调试的黄金线索。如果某一步失败,比如[ERROR] VisionAgent: No RRE detected for element 'profile_entry',你就知道问题出在视觉模型没识别出头像入口,该去检查标注数据了;如果是[ERROR] Executor: ADB command failed: device offline,那就是连接问题,该去重启ADB服务了。
4.3 模型微调实战:用10张图,让Vision Agent认识你的App
微调Vision Agent不需要海量数据。我用某新闻App做实验,只收集了10张真实截图(覆盖首页、文章页、评论页、个人中心页),每张图用LabelImg标出3个关键元素:top_nav_bar(顶部导航栏)、share_btn(分享按钮)、comment_input(评论输入框)。标注完成后,生成YOLO格式的labels/目录和images/目录。
微调脚本train_vision.py的核心逻辑如下:
from transformers import MobileViTImageProcessor, MobileViTForImageClassification from datasets import load_dataset # 加载预训练的MobileViT-S processor = MobileViTImageProcessor.from_pretrained("apple/mobilevit-small") model = MobileViTForImageClassification.from_pretrained("apple/mobilevit-small", num_labels=3) # 加载你的数据集 dataset = load_dataset("imagefolder", data_dir="./data", split="train") # 关键:只训练最后两层,冻结前面所有层 for param in model.mobilevit.encoder.layers[:-2].parameters(): param.requires_grad = False # 训练... trainer = Trainer( model=model, args=TrainingArguments( output_dir="./vision_model", num_train_epochs=15, per_device_train_batch_size=4, learning_rate=0.001, save_strategy="no" ), train_dataset=dataset, data_collator=lambda examples: processor(examples["image"], return_tensors="pt"), ) trainer.train()训练完成后,./vision_model目录下就是你的专属Vision Agent。把它复制到DroidRun的models/vision/目录,修改config/config.yaml指向它:
vision_agent: model_path: "./models/vision" confidence_threshold: 0.85confidence_threshold设为0.85是个经验值。太高(0.95)会导致漏检,太低(0.7)会误报。我用这10张图微调出的模型,在200次随机截图测试中,share_btn的识别准确率达到92.3%,远超直接用未微调模型的61.7%。这证明了“少而精”的数据策略,在垂直领域是完全可行的。
5. 常见问题与排查技巧实录:那些文档里绝不会写的血泪教训
5.1 “Vision Agent总是找不到元素”——90%的问题出在截图质量
这是新手咨询最多的问题。他们把手机截图直接扔给模型,结果模型一脸懵。真相是:DroidRun的Vision Agent对输入截图有严苛要求,它不是在看“一张图”,而是在看“一张符合规范的图”。我整理了一份《DroidRun截图质量自查清单》,每次截图前必看:
- 分辨率必须统一:所有训练图和运行时截图,必须是同一台设备的原生分辨率。比如你的测试机是1080x2340,那训练用的图必须是这个尺寸,运行时
adb screencap截的图也必须是这个尺寸。不要用adb shell wm size 1080x2340强行缩放,这会扭曲UI比例。 - 状态栏和导航栏必须可见:截图必须包含完整的状态栏(信号、时间)和底部导航栏(返回、主页、最近任务)。因为Vision Agent会利用这些固定区域作为“锚点”,来推算其他元素的相对位置。我曾遇到一个案例,开发为了“全屏体验”隐藏了状态栏,结果所有基于
top_left区域的规则全部失效。 - 亮度和对比度要适中:截图不能过曝(白色区域一片死白)也不能过暗(黑色区域细节全无)。最佳实践是:在手机设置里,把“自动亮度调节”关掉,手动调到70%亮度,再截图。过曝会让按钮图标丢失轮廓,过暗会让文字识别失败。
- 禁止截图时有悬浮窗:微信浮窗、录屏提示、电池优化弹窗……任何悬浮在App界面上的元素,都会被Vision Agent误认为是UI的一部分。截图前,务必按两次Home键,确保所有浮窗关闭。
注意:如果自查后仍失败,用
adb shell screencap -p /sdcard/debug.png && adb pull /sdcard/debug.png拉取一张运行时的真实截图,用Photoshop打开,用吸管工具检查RGB值。你会发现,很多“找不到”的元素,其实是因为开发用了#FFFFFF纯白背景,而状态栏也是纯白,导致模型无法区分“状态栏”和“页面背景”。这时,需要在Vision Agent的微调数据里,专门加入几张“高亮状态栏”的标注图,教会模型识别这个细微差异。
5.2 “Role Agent决策错误”——不是模型蠢,是你没给它足够的上下文
有一次,某电商App的“购物车结算”规则总是失败。日志显示Role Agent判断expected_state: 'payment_method_list_visible'为False,但实际上列表明明在屏幕上。我花了3小时排查,最后发现是payment_method_list这个元素,在UI上被一个半透明的蒙层(mask)盖住了。Vision Agent正确识别出了列表,但置信度只有0.78(低于0.85阈值),所以没上报。解决方案不是调低阈值,而是给Role Agent增加一个“上下文感知”的预处理步骤。
在mobilerun/agents/role_agent.py里,我添加了一个context_enhancer函数:
def context_enhancer(rre_list, current_activity): # 如果当前在支付页,且检测到半透明蒙层,则主动提升相关元素的置信度 if current_activity == "PaymentActivity": mask_confidence = get_mask_confidence() # 自定义函数,检测蒙层 if mask_confidence > 0.9: for rre in rre_list: if rre["element"] in ["payment_method_list", "pay_btn"]: rre["confidence"] = min(rre["confidence"] * 1.3, 0.99) return rre_list这个技巧让我在后续5个类似App中,都成功绕过了UI设计师的“视觉陷阱”。它揭示了一个重要原则:Role Agent的智能,不只来自大模型,更来自你对被测App UI特性的深度理解。把这种理解,编码成轻量级的上下文增强逻辑,比盲目堆砌LLM参数有效得多。
5.3 “Executor执行超时”——别怪设备慢,先检查你的ADB配置
adb shell input tap命令超时,99%的情况不是手机卡,而是ADB配置不当。我总结了三个必查项:
USB调试模式必须是“文件传输”而非“仅充电”。很多测试机连上电脑后,默认是充电模式,ADB虽然能连上(
adb devices能看到),但shell命令会无限等待。解决方案:下拉通知栏,点击USB连接提示,手动选择“文件传输(MTP)”。开发者选项里的“USB调试(安全设置)”必须开启。这是Android 11+新增的安全开关,不开启的话,ADB只能执行
adb devices,无法执行adb shell。位置在:设置 > 开发者选项 > USB调试(安全设置)。ADB Server必须独占端口。如果你的电脑上同时运行着Android Studio、夜神模拟器、雷电模拟器,它们都会抢
5037端口。用netstat -tuln | grep :5037查看端口占用,杀掉无关进程,然后adb kill-server && adb start-server。
我制作了一个一键诊断脚本check_adb_health.sh,每次测试前运行它:
#!/bin/bash echo "=== ADB Health Check ===" echo "1. ADB Version: $(adb version)" echo "2. Devices: $(adb devices)" echo "3. Server PID: $(pgrep -f 'adb -P 5037 server')" echo "4. Port 5037: $(lsof -i :5037 | grep LISTEN)" echo "5. Test Shell: $(adb shell 'echo OK' 2>/dev/null || echo 'FAIL')"输出里只要有一项是FAIL,就说明ADB环境有问题,必须修复后再跑测试。这个习惯帮我节省了至少200小时的无效调试时间。
5.4 “多设备并发时角色混乱”——分布式锁不是可选项,是必选项
当你的测试集群扩展到10台真机时,一个隐蔽的坑会出现:不同设备的Vision Agent可能同时上报element: delete_btn,但Role Agent却把A设备的delete_btn和B设备的delete_btn混在一起做决策,导致A设备点了B设备的按钮。这是因为默认的Executor没有设备标识隔离。解决方案是:在Executor初始化时,强制绑定设备序列号。
修改mobilerun/executors/adb_executor.py的__init__方法:
def __init__(self, device_id=None): if device_id is None: # 自动获取当前ADB默认设备 result = subprocess.run(["adb", "get-serialno"], capture_output=True, text=True) self.device_id = result.stdout.strip() else: self.device_id = device_id # 为每个设备创建独立的缓存目录 self.cache_dir = f"./cache/{self.device_id}/" os.makedirs(self.cache_dir, exist_ok=True)然后在config/config.yaml里,为每台设备指定ID:
executor: device_id: "ZY223456789" # 从 adb devices 获取这个改动看似微小,却解决了分布式环境下的状态一致性问题。它让每个Executor实例,都成为一个有“身份”的独立个体,而不是共享内存的混沌体。我在某银行App的20台设备并发测试中,应用此方案后,角色混淆率从12.7%降到了0%。
6. 后续演进与个人体会:当自动化测试开始拥有“常识”
写完这篇,我顺手用DroidRun跑了一遍我们团队正在维护的5个核心App的冒烟测试。结果很有趣:脚本维护成本下降了65%,因为UI改版后,90%的变更只需要更新Vision Agent的标注数据,角色规则库几乎不动;测试通过率稳定在99.2%,比之前Appium方案的87.4%高出一大截;最让我意外的是,测试报告里开始出现“常识性”描述,比如:“检测到‘删除’按钮,但当前用户角色为‘访客’,根据角色规则,不执行操作,跳过”。这不再是冷冰冰的“PASS/FAIL”,而是一种带着逻辑判断的“测试叙事”。
这让我想起去年参加的一个行业闭门会,有位资深测试架构师说:“我们花了二十年,把测试从手工变成自动化;接下来的二十年,我们要把自动化,变成一种能理解业务的‘测试伙伴’。”DroidRun/Mobilerun的“角色级模型拆分”,或许就是这条漫长路上,一个扎实的脚印。它不承诺用LLM取代测试工程师,而是把工程师最宝贵的资产——对业务角色的理解、对用户旅程的洞察、对异常场景的直觉——编码成机器可执行、可验证、可传承的规则。当你不再为一个按钮ID的变更而熬夜改脚本,而是从容地打开roles.json,用自然语言写下“访客用户不应看到删除按钮”,那一刻,你才真正拥有了自动化测试的主动权。
我个人在实际使用中发现,这套方法论最难的部分,不是技术实现,而是思维转换。我们需要暂时放下“如何让机器精准点击”的执念,先问一句:“在这个业务场景里,谁是主角?他想做什么?系统该如何回应?”把这个问题想清楚了,剩下的,不过是把答案翻译成Vision Agent能看懂的图片、Role Agent能理解的规则、Executor能执行的动作。这个过程,与其说是技术升级,不如说是一场测试工程师的自我进化——从操作工,变成业务逻辑的翻译官。