1. 项目概述:当大模型遇上UI自动化测试
最近在搞一个挺有意思的玩意儿,把智谱的GLM-4.7-Flash大模型和OpenClaw这个自动化测试框架给搭上了。说白了,就是想看看,能不能让AI自己去看网页、点按钮、填表单,然后告诉我这个功能到底行不行。这听起来有点像让一个刚学会用电脑的“数字员工”去干测试的活儿,但背后的逻辑其实更复杂,也更有趣。
传统的UI自动化测试,无论是用Selenium、Appium还是Playwright,核心都是“脚本驱动”。我们得预先写好每一步操作:点这里,等两秒,在那个输入框里填上“test@example.com”,再点提交按钮。脚本是死的,页面稍微变一下,比如按钮的ID改了,或者加载慢了一秒,脚本就可能“卡壳”报错,维护成本不低。而“OpenClaw + GLM-4.7-Flash”这个组合,想走的是另一条路:“意图驱动”。我不再告诉它具体的DOM路径或坐标,而是告诉它:“去这个网址,找到登录框,输入我的账号密码,然后点击登录。” 剩下的,让大模型去理解页面内容,识别元素,并执行操作。这不仅仅是自动化,更是在尝试赋予测试脚本一定的“认知”和“适应”能力。
这个项目特别适合两类朋友:一是对AI应用落地充满好奇的开发者,想亲手试试大模型除了聊天还能干啥实在活;二是被繁重回归测试所困的测试工程师或开发者,在寻找更智能、维护成本更低的自动化方案。如果你正在为那些频繁变动的页面写一堆脆弱的XPath而头疼,那接下来的内容或许能给你打开一扇新窗户。整个过程涉及环境部署、模型集成、意图编写和结果验证,我会把踩过的坑和验证有效的技巧都摊开来讲。
2. 核心思路与架构选型解析
2.1 为什么是OpenClaw + GLM-4.7-Flash?
首先得搞清楚我们手里的“兵器”。OpenClaw是一个开源的AI智能体框架,它本身不直接提供“视觉”或“认知”能力,但它提供了一个优秀的“大脑”调度和“肢体”控制框架。你可以把它理解为一个机器人的“中枢神经系统”,它负责接收任务指令(比如“测试登录功能”),然后调度合适的“工具”(Tool)去执行。OpenClaw内置或可以方便地集成各种工具,例如操作浏览器、读写文件、调用API等。
而GLM-4.7-Flash,是智谱AI推出的一个轻量级但能力强劲的大语言模型。它的“Flash”版本在保持不错性能的同时,推理速度更快,成本更低,非常适合需要频繁交互的自动化场景。它的核心价值在于“理解”:理解我们用自然语言描述的测试意图,理解它通过OpenClaw从浏览器获取到的页面信息(可能是HTML片段、截图或结构化数据),然后做出决策,生成下一步的操作指令。
所以,这个组合的分工很明确:GLM-4.7-Flash充当“眼睛”和“大脑”,负责观察与思考;OpenClaw充当“神经”和“手”,负责调度与执行。我们不需要教AI认识每一个HTML标签,只需要告诉它目标是什么。
2.2 与传统自动化框架的对比思考
肯定会有人问,这比直接用Selenium强在哪?又有什么坑?这里我列了一个简单的对比表,方便大家理解:
| 特性维度 | 传统框架 (如 Selenium) | OpenClaw + GLM-4.7-Flash 方案 |
|---|---|---|
| 驱动方式 | 脚本驱动,依赖元素定位器 (ID, XPath, CSS Selector)。 | 意图驱动,依赖自然语言描述和模型对页面的理解。 |
| 维护成本 | 高。页面结构变化需同步更新定位脚本,用例维护工作量大。 | 潜在较低。模型具备一定的泛化能力,能适应小幅度的UI变化(如文本描述改变)。 |
| 开发门槛 | 中。需要学习特定框架的API和元素定位技术。 | 中偏高。需要理解大模型交互、提示工程,以及框架的集成方式。 |
| 执行稳定性 | 高(在脚本正确的前提下)。操作精确,可预测性强。 | 待观察/依赖调优。受模型理解准确性、页面加载状态影响,可能存在“迷惑”行为。 |
| 适用场景 | 稳定、核心业务流程的回归测试。 | 探索性测试、复杂交互流程测试、对变化有一定容忍度的监控场景。 |
| 初始投入 | 相对较低,有成熟生态和大量资料。 | 相对较高,涉及模型部署/API成本、提示词调试等。 |
选择这个方案,并非要完全取代Selenium。它的价值在于处理那些用传统脚本难以编写或维护成本极高的场景。比如,一个充满动态生成内容、元素没有稳定ID的富交互单页应用(SPA),或者是需要根据页面内容动态决定执行路径的复杂业务流程。我们的目标是增强自动化测试的能力边界,而不是简单的替换。
2.3 整体工作流设计
整个自动化测试流程,我把它设计成一个闭环:
- 任务解析:我们将一个测试用例(如“验证用户登录功能”)以自然语言形式提交给OpenClaw。
- 环境感知:OpenClaw调度浏览器工具(例如集成Playwright)打开目标URL,并获取当前页面的“状态”。这个“状态”可以是截屏、可访问的DOM树,或者是经过处理的简化HTML。
- 模型决策:将“任务描述”和“页面状态”一起,构造成一个清晰的提示词(Prompt),发送给GLM-4.7-Flash模型。模型分析后,输出一个结构化决策,例如:
{"action": "click", "selector": "button:has-text('登录')", "reason": "这是页面上唯一的登录按钮文本"}。 - 指令执行:OpenClaw解析模型的输出,调用对应的浏览器工具API执行点击操作。
- 验证与迭代:操作执行后,再次获取新的“页面状态”,并判断是否达到任务终态(如登录成功,出现“欢迎,用户”的提示)。如果未完成,则重复步骤3-4,形成多轮对话式的自动化执行。
- 结果断言:最终,根据预设的验证点(如特定成功文本出现、URL跳转),由OpenClaw或模型本身给出测试通过与否的结论。
这个流程里,最关键的环节是步骤3:如何让模型准确地理解页面并做出可靠决策。这直接取决于我们如何为它准备“页面状态”信息,以及如何设计提示词。
3. 环境部署与核心组件搭建
3.1 基础运行环境准备
我的实验环境基于一台Linux服务器,拥有NVIDIA GPU(对于本地部署GLM模型几乎是必须的,除非你全程使用API)。如果你没有GPU,可以考虑使用GLM的云端API服务,但需要注意网络延迟和成本。
第一步是准备好Python环境。我强烈建议使用conda或venv创建独立的虚拟环境,避免包冲突。
# 创建并激活虚拟环境 conda create -n openclaw-glm python=3.10 conda activate openclaw-glm接下来安装PyTorch。这一步需要根据你的CUDA版本到 PyTorch官网 获取准确的安装命令。例如,对于CUDA 11.8:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118注意:PyTorch版本与CUDA版本的匹配至关重要。使用
nvidia-smi查看CUDA版本,如果版本不匹配,后续运行模型时会失败。
3.2 OpenClaw的安装与初步配置
OpenClaw的安装相对直接。我们从GitHub拉取源码进行安装,这样可以方便地查看和修改其内部工具集成方式。
git clone https://github.com/open-mmlab/OpenClaw.git cd OpenClaw pip install -e . # 以可编辑模式安装,便于开发调试安装完成后,你可以运行openclaw --help来验证安装是否成功。OpenClaw的核心配置文件通常是一个YAML文件,它定义了智能体使用的工具、模型连接等。我们需要创建一个自定义的配置文件。这里我给出一个最简化的示例config.yaml,它集成了一个假设的“WebPlaywrightTool”浏览器工具,并留出了模型配置位置。
# config.yaml agent: name: "ui_tester" tools: - name: "web_browser" type: "playwright" # 假设我们使用Playwright作为浏览器驱动工具 launch_args: headless: false # 调试时可设为false,观察执行过程 slow_mo: 500 # 操作间延迟,方便观察 model: provider: "zhipu" # 使用智谱AI name: "glm-4-7-flash" # 模型名称 api_key: "${ZHIPU_API_KEY}" # 建议通过环境变量传入API Key base_url: "https://open.bigmodel.cn/api/paas/v4/" # 智谱开放平台API地址实操心得:在配置工具时,
headless: false在开发调试阶段极其有用。你能亲眼看到浏览器被打开、页面跳转、元素被点击的全过程,对于调试模型决策错误至关重要。等流程稳定后,再改为headless: true用于无人值守执行。
3.3 GLM-4.7-Flash模型的两种接入方式
这里有两个选择:本地部署和调用云端API。
方案一:本地部署(推荐用于深度集成和调试)智谱AI开源了GLM系列模型。我们可以使用ollama这个强大的工具来本地拉取和运行GLM-4.7-Flash模型。
- 首先安装ollama。访问 ollama.com 根据你的操作系统下载安装。
- 拉取GLM-4.7-Flash模型(请注意模型名称可能随版本更新,需查阅最新文档):
ollama pull glm-4-7-flash - 运行模型服务:
默认情况下,ollama会在ollama run glm-4-7-flash11434端口提供一个兼容OpenAI API格式的本地服务。这意味着我们可以像调用ChatGPT API一样调用本地模型。此时,OpenClaw配置中的model部分需要修改:model: provider: "openai" # 使用OpenAI兼容格式 name: "glm-4-7-flash" # 任意名称,用于标识 base_url: "http://localhost:11434/v1" # ollama的API地址 api_key: "ollama" # ollama默认不需要key,但某些框架要求非空,可随意填写
方案二:调用智谱AI开放平台API(推荐用于快速验证和稳定生产)如果你不想管理本地GPU资源,智谱AI提供了稳定的云端API服务。
- 前往 智谱AI开放平台 注册并创建API Key。
- 在OpenClaw配置中,直接使用前面
config.yaml示例中的配置,并将你的API Key设置为环境变量ZHIPU_API_KEY。
关键选择解析:本地部署的优势是数据不出内网、无网络延迟、调用成本固定(电费)。缺点是硬件门槛高(需要足够显存的GPU),且需要自行维护模型服务稳定性。云端API的优势是开箱即用、免运维、按需付费。对于测试任务不密集或初期探索,我建议先用云端API快速跑通流程;当需要高频、大批量测试时,再考虑本地化部署以控制成本。
3.4 浏览器自动化工具集成
OpenClaw本身可能不直接包含一个成熟的浏览器工具。我们需要为其集成一个。Playwright是目前非常强大的浏览器自动化库,支持多浏览器(Chromium, Firefox, WebKit),且API友好。我们可以基于Playwright为OpenClaw编写一个自定义工具。
这个工具的核心功能至少包括:goto(跳转URL)、screenshot(截图)、get_page_content(获取页面文本或简化HTML)、click(点击)、fill(输入)、wait_for_selector(等待元素)等。工具类需要继承OpenClaw的BaseTool,并在_run方法中实现具体逻辑。
由于编写完整的工具代码较长,这里给出一个概念性的示例,说明如何让OpenClaw能够调度Playwright:
# 示例:custom_web_tool.py from openclaw.tools import BaseTool import playwright.sync_api class WebPlaywrightTool(BaseTool): name = "web_browser" description = "A tool to control a web browser for UI automation." def __init__(self): super().__init__() self.playwright = playwright.sync_api.sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=False) self.context = self.browser.new_context() self.page = self.context.new_page() def _run(self, action: str, **kwargs): if action == "goto": url = kwargs.get("url") self.page.goto(url) return f"Navigated to {url}" elif action == "get_content": # 获取页面主要文本内容,可以过滤掉脚本、样式等 content = self.page.inner_text("body") # 简单示例,实际可更复杂 return content # ... 其他action实现 else: raise ValueError(f"Unknown action: {action}") def close(self): self.context.close() self.browser.close() self.playwright.stop()然后,在你的OpenClaw主程序中,将这个工具实例注册给智能体。这样,当模型决定要“点击登录按钮”时,它可以通过调用web_browser工具的click动作来实现。
4. 提示词工程与模型交互设计
这是整个项目中最具“艺术性”和“技巧性”的部分。模型的表现好坏,八成取决于你如何与它“对话”。
4.1 构建有效的系统提示词(System Prompt)
系统提示词用于设定模型的角色和行为规范。对于UI自动化测试,我们需要模型扮演一个严谨、细致、按部就班的测试执行者。
一个有效的系统提示词可能长这样:
你是一个专业的Web UI自动化测试助手。你的任务是严格根据用户给出的测试步骤和当前页面信息,决定下一步在网页上执行什么操作。 ## 你的能力 1. 你可以通过工具控制浏览器(打开网页、点击、输入文本等)。 2. 你会收到当前的页面文本摘要或关键元素信息。 ## 你的行为准则 1. **专注当前步骤**:只思考如何完成当前被要求的测试步骤,不要自行跳跃或添加步骤。 2. **精确描述操作**:你的输出必须是严格的JSON格式,包含以下字段: - `thought`: 简要说明你为什么做出这个决策,基于页面上的什么信息。 - `action`: 操作类型,只能是以下之一:`click`, `fill`, `goto`, `wait`, `assert`, `finish`。 - `selector`: 用于定位元素的CSS选择器或Playwright定位器文本(如 `button:has-text('登录')`)。必须确保其能唯一、稳定地定位到目标元素。 - `value`: 仅当`action`为`fill`时需要,表示要输入的文字。 - `expectation`: 仅当`action`为`assert`时需要,描述断言期望看到的内容。 3. **安全第一**:不要执行任何可能破坏数据或危险的操作。如果页面状态不明确或元素找不到,输出`action`为`wait`,并说明原因。 4. **一步一动**:每次只输出一个操作指令。等待该指令执行并返回新页面状态后,再进行下一步决策。这个提示词明确了角色、输出格式、行为边界和安全要求。thought字段非常关键,它让模型的“思考过程”变得可追溯,便于我们调试为什么它会做出一个错误的选择。
4.2 设计多轮对话的上下文管理
一次完整的测试用例(如“登录并检查收件箱”)通常需要多个操作步骤。我们不能把整个任务描述一次性丢给模型,然后指望它输出一长串操作序列。因为页面状态是动态变化的,模型需要根据上一步操作的结果来决定下一步。
因此,我们需要实现一个多轮对话循环:
- 将系统提示词和第一轮的用户提示(包含测试目标,如“开始执行登录测试,当前页面是首页”)发送给模型。
- 模型返回第一个操作指令(如
{"action": "click", "selector": "a:has-text('登录')", ...})。 - OpenClaw执行该操作,并获取新的页面状态。
- 将新的页面状态作为下一轮对话的“用户输入”,再次发送给模型。模型结合历史对话(上下文)和最新页面状态,决定下一个操作。
- 重复步骤3-4,直到模型输出
{"action": "finish", ...}或达到最大轮次限制。
这里的关键是页面状态信息的组织。直接把完整的HTML丢给模型,会消耗大量token,且包含太多噪音。我们需要进行“信息压缩”。一种有效的方法是提取页面的关键文本信息、链接文本和按钮文本。例如,使用Playwright可以这样获取:
# 获取页面所有交互元素的文本信息 elements = page.query_selector_all('a, button, input[type="submit"], input[type="button"], [role="button"]') page_info = [] for el in elements: text = el.inner_text().strip() if text: tag = el.evaluate('el => el.tagName.toLowerCase()') page_info.append(f"[{tag.upper()}] '{text}'") current_state = "\n".join(page_info) # 示例输出: # [A] '登录' # [BUTTON] '搜索' # [INPUT] '' (可能是输入框)将这样简洁的current_state连同任务描述一起喂给模型,能极大提高其决策的准确性和速度。
4.3 输出格式约束与解析
我们必须强制模型以严格的JSON格式输出。在提示词中强调这一点,并在代码层面进行验证和解析。如果模型返回了非JSON或格式错误的内容,我们的程序应该能处理这种异常,例如尝试修复或要求模型重试。
import json import re def parse_model_response(response_text: str): """ 尝试从模型回复中解析JSON指令。 """ # 尝试直接解析 try: return json.loads(response_text) except json.JSONDecodeError: # 如果失败,尝试用正则表达式提取可能的JSON块 json_match = re.search(r'\{.*\}', response_text, re.DOTALL) if json_match: try: return json.loads(json_match.group()) except: pass # 如果还是失败,返回一个安全的等待指令 return { "thought": "无法解析模型响应,等待人工检查。", "action": "wait", "selector": "body" }避坑技巧:即使提示词要求了JSON,模型偶尔仍会在JSON前后添加解释性文字。因此,一个健壮的解析函数是必不可少的。此外,在开发初期,可以将模型完整的回复(包括非JSON部分)都打印出来,这对于调试提示词效果至关重要。
5. 完整测试流程实现与案例拆解
让我们用一个具体的案例——“在某电商网站完成商品搜索并加入购物车”——来串联整个流程。假设网站首页URL是https://demo-shop.example.com。
5.1 测试用例定义与任务启动
首先,我们将测试用例抽象成一个任务描述文件task_search_and_cart.yaml:
task_id: "test_search_add_cart_001" description: "在电商网站搜索'无线鼠标',从结果列表选择第一个商品,将其加入购物车,并验证购物车数量增加。" start_url: "https://demo-shop.example.com" max_steps: 20 # 防止无限循环主程序会加载这个任务,初始化OpenClaw智能体(加载配置和工具),然后开启执行循环。
5.2 多轮执行循环的代码骨架
以下是核心执行逻辑的简化代码:
import yaml from openclaw import OpenClawAgent import asyncio async def run_test_case(task_config): # 1. 加载配置,初始化智能体 agent = OpenClawAgent.from_config("config.yaml") await agent.initialize() # 2. 获取浏览器工具并打开起始页 browser_tool = agent.get_tool("web_browser") await browser_tool.run("goto", url=task_config['start_url']) current_state = await browser_tool.run("get_simplified_content") # 3. 构建初始对话 messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"测试任务:{task_config['description']}\n当前页面信息:\n{current_state}\n请开始执行第一步。"} ] step_count = 0 # 4. 多轮交互循环 while step_count < task_config['max_steps']: step_count += 1 print(f"\n=== 步骤 {step_count} ===") # 调用模型获取决策 response = await agent.model.call(messages) decision = parse_model_response(response['content']) print(f"模型决策: {decision}") # 检查是否结束 if decision.get('action') == 'finish': print("任务完成!") break # 执行操作 action = decision['action'] if action in ['click', 'fill', 'goto']: result = await browser_tool.run(action, **{k: decision[k] for k in ['selector', 'value', 'url'] if k in decision}) print(f"执行结果: {result}") elif action == 'assert': # 执行断言验证 success = await perform_assertion(browser_tool, decision['expectation']) print(f"断言结果: {'通过' if success else '失败'}") if not success: print("断言失败,终止测试。") break elif action == 'wait': print(f"等待: {decision.get('thought', '')}") await asyncio.sleep(2) # 简单等待 # 获取新状态,准备下一轮对话 current_state = await browser_tool.run("get_simplified_content") messages.append({"role": "assistant", "content": json.dumps(decision)}) messages.append({"role": "user", "content": f"上一步操作已执行。当前页面信息:\n{current_state}\n请继续下一步。"}) # 5. 清理资源 await browser_tool.run("close") print("测试执行结束。") # 运行任务 with open('task_search_and_cart.yaml', 'r') as f: task = yaml.safe_load(f) asyncio.run(run_test_case(task))5.3 关键步骤的模型决策模拟
让我们模拟一下模型在几个关键步骤可能产生的思考与输出:
步骤1:从首页到搜索框
- 页面状态:
[A] '登录' [A] '注册' [INPUT] '' [BUTTON] '搜索' [DIV] '热门推荐...' - 模型思考 (thought):“用户任务是搜索商品。当前页面有一个输入框和一个‘搜索’按钮,这很可能是搜索功能区域。我应该先在输入框中输入关键词。”
- 模型输出:
{ "thought": "在首页发现了搜索输入框和按钮。我将首先在输入框中输入‘无线鼠标’。", "action": "fill", "selector": "input[placeholder*='搜索' i], input[type='search'], input:first-of-type", "value": "无线鼠标" }注意:模型给出的
selector可能不够精确。这里它使用了多个备选选择器,用逗号分隔,Playwright会尝试第一个匹配项。在实际应用中,我们可能需要更精细的页面信息来生成更准的定位器。
步骤2:点击搜索按钮
- 执行上一步后,页面状态可能无变化(输入框已填好)。
- 模型输出:
{ "thought": "关键词已输入,现在需要触发搜索。点击旁边的‘搜索’按钮。", "action": "click", "selector": "button:has-text('搜索')" }
步骤3:在结果页选择第一个商品
- 执行搜索后,页面状态更新:
[DIV] '找到100个结果' [DIV] '品牌A 无线鼠标...' [BUTTON] '加入购物车' [DIV] '品牌B 无线鼠标...' [BUTTON] '加入购物车' - 模型思考:“任务要求选择第一个商品。页面显示了商品列表,第一个商品是‘品牌A 无线鼠标...’。我需要点击这个商品条目或它的‘加入购物车’按钮。通常,点击商品本身会进入详情页,而直接点击‘加入购物车’可能更直接。根据常见电商逻辑,我先尝试点击第一个‘加入购物车’按钮。”
- 模型输出:
{ "thought": "已进入搜索结果页。我将点击第一个商品的‘加入购物车’按钮。", "action": "click", "selector": "button:has-text('加入购物车'):near(:text('品牌A 无线鼠标'))" }技巧:这里模型使用了Playwright的
:near()伪类来更精确地定位属于第一个商品的按钮,这是一个高级但非常实用的定位策略,需要在给模型的页面状态信息或提示词中引导它使用。
步骤4:验证购物车数量
- 点击加入购物车后,可能弹出浮层或页面刷新。新的页面状态可能包含:
[SPAN] '购物车(1)' [BUTTON] '去结算' - 模型思考:“商品已加入。需要验证购物车数量是否从0变为1。页面上有‘购物车(1)’的文本,这符合预期。”
- 模型输出:
或者,如果我们要求模型做显式断言:{ "thought": "检测到购物车图标旁的数字变为1,表明商品已成功加入。任务完成。", "action": "finish" }
然后,我们的{ "thought": "需要验证购物车数量。当前页面显示‘购物车(1)’。", "action": "assert", "expectation": "页面应包含‘购物车(1)’或类似表示数量为1的文本。" }perform_assertion函数会去检查页面是否存在该文本。
通过这个案例,你可以看到整个“意图-感知-决策-执行”的循环是如何一步步推进的。模型的“智能”体现在它能够根据动态的页面信息,理解任务上下文,并选择合理的操作路径。
6. 常见问题、调试技巧与优化策略
在实际操作中,事情很少有一帆风顺的。下面是我在调试过程中遇到的一些典型问题及解决方法。
6.1 模型决策不准或“迷惑行为”
- 问题表现:模型点击了错误的按钮,或者在一个页面上“犹豫不决”,输出重复或无效操作。
- 根因分析:
- 页面状态信息质量差:提供给模型的页面摘要太简单或太杂乱,无法让模型准确理解页面结构。
- 提示词不够清晰:系统提示词中对输出格式、操作类型的约束不够强,或对任务分解的指导不明确。
- 任务描述模糊:比如“登录系统”,没有指定用户名和密码是什么,模型可能卡在输入框不知如何填写。
- 解决方案:
- 增强页面状态信息:除了文本,可以附加元素的类型(button, link, input)、可能的角色(role)甚至屏幕坐标区域。例如,将
[BUTTON] ‘搜索’扩展为[BUTTON: id=‘searchBtn’, near=‘搜索框’] ‘搜索’。 - 细化提示词:在系统提示词中提供更具体的例子(Few-shot Learning)。例如,展示一个从“打开登录页”到“点击登录按钮”的完整决策序列示例。
- 分步骤引导:将复杂的测试用例拆分成更原子化的子任务,逐个提交给模型。例如,先执行“导航到登录页”,成功后再执行“在登录页填写凭证”。
- 引入验证点:在每一轮操作后,让模型不仅输出动作,也输出一个对操作后页面变化的简短预期。这有助于我们判断模型是否“理解”了它在做什么。
- 增强页面状态信息:除了文本,可以附加元素的类型(button, link, input)、可能的角色(role)甚至屏幕坐标区域。例如,将
6.2 元素定位器不稳定或失效
- 问题表现:模型生成的
selector(如button:has-text('提交'))在页面上匹配到多个元素或匹配不到,导致执行失败。 - 根因分析:纯文本定位在页面有多个相同文本按钮时(如多个“提交”按钮)会失败。页面动态加载导致元素尚未出现。
- 解决方案:
- 引导模型使用更稳健的定位策略:在提示词中鼓励模型结合文本、元素类型和相对位置。例如:“优先使用包含唯一文本的按钮,如果文本不唯一,尝试使用
:near()伪类结合附近唯一文本来定位。” - 工具层增强:在浏览器工具中实现一个
smart_find方法。当模型返回的selector执行失败时,自动尝试一些备选方案,比如放宽文本匹配的严格度,或者使用Playwright的get_by_role和get_by_label等语义化定位方法进行回退查找。 - 强制等待与重试:在执行操作前,工具自动等待页面稳定(如网络空闲、主要元素可见)。对于点击等操作,加入重试机制。
- 引导模型使用更稳健的定位策略:在提示词中鼓励模型结合文本、元素类型和相对位置。例如:“优先使用包含唯一文本的按钮,如果文本不唯一,尝试使用
6.3 执行效率与成本问题
- 问题表现:测试一个简单流程耗时过长,或者调用API/本地模型消耗大量资源。
- 优化策略:
- 缓存页面状态:对于短时间内重复访问的静态部分页面,可以缓存其状态信息,避免重复获取和发送给模型。
- 压缩提示词上下文:及时清理对话历史中过时的、不相关的消息,只保留最近几轮的关键交互,以减少token消耗。
- 使用更轻量的模型或API:对于简单的元素识别和操作决策,GLM-4.7-Flash已经足够。如果某些步骤非常固定(如输入固定的用户名密码),可以设计规则引擎来绕过模型调用,直接执行。
- 并行执行:如果测试集独立,可以启动多个浏览器实例和智能体并行运行不同的测试用例。
6.4 验证与断言机制
自动化测试的灵魂在于“验证”。仅仅执行操作是不够的,必须检查结果是否符合预期。
- 模型自我断言:如前所述,可以让模型在任务结束时输出一个
assert动作,描述期望看到的页面状态。然后由测试框架去验证。 - 框架侧断言:在测试用例定义中,预先定义好关键的验证点(Checkpoints)。例如,在“加入购物车”任务中,预设验证点为“页面元素包含文本‘添加成功’或购物车图标计数增加”。当模型执行到相应步骤后,由框架主动触发这些断言。
- 混合断言策略:对于关键业务结果(如订单创建成功),采用框架侧强断言。对于页面流转是否正确这类问题,可以依赖模型的
finish动作或assert动作作为辅助判断。
调试时,务必打开浏览器的headless: false模式,并配合slow_mo参数放慢操作速度,亲眼观察模型的每一步操作。同时,将每一轮的页面状态、模型决策和执行结果都详细记录到日志文件中。当测试失败时,这份日志是分析问题根源的最宝贵资料。
7. 总结与未来展望
把GLM-4.7-Flash这样的多模态大模型通过OpenClaw框架应用到UI自动化测试中,是一次充满挑战但也极具前景的探索。它并非要颠覆现有的自动化测试方法论,而是提供了一种新的、更接近人类测试思维模式的补充手段。
从我实际的搭建和调试体验来看,这套方案的成熟度还处于早期。它的稳定性严重依赖于提示词工程、页面状态信息的质量以及模型本身的理解能力。对于逻辑复杂、状态多变的交互流程,需要投入大量的精力进行“调教”和边界情况处理。然而,它的优势同样明显:对于UI频繁变动、探索性测试、以及需要一定认知判断(例如“找到那个看起来像提交的按钮”)的场景,它展现出了比传统脚本更强的适应性和潜力。
一个很直接的进阶想法是引入“视觉”能力。目前我们主要依赖文本化的页面信息。如果能让模型直接“看到”屏幕截图(结合GLM-4V等多模态模型),那么它对图标、布局、非文本元素的理解将大大增强,定位也会更加精准。这将是下一代AI原生测试工具的核心方向。
另一个实践中的体会是,不要试图用AI完全替代所有测试。最有效的模式是“人机协同”。将稳定的、核心的冒烟测试用例用传统脚本维护;将那些易变的、探索性的、或者需要适配多种相似界面的测试任务,交给AI智能体。让合适的工具做合适的事,才能最大化整个测试体系的效率和鲁棒性。
如果你也准备尝试,我的建议是从一个最简单的、页面元素清晰的单一步骤开始(比如“在百度首页点击‘新闻’链接”),确保整个管道能跑通。然后逐步增加复杂度。这个过程就像训练一个新员工,需要耐心和清晰的指引。虽然前期搭建和调试的成本不低,但一旦流程跑顺,它为你打开的自动化测试新思路,绝对是值得的。