1. 项目概述当大模型“长出”了手和眼最近在折腾一个挺有意思的东西我把它叫做“让大模型学会点鼠标”。核心就是利用ollama本地部署的QwQ-32B大语言模型去驱动一个名为OpenClaw的自动化测试框架让它不仅能“看懂”电脑屏幕上的用户界面还能“动手”去操作最后还能“判断”操作结果对不对。这听起来是不是有点像给大模型装上了机械臂和摄像头让它从纯文本对话进化到了物理世界交互其实这正是当前AI Agent智能体和自动化测试领域一个非常前沿的交叉点。传统的UI自动化测试无论是用Selenium、Playwright还是Appium核心逻辑都是我们人类程序员预先写好一套脚本点击这里输入那个然后检查某个元素是否存在或文本是否匹配。这套流程稳定但缺乏“智能”。当界面布局稍有改动或者出现预期外的弹窗时脚本很容易就“卡住”报错。而引入大模型的目的就是希望赋予自动化流程一定的理解和决策能力。OpenClaw在这里扮演的是“执行器”和“观察者”的角色它负责捕获屏幕信息、控制鼠标键盘而ollama本地运行的QwQ-32B模型则作为“大脑”负责解析屏幕信息、生成操作指令、并验证结果。这个组合非常适合测试那些流程复杂、界面多变或者需要一定认知能力才能完成的任务。比如测试一个图形设计软件里“将图片拖入画布并调整透明度”这一系列操作用传统脚本描述每一步的坐标和元素选择器会非常繁琐且脆弱但大模型可以理解“拖拽”、“找到透明度滑块”这样的自然语言指令。再比如验证一个电商应用在下单流程中是否正确计算了包含优惠券、运费在内的总价这需要模型理解页面上的数字信息并进行逻辑计算。对于测试工程师、RPA开发者和AI应用研究者来说搭建这样一套系统意味着能处理更复杂、更接近真实用户行为的测试场景也是探索AI赋能传统软件工程的一个绝佳实践。2. 核心组件选型与部署踩坑实录工欲善其事必先利其器。这套系统的核心就三个模型底座ollama、模型本身QwQ-32B、以及自动化执行框架OpenClaw。每一个的选型和部署都有讲究我也踩了不少坑。2.1 Ollama本地大模型引擎的快速部署Ollama现在几乎是本地玩转开源大模型的首选工具了。它把模型下载、加载、运行和提供API接口这些麻烦事打包成了一个简单的命令行工具特别适合快速原型开发。它的核心优势在于其统一的模型管理方式和高效的推理后端。部署与镜像加速官方的一键安装脚本curl -fsSL https://ollama.ai/install.sh | sh在海外网络下很顺畅但在国内直接下载模型动辄几十GB速度可能慢到令人绝望。这里就必须用到国内镜像源。我实践下来最稳的方法是使用代理环境变量如果具备条件在运行ollama run命令前设置HTTP_PROXY和HTTPS_PROXY环境变量。借助国内镜像站拉取有些社区提供了ollama模型的镜像。一个可行的方法是先通过其他方式如云服务器、学术资源等下载好模型的Modelfile和分片文件然后通过ollama create命令从本地文件创建。更直接的是可以寻找托管了常用模型镜像的 Docker 仓库但这对QwQ-32B这类较新或小众的模型可能不适用。注意在寻找和下载模型时务必从官方或可信度极高的社区渠道获取避免模型被恶意篡改导致后续自动化行为不可控。一个关键配置Ollama默认的API服务只监听本地回环地址127.0.0.1。为了让同一网络下的其他机器比如运行OpenClaw的测试机也能调用需要修改启动配置。对于Linux系统可以编辑systemd服务文件通常是/etc/systemd/system/ollama.service在[Service]部分的ExecStart命令后加上--host 0.0.0.0参数。重启服务后ollama就会监听所有网络接口了。务必记得配置好防火墙不要将服务暴露在公网风险极高。2.2 QwQ-32B为何选择这个“小众”模型在ollama的官方库里有Llama 3、Qwen、Gemma等一众明星模型为什么偏偏选QwQ-32B这源于我们的特定需求。首先任务需求是理解屏幕和生成精确操作。这要求模型具备强大的视觉语言理解能力虽然我们输入的是UI元素的结构化数据或截图描述而非原始像素、严谨的逻辑推理能力以及遵循指令的准确性。纯聊天模型可能擅长创造但未必能严格输出“点击(id‘submit’)按钮”这样的格式化指令。QwQ-32B是一个基于Qwen架构进行针对性调优的模型据社区反馈它在工具调用、代码生成和结构化输出方面表现突出。32B的参数规模在消费级显卡如RTX 4090 24GB上通过量化技术如q4_K_M可以勉强跑起来提供了足够强的能力又不像70B模型那样对硬件要求苛刻。相比之下一些7B或13B的模型在复杂任务链的理解上容易“力不从心”。实测心得一开始我尝试用Llama 3 8B发现它在多步指令的理解上经常出现偏差比如会把“验证登录成功”理解为“去点击登录按钮”。换成QwQ-32B后指令遵循的稳定性和步骤推理的准确性有明显提升。模型的选择没有绝对但针对“智能体控制”这类任务优先考虑在工具调用、Agent基准测试如WebArena、AgentBench中表现好的模型是更明智的。2.3 OpenClaw连接大模型与操作系统的桥梁OpenClaw是这个体系中的“手眼”。它不是一个单一的库而是一个框架或一套工具集核心目标是接收自然语言或结构化指令并将其转化为对操作系统Windows、macOS、Linux的实际输入输出操作包括截图、鼠标控制、键盘输入、OCR识别、元素查找等。它的核心工作原理可以概括为观察See通过截图或访问操作系统可访问性API如Windows上的UI Automation macOS的Accessibility获取当前窗口和控件的层级化信息。这比纯截图更高级它能获取到按钮的ID、文本、类型等结构化属性。规划Plan将观察到的信息屏幕描述或元素树和任务目标如“登录系统”一起发送给大模型ollama。大模型分析当前状态并规划出下一步具体操作例如“在‘用户名’输入框定位信息name‘username’中输入‘admin’”。执行ActOpenClaw解析大模型返回的指令调用对应的底层库如pyautogui、pynput或系统专属API执行点击、输入等操作。验证Verify执行后再次“观察”屏幕将结果如是否跳转到新页面、是否出现成功提示反馈给大模型进行判断形成闭环。部署与集成难点OpenClaw的安装通常通过pip(pip install openclaw) 即可。真正的挑战在于环境配置。因为它严重依赖系统级的可访问性接口。在Windows上需要确保pywin32等库正确安装并且测试程序以足够的权限运行有时需要管理员权限才能完整获取UI Automation信息。在macOS上需要在“系统设置-隐私与安全性-辅助功能”中为你的终端或IDE授予完全磁盘访问和控制电脑的权限否则OpenClaw无法模拟鼠标键盘事件。在Linux上通常依赖X11或Wayland的相应工具如xdotool、scrot等可能需要单独安装。我个人的踩坑点是在macOS上即使授予了权限如果通过ssh远程执行脚本辅助功能授权可能会失效。最终解决方案是在本地终端直接运行启动脚本或者研究使用launchd配置后台服务。3. 系统架构与工作流设计把三个核心组件拼装成一个能协同工作的系统需要清晰的设计。下图展示了整个自动化测试Agent的核心工作流flowchart TD A[启动测试任务br如“测试登录流程”] -- B[OpenClaw 捕获当前UI状态] B -- C{UI信息格式化br截图/元素树} C -- 路径1: 视觉描述 -- D[将截图送入视觉模型br如 LLaVA生成文本描述] C -- 路径2: 结构化数据 -- E[直接使用UI元素树brID, Name, Type, Bounds等] D -- F E -- F[组合任务目标与UI状态br构造提示词Prompt] F -- G[调用 Ollama (QwQ-32B)br获取下一步动作指令] G -- H{解析模型指令} H -- 是操作指令 -- I[OpenClaw 执行具体操作br点击、输入、滚动等] H -- 是验证/断言指令 -- J[OpenClaw 捕获新状态br进行结果比对] I -- K[等待界面稳定br短延时或检测条件] K -- B J -- L{验证结果是否符合预期?} L -- 符合 -- M[记录成功继续或结束任务] L -- 不符合 -- N[记录失败详情br截图、错误信息] N -- O[任务终止或进入异常处理流程] M -- P{任务是否完成?} P -- 未完成 -- B P -- 已完成 -- Q[生成测试报告]这个工作流的核心在于“观察-思考-行动”的循环。与硬编码脚本的最大不同在于“思考”环节由大模型动态完成。我们需要精心设计连接“观察”与“思考”的提示词Prompt这是项目成败的关键。4. 核心实现提示词工程与动作指令设计让大模型可靠地控制UI八成功夫在提示词设计上。模型需要明确知道自己的角色、可用的工具、当前的屏幕状态以及输出的格式。4.1 系统提示词System Prompt构建系统提示词定义了模型的角色和行为规范。一个好的系统提示词应该包含角色定义明确告诉模型它是一个UI自动化助手。能力范围列出它能执行的所有操作类型如click,type,scroll,read,assert。输入格式说明描述它将接收到什么样的屏幕信息例如一个包含控件列表的JSON或对截图的文字描述。输出格式强制要求这是最重要的部分。必须要求模型以严格的、可解析的格式如JSON输出且只输出这个格式的内容不能有任何额外解释。示例系统提示词骨架你是一个专业的UI自动化测试助手。你的任务是根据提供的当前屏幕用户界面(UI)信息决定下一步要执行什么操作来完成指定的任务。 ## 你的能力 你可以执行以下类型的操作每次只能输出一个操作 1. CLICK: 点击一个元素。你需要提供元素的精准定位信息。 2. TYPE: 向一个输入元素输入文本。 3. SCROLL: 向上或向下滚动。 4. READ: 读取一个元素的文本内容用于后续判断。 5. ASSERT: 验证某个条件是否成立如某个文本出现。 6. DONE: 当任务被判定为完成或无法继续时使用。 ## 输入信息 你将收到一个JSON对象包含两个字段 - task: 字符串描述需要完成的最终任务例如“登录到邮箱系统”。 - ui_state: 一个数组描述当前屏幕上所有可交互或关键的UI元素。每个元素包含属性如id, name, type (如 “button”, “textfield”), text (显示的文字), bounds (屏幕坐标)等。 ## 输出格式 你必须且只能输出一个JSON对象格式如下 { action: CLICK | TYPE | SCROLL | READ | ASSERT | DONE, target: { ... }, // 操作目标元素的定位信息与ui_state中的描述对应 value: ... // 可选对于TYPE是输入文本对于ASSERT是预期值 }4.2 动态上下文与动作决策在每一轮循环中我们构造的用户提示词User Prompt是这样的{ task: 登录到演示系统用户名为‘test’密码为‘123456’, ui_state: [ {id: username_field, type: textfield, name: 用户名, text: , bounds: [100, 200, 300, 220]}, {id: password_field, type: password, name: 密码, text: , bounds: [100, 250, 300, 270]}, {id: login_btn, type: button, name: 登录, text: 登录, bounds: [150, 300, 250, 330]} ] }模型收到后应该分析出第一步是在id为username_field的字段中输入“test”。它返回{action: TYPE, target: {id: username_field}, value: test}OpenClaw解析这个JSON找到对应坐标或元素执行输入操作。然后OpenClaw等待片刻如0.5秒再次捕获UI状态将新的ui_state此时用户名框应有内容连同原始任务再次发送给模型。模型接着决策下一步是输入密码以此类推。实操心得UI状态的描述至关重要。直接给坐标 (bounds) 是最直接的但界面缩放或分辨率变化会导致失效。优先使用id、name这些语义化属性。如果OpenClaw无法获取这些退而求其次可以使用基于截图和OCR的“视觉定位”但这需要更强大的多模态模型如LLaVA来理解截图并将“点击登录按钮”转换为屏幕坐标复杂度更高。在我们的架构中首选是利用系统可访问性API获取结构化元素树。5. 实操演练从零搭建一个登录测试用例理论说再多不如动手跑一遍。我们以测试一个简单的桌面客户端登录功能为例串联整个流程。5.1 环境准备与启动假设我们在一个Windows测试机上操作。启动Ollama服务确保ollama已安装并后台运行且加载了QwQ-32B模型。可以通过ollama list查看使用ollama run qwq:32b在命令行简单测试一下模型是否正常响应。安装OpenClaw在Python环境中pip install openclaw。根据你的主要测试平台如win32可能还需要额外安装一些后端依赖。编写核心驱动脚本创建一个test_login.py文件。5.2 核心代码解析以下是简化版的核心代码逻辑展示了如何将各个部分连接起来。import json import time import requests from openclaw import Claw # 假设OpenClaw的主类叫Claw class UIAgent: def __init__(self, ollama_base_urlhttp://localhost:11434): self.claw Claw() # 初始化OpenClaw self.ollama_url f{ollama_base_url}/api/generate self.system_prompt 此处填入上一节设计好的完整系统提示词 def get_ui_state(self): 使用OpenClaw获取当前活动窗口的UI元素树并转换为我们定义的格式 # 这里调用OpenClaw的API来获取元素信息 # 实际API名称可能需要查阅OpenClaw文档例如 elements self.claw.get_active_window_elements() ui_state [] for elem in elements: if elem.is_interactive(): # 过滤出可交互元素 ui_state.append({ id: elem.get_attribute(id) or , name: elem.get_attribute(name) or , type: elem.get_control_type(), # 如 Button, Edit text: elem.get_text() or , bounds: elem.get_bounding_rect() # 返回 [x1, y1, x2, y2] }) return ui_state def ask_model(self, task, ui_state): 构造提示词调用Ollama API获取模型返回的动作指令 user_prompt json.dumps({task: task, ui_state: ui_state}, ensure_asciiFalse) full_prompt fSystem: {self.system_prompt}\nUser: {user_prompt}\nAssistant: payload { model: qwq:32b, prompt: full_prompt, stream: False, options: {temperature: 0.1} # 低温度保证输出稳定 } try: resp requests.post(self.ollama_url, jsonpayload, timeout30) resp.raise_for_status() result resp.json() # 模型响应在 result[response] 中 response_text result[response].strip() # 尝试解析为JSON return json.loads(response_text) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: print(f调用模型失败或解析响应出错: {e}) print(f原始响应: {response_text}) return {action: DONE} def execute_action(self, action_cmd): 解析并执行模型返回的动作指令 action action_cmd.get(action) target action_cmd.get(target, {}) if action CLICK: # 根据target中的定位信息找到元素并点击 elem self.claw.find_element(**target) if elem: elem.click() else: # 如果找不到可以尝试根据bounds点击 bounds target.get(bounds) if bounds: self.claw.click_at(bounds[0], bounds[1]) elif action TYPE: elem self.claw.find_element(**target) value action_cmd.get(value, ) if elem: elem.type_text(value) elif action DONE: return False # 停止循环 # ... 处理其他动作类型 return True # 继续循环 def run_task(self, task_description): 运行一个任务的主循环 print(f开始任务: {task_description}) max_steps 20 # 防止无限循环 for step in range(max_steps): print(f\n--- 步骤 {step1} ---) # 1. 观察 ui_state self.get_ui_state() print(f当前UI元素数: {len(ui_state)}) # 2. 思考 action_cmd self.ask_model(task_description, ui_state) print(f模型指令: {action_cmd}) # 3. 执行 should_continue self.execute_action(action_cmd) if not should_continue: print(任务完成或终止。) break # 短暂等待界面响应 time.sleep(1) else: print(达到最大步数任务可能未完成。) if __name__ __main__: agent UIAgent() # 启动待测应用例如 # subprocess.Popen([path/to/your/app.exe]) # time.sleep(3) # 等待应用启动 agent.run_task(登录到应用用户名为‘demo’密码为‘pass123’然后点击‘确定’按钮)5.3 执行过程与调试运行脚本前手动打开被测的桌面应用并停留在登录界面。运行脚本后你会看到控制台打印出每一步捕获的UI元素和模型生成的指令。理想情况下它会自动完成输入用户名、密码、点击登录的操作。关键调试点UI状态获取是否准确首先检查get_ui_state()函数返回的数据。元素是否齐全id、name属性是否抓取到了如果这里数据不对后续全错。模型指令是否合规观察ask_model返回的JSON是否严格符合我们定义的格式。常见的初期问题是模型会在JSON外加一层 Markdown 代码块标记json ...或者在JSON后附加解释文字。这需要在提示词中强烈警告模型“只输出JSON不要任何其他内容”。动作执行是否精准如果模型指令正确但点击位置不对可能是bounds坐标转换有问题或者需要加入点击前的元素.ensure_visible()滚动到可视区域操作。6. 性能优化与稳定性提升实战这套系统跑起来后你会发现它比传统脚本慢且存在不确定性。以下是几个关键的优化方向。6.1 减少大模型调用次数每次调用大模型都有延迟本地32B模型一次生成也需要数秒。优化策略动作批处理对于连续的、无逻辑依赖的多个操作可以提示模型一次性输出一个动作列表。例如“清空输入框并输入新文本”可以合并为一个TYPE动作先发送全选快捷键再输入。状态缓存与对比不要每次循环都全量获取UI状态。可以缓存上一次的状态只将发生变化的部分或关键区域的信息发送给模型。引入确定性规则对于非常明确、固定的操作可以设置一个旁路规则引擎。例如如果检测到“错误弹窗”且其“确定”按钮存在则直接点击无需询问模型。这相当于给智能体加上了条件反射。6.2 提升指令遵循的稳定性模型有时会“抽风”输出奇怪的动作或无法解析的格式。温度Temperature参数调用API时将temperature设为较低值如0.1降低随机性使输出更确定。后处理与重试对模型的输出进行健壮性解析。如果JSON解析失败可以尝试清理响应文本如去除代码块标记或者将错误响应和原始UI状态一起再次发送给模型要求它纠正。可以设置最多2-3次重试。更精细的提示词在系统提示词中提供更具体的例子Few-shot Learning。例如直接给出2-3轮“UI状态 - 正确动作JSON”的示例能极大提高模型输出的准确性。6.3 处理动态界面与等待自动化测试经典难题如何知道界面已加载完成智能等待执行一个动作后不要固定等待time.sleep(2)。可以改为等待直到某个关键元素如登录后的主页标题出现或者连续多次采样UI状态发现其稳定不再变化。OpenClaw需要提供轮询查找元素的功能。异常状态检测与恢复在提示词中教导模型识别常见异常状态如“页面加载中显示旋转图标”、“网络错误提示”、“权限请求弹窗”。并为其设计恢复策略例如遇到权限弹窗模型应输出CLICK“允许”按钮。7. 结果验证与测试报告生成自动化测试的最终价值在于验证和报告。单纯的“执行了操作”不够必须验证“操作达到了预期效果”。7.1 多层次验证策略模型自验证Assert动作在任务描述中明确最终验证条件。例如任务可以是“登录并验证跳转后的主页标题为‘欢迎demo’”。在模型执行完登录点击后下一轮循环中UI状态会包含新页面的元素。模型通过READ动作获取标题文本然后输出ASSERT动作比较读取值与预期值是否一致。OpenClaw执行ASSERT时进行比对并记录结果。外部断言库集成OpenClaw可以与传统的测试断言库如Python的assert语句或pytest结合。在关键步骤后用OpenClaw获取屏幕上的特定文本或元素状态然后用assert进行判断。这样可以利用成熟的测试框架来管理用例和生成报告。视觉验证对于难以通过元素属性验证的UI变化如图表渲染、颜色变化可以结合截图比对。在关键步骤前后截图使用图像差分算法计算相似度或使用OCR读取截图中的关键信息进行验证。7.2 生成有价值的测试报告报告不应只是“通过/失败”。它应该是一个可追溯的决策日志。记录每一步保存每一轮循环的UI状态快照或截图、发送给模型的提示词、模型返回的原始指令、执行的实际操作。记录时间戳与性能数据每一步的耗时特别是调用大模型的延迟。最终报告格式可以生成HTML报告用时间线的方式展示整个测试过程在什么界面状态下模型做出了什么决策执行了什么操作结果如何。这对于调试失败的用例至关重要你可以清晰地看到是模型理解错了还是元素定位失败了或是应用程序响应异常。我个人在实践中会用一个轻量级的SQLite数据库来存储这些执行日志。失败时直接查询数据库就能还原故障现场比看控制台日志直观得多。8. 常见问题与排查指南在开发和运行过程中你肯定会遇到各种问题。这里列一些典型问题及其解决思路。问题现象可能原因排查步骤与解决方案OpenClaw无法获取UI元素或截图黑屏1. 权限不足macOS/Linux。2. 运行在远程桌面或虚拟环境图形界面访问受限。3. 待测应用是特权应用或游戏DirectX/OpenGL渲染。1.检查权限macOS务必在系统设置中授权。Windows尝试以管理员身份运行脚本。2.本地执行确保脚本在物理机的本地会话中运行而非远程连接如RDP、VNC后启动。3.尝试兼容模式对于游戏或特殊应用OpenClaw可能失效需研究其是否支持钩取特定图形API。模型返回的指令无法解析非JSON格式1. 提示词未强制要求纯JSON输出。2. 模型温度temperature设置过高输出随机。3. 网络超时导致响应不完整。1.强化提示词在系统提示词开头和结尾用醒目的方式强调“只输出JSON”。2.降低温度将API调用时的temperature参数设为0.1或0.2。3.添加后处理在代码中尝试提取json 和之间的内容或使用正则表达式匹配第一个{和最后一个}。模型指令逻辑错误如点错按钮1. UI状态描述不准确或信息缺失。2. 任务描述有歧义。3. 模型能力不足。1.检查UI State确认传递给模型的元素信息是否包含了足够定位的属性如唯一的id或name。如果只有模糊的text模型容易混淆。2.细化任务将“登录系统”改为“在用户名输入框输入‘admin’在密码输入框输入‘123456’然后点击文本为‘登录’的按钮”。3.提供示例在提示词中加入1-2个正确的决策示例Few-shot Learning。执行速度非常慢1. 每次循环都调用大模型延迟高。2.OpenClaw获取全量UI状态耗时久。3. 固定等待时间过长。1.批处理与缓存如第6.1节所述合并动作缓存状态。2.限制元素范围只获取当前活动窗口或特定区域的元素而非整个桌面。3.自适应等待用轮询等待元素出现代替固定sleep。测试结果不稳定时而成功时而失败1. 界面加载时间波动。2. 元素定位方式不稳定如依赖动态坐标。3. 模型输出存在轻微随机性。1.增加稳健等待使用更可靠的等待条件如等待元素可交互enabled。2.使用更稳定的定位器优先使用id、name其次是text最后才是bounds。可以尝试组合使用。3.设置确定性种子如果模型支持在调用时设置固定的seed参数。这套由OpenClaw和ollama-QwQ-32B驱动的自动化测试方案将传统脚本的“死板”与大模型的“灵活”相结合为处理复杂、动态的UI测试场景打开了一扇新的大门。它目前更像一个需要精心调教的“实习生”提示词是它的工作手册UI状态是它的眼睛而我们的代码则是确保它不闯祸的护栏。随着模型能力的持续进化以及框架的日益成熟这种“认知驱动”的自动化测试很可能从现在的探索阶段逐步走向主流成为应对日益复杂软件界面的重要测试手段。