1. 为什么 Web 测试需要 AI Agent
如果你写过一段时间的 Web 自动化测试,大概会遇到这些让人头疼的场景:
- 页面结构一变,之前的 XPath、CSS 选择器全部失效,测试代码跟着一起“重构”。
- 登录、下单、权限校验这类跨页面流程,每一步都要写大量样板代码。
- 需求文档更新很快,测试脚本永远落后于产品迭代。
- 回归测试用例数量巨大,真正执行的频率却很低。
- 测试数据、环境状态、权限条件稍不一致,用例就出现“随机失败”。
传统自动化测试的核心问题是:把人的操作步骤固化成代码。一旦被测系统变化,测试代码就得同步改。本质上,我们是在用代码“翻译”测试用例,而没有赋予测试工具“理解”和“自主决策”的能力。
最近几年,随着大语言模型(LLM)能力的提升,出现了一种新思路:让 AI Agent 充当测试执行者。Agent 不只是执行预设步骤,它可以根据当前页面状态、任务目标、工具返回结果,决定下一步该点击哪里、输入什么、断言什么。
本文要聊的开源项目 Argus(Show HN 上展示的开源项目),走的就是这个方向:用开源 AI agents 对 Web 应用做自动化测试。
这篇文章不会停留在概念介绍上。我会从核心原理出发,带你拆解 Agent 测试 Web 应用的工作方式,并给出一个可以实际运行的极简示例。通过这个示例,你可以理解 LLM 如何驱动浏览器、如何设计工具调用、如何收集页面状态并生成结论。读完之后,你既能把类似思路落地到自己的测试项目中,也能更好地评估应该选择哪种 AI 测试方案。
如果你是刚开始接触 AI Agent 的开发者、测试工程师,或者正在为项目选型 AI 测试方案,这篇文章都很适合你。
2. Argus 是什么:开源 AI 测试 Agent 的核心思路
2.1 Argus 的定位
Argus 是一个开源项目,定位很明确:面向 Web 应用测试场景的 AI agents 工具集。
简单说,它想解决传统自动化测试中的两个老大难:
- 用例维护成本高:页面改动、文案调整、交互逻辑变化,都会让写死的测试脚本失效。
- 回归覆盖面不足:测试用例数量庞大,人工维护跟不上,导致回归测试总是做不完整。
Argus 的思路是把“测试执行过程”交给 AI Agent。测试人员提供目标描述,比如“打开首页,完成登录,验证用户中心入口存在”,Agent 自己决定用什么方式实现这个目标,并在执行过程中根据实际页面反馈不断调整。
这种模式下,测试脚本不再是一系列固定的操作指令,而是一段“任务描述 + 工具能力 + 终止条件”。页面结构变化时,只要语义没有大变,Agent 就有可能自动适应新页面。
2.2 Agent 测试的工作流程
一个典型的 AI Agent 测试流程大致如下:
- 接收测试任务描述,例如“验证登录失败时是否提示错误信息”。
- 观察当前页面状态,提取可点击元素、输入框、按钮等关键信息。
- 根据任务目标和当前状态,决定下一步动作。
- 调用工具执行动作,比如点击、输入、跳转、截图。
- 观察动作结果,判断是否达到目标。
- 如果尚未完成,回到第 3 步继续执行。
- 达到目标后,输出测试结论和必要证据。
这个过程形成了一个闭环:观察 → 决策 → 执行 → 再观察。只要 Agent 的决策能力足够强,工具集足够完整,它就能处理相当复杂的多步骤流程。
2.3 什么时候适合用 AI Agent 测试
AI Agent 测试并不是要完全取代传统自动化测试,而是适用于特定的场景:
| 场景 | 传统自动化 | AI Agent 测试 |
|---|---|---|
| 页面结构频繁变化 | 维护成本高 | 适应能力较强 |
| 多步骤业务链路(登录、下单、支付) | 脚本冗长 | 自然语言描述即可 |
| 回归测试全量执行 | 成本可控 | 需要关注成本和安全 |
| 精确到像素级断言 | 可以做到 | 目前偏弱 |
| 离线、无网络环境 | 可以运行 | 依赖 LLM 服务 |
在快速迭代的 Web 业务中,AI Agent 测试尤其适合做探索性测试和冒烟测试,它能以较低成本快速覆盖关键路径,并发现传统脚本难以预料的边界问题。
3. 环境准备与项目结构
接下来我们进入实操环节。我会基于 Argus 的设计理念,实现一个极简的 AI 测试 Agent。注意:这是用于理解原理的最小实现,不是 Argus 官方代码。具体使用 Argus 项目时,请以官方 README 和实际版本为准。
3.1 运行环境
本文示例使用以下环境:
- Python 3.10 及以上版本
- Playwright for Python
- OpenAI Python SDK(或其他兼容 OpenAI 接口的 LLM 服务)
版本方面不必完全一致,只要保持依赖之间兼容即可。示例代码在 Python 3.10 / 3.11 上测试通过。
3.2 安装依赖
创建项目目录后,执行以下命令安装依赖:
mkdir ai-agent-test cd ai-agent-test pip install playwright openai安装完成后,还需要安装 Playwright 的浏览器内核:
playwright install chromium如果你希望使用界面模式观察 Agent 操作,安装 chromium 就够了;如果要用 WebKit 或 Firefox,可以对应安装其他内核。
3.3 项目结构
这个极简项目只需要两个文件:
ai-agent-test/ ├── agent_test.py # Agent 主脚本 └── .env # 环境变量(可选)其中.env文件用于保存 LLM API Key,内容如下:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx当然,也可以不创建.env,直接在系统环境变量中配置OPENAI_API_KEY。
3.4 准备被测系统
为了让 Agent 有东西可测,你需要准备一个测试用的 Web 应用。如果你手头没有现成项目,可以用任意一个公开的、允许自动化访问的演示站点,或者本地启一个简单的 HTML 页面。
这里我建议准备一个带登录功能的简单页面,因为登录流程是 Web 测试中最典型的场景。示例站点不一定需要多复杂,关键是要有输入框、按钮、页面跳转等要素。
4. 核心原理拆解:LLM + 浏览器工具调用
在写代码之前,先把核心原理讲清楚。只有理解了原理,你才能根据自己的业务场景扩展工具集和任务目标。
4.1 Agent 循环
AI 测试 Agent 本质上是一个循环:
- 把任务目标、历史操作、页面状态打包成消息。
- 调用 LLM,让模型决定下一步动作。
- LLM 返回一个工具调用指令,比如
click(selector="button.login")。 - Agent 执行工具,把执行结果追加到消息历史。
- 再次调用 LLM,直到模型认为任务完成,或者达到最大步数。
这个循环和传统自动化“按步骤执行”有本质区别:每一步都是基于当前真实页面状态动态决策的,而不是阅读写死的步骤列表。
4.2 工具集设计
Agent 能执行什么动作,取决于我们给它提供什么工具。一个最小可用的 Web 测试工具集至少需要:
| 工具 | 作用 |
|---|---|
| open_url | 打开指定 URL |
| click | 点击元素(支持文本或选择器) |
| fill | 在输入框中填入内容 |
| get_text | 获取页面文字内容,用于断言 |
| screenshot | 截图保存证据 |
| page_info | 获取当前 URL 和标题 |
更完整的工具集还可以包括:切换 Tab、处理弹窗、等待元素、获取元素属性、上传文件、执行 JavaScript 等。
设计工具集时有一个原则:工具粒度要适合 LLM 调用。太细(比如“移动鼠标 10 像素”)会让模型难以决策;太粗(比如“执行整个登录流程”)又让模型失去灵活性。推荐以“用户视角的可见操作”为粒度,比如点击、输入、读取文字、截图。
4.3 上下文与状态管理
Agent 之所以能在多步骤流程中保持连续性,是因为我们始终把“之前的操作和结果”放在消息历史里。每次调用 LLM 时,都会携带:
- 系统提示词(说明 Agent 的角色和任务)
- 用户的测试目标
- 之前每一步的工具调用及结果
- 当前页面状态摘要
需要注意的是,页面状态不能全量塞给模型。完整的 DOM 可能有几万 token,既不经济又容易让模型“迷失”。更合理的做法是抽取关键信息,比如可见文字、按钮文案、输入框占位符、当前 URL,组成一段简洁的页面摘要。
5. 完整实战:仿 Argus 思路实现极简 AI 测试 Agent
下面进入代码实现。这个示例会实现一个带“登录验证”任务的极简 AI 测试 Agent。代码结构清晰,适合你在此基础上扩展。
5.1 创建项目文件
首先创建agent_test.py,并在开头导入依赖:
# agent_test.py import json import os from openai import OpenAI from playwright.sync_api import sync_playwright client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))这里从环境变量读取OPENAI_API_KEY。如果你在.env里配置了 Key,需要先加载:
from dotenv import load_dotenv load_dotenv()如果没有安装python-dotenv,可以先执行pip install python-dotenv。
5.2 定义工具清单
为了让 LLM 知道有哪些工具可用,我们需要声明工具清单。OpenAI 的 Function Calling 支持结构化工具描述:
TOOLS = [ { "type": "function", "function": { "name": "open_url", "description": "打开指定 URL,参数:url(目标网址)", "parameters": { "type": "object", "properties": { "url": {"type": "string", "description": "目标网址"} }, "required": ["url"] } } }, { "type": "function", "function": { "name": "click", "description": "点击页面元素,可通过 text 指定可见文本,或通过 selector 指定 CSS 选择器", "parameters": { "type": "object", "properties": { "selector": {"type": "string", "description": "CSS 选择器"}, "text": {"type": "string", "description": "要点击的按钮/链接文本"} } } } }, { "type": "function", "function": { "name": "fill", "description": "在输入框中输入内容,参数:selector(CSS 选择器)、value(输入内容)", "parameters": { "type": "object", "properties": { "selector": {"type": "string", "description": "CSS 选择器"}, "value": {"type": "string", "description": "输入值"} }, "required": ["selector", "value"] } } }, { "type": "function", "function": { "name": "get_text", "description": "获取当前页面的文本内容,用于检查页面是否包含特定文字", "parameters": { "type": "object", "properties": {} } } }, { "type": "function", "function": { "name": "screenshot", "description": "对当前页面截图并保存,参数:path(保存路径)", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "截图保存路径"} }, "required": ["path"] } } } ]工具描述中,每个参数的说明要尽量具体。LLM 靠描述来理解参数含义,描述不清晰会严重影响工具调用的准确性。
5.3 实现工具执行器
工具清单是“声明”,还需要一个函数负责实际执行。这个执行器接收工具名称和参数,调用 Playwright 的 API 操作页面:
def execute_tool(page, tool_name, args): if tool_name == "open_url": page.goto(args["url"], timeout=15000) page.wait_for_load_state("networkidle") return {"status": "ok", "url": page.url} if tool_name == "click": if args.get("text"): page.click(f"text={args['text']}", timeout=8000) elif args.get("selector"): page.click(args["selector"], timeout=8000) else: return {"status": "error", "message": "click 需要 text 或 selector 参数"} page.wait_for_timeout(800) return {"status": "ok", "url": page.url} if tool_name == "fill": page.fill(args["selector"], args["value"]) page.wait_for_timeout(300) return {"status": "ok", "selector": args["selector"]} if tool_name == "get_text": content = page.inner_text("body") return {"status": "ok", "text_preview": content[:1200], "url": page.url} if tool_name == "screenshot": page.screenshot(path=args.get("path", "screenshot.png"), full_page=False) return {"status": "ok", "path": args.get("path", "screenshot.png")} return {"status": "error", "message": f"未知工具: {tool_name}"}这里我对get_text返回的文本做了截断,只保留前 1200 字符。原因前面说过:页面文本太长会占用大量上下文,截断后对模型来说已经足够获取关键信息。
5.4 构建 Agent 循环
现在实现核心的 Agent 循环。这个循环的思路是:
- 将任务目标、系统提示、历史操作记录统一放入消息列表。
- 调用 OpenAIA 接口,带上工具清单。
- 如果返回结果包含工具调用,就执行工具,把结果加入消息历史,然后继续。
- 如果返回结果没有工具调用,说明 Agent 认为任务已经结束,输出最终结论。
def run_test_agent(page, task: str, max_steps: int = 8): messages = [ { "role": "system", "content": ( "你是一个 Web 应用测试 Agent。你的任务是通过调用工具完成给定的测试目标。" "每次行动前先分析当前页面状态,选择最合适的工具。" "完成目标后,直接输出测试结论,不要再调用工具。" "如果遇到错误,尝试换一种方式处理。" ) }, {"role": "user", "content": f"测试目标:{task}"} ] for step in range(1, max_steps + 1): print(f"\n===== 第 {step} 步 =====") response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=TOOLS, tool_choice="auto" ) message = response.choices[0].message messages.append(message) if not message.tool_calls: print("Agent 结论:", message.content) return message.content for tool_call in message.tool_calls: fn_name = tool_call.function.name try: fn_args = json.loads(tool_call.function.arguments) except json.JSONDecodeError: fn_args = {} print(f"调用工具: {fn_name}, 参数: {fn_args}") try: result = execute_tool(page, fn_name, fn_args) except Exception as e: result = {"status": "error", "message": str(e)} messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) print("达到最大步数,任务结束。") return "未能在限制步数内完成任务"这里有几个关键点需要说明:
tool_choice="auto":让模型自己决定是否调用工具、调用哪个工具。message.tool_calls为空时:说明模型不再调用工具,直接输出文字结论,此时视为任务完成。- 异常捕获:即使工具执行出错,也把错误信息返回给模型,让它修正策略。这比直接崩溃健壮得多。
max_steps限制:防止 Agent 陷入无限循环。实际项目中建议把这作为可配置项。
5.5 编写主函数
主函数负责启动浏览器、创建页面、调用 Agent 循环:
def main(): task = ( "打开登录页面 http://localhost:8080/login," "使用用户名 admin、密码 123456 登录," "然后检查登录后的页面是否包含“登录成功”字样,并截图保存。" ) with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() try: run_test_agent(page, task) finally: browser.close() if __name__ == "__main__": main()这里把任务描述写成了自然语言。实际项目中可以把任务通过命令行参数或测试用例文件传入,实现“数据和逻辑分离”。
5.6 运行与验证
执行脚本:
python agent_test.py如果你用的 LLM 服务商、模型名或 API 地址与示例不同,需要在代码中调整client的初始化参数。比如使用本地部署的兼容 OpenAI 接口模型时:
client = OpenAI( api_key="local", base_url="http://localhost:11434/v1" )5.7 预期输出说明
一次典型运行可能产生类似输出:
===== 第 1 步 ===== 调用工具: open_url, 参数: {'url': 'http://localhost:8080/login'} ===== 第 2 步 ===== 调用工具: fill, 参数: {'selector': '#username', 'value': 'admin'} ===== 第 3 步 ===== 调用工具: fill, 参数: {'selector': '#password', 'value': '123456'} ===== 第 4 步 ===== 调用工具: click, 参数: {'text': '登录'} ===== 第 5 步 ===== 调用工具: get_text, 参数: {} ===== 第 6 步 ===== 调用工具: screenshot, 参数: {'path': 'login_result.png'} Agent 结论: 登录成功,页面包含“登录成功”字样,截图已保存为 login_result.png。注意:由于 LLM 不是确定性算法,实际运行时的步骤顺序、选择器、文本都可能与上面不同。如果页面元素缺少稳定的id或name属性,模型可能选择用文本定位,这是正常的。
如果 Agent 在某个步骤连续失败,它会尝试换一种策略。比如第一次用 selector 定位失败,第二次可能会改用文本定位。这正是 Agent 测试相比传统脚本的优势所在。
6. 常见问题与排查思路
AI Agent 测试属于较新的实践,运行中遇到的问题也不少。下面整理高频问题及排查思路:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型不调用任何工具,直接输出臆想结果 | 系统提示词不明确,或工具描述不清晰 | 强化提示词,明确要求“必须通过工具获取页面信息后再下结论” |
| Agent 反复执行同一个动作 | 页面状态没有变化,模型陷入循环 | 限制最大步数;要求每次工具调用前先输出“对页面状态的判断” |
| 定位元素失败 | 页面结构复杂,选择器不准确 | 截取页面文本和 DOM 片段反馈给模型;增加等待时间 |
| 页面状态信息过大,超 token 限制 | DOM 文本太长 | 对页面文本做截断;优先提取可见文本、按钮文案、输入框占位符 |
| LLM API 调用报错 | 网络问题或 Key 失效 | 检查 API Key、网络环境、服务商状态 |
| 测试结果不稳定 | LLM 决策有随机性 | 设置较低温度参数;增加关键步骤的断言;将“检查点”写进提示词 |
6.1 关于测试用例的筛选问题
传统自动化测试框架中,例如 Google Test(C++ 测试框架),提供了::testing::flags_gtest_filter这样的机制,可以通过--gtest_filter=Foo.Bar精确筛选要执行的用例。这种“用例过滤”思路在传统测试中非常重要,它让你只跑相关的用例。
但在 AI Agent 测试中,“筛选”发生了变化。你不需要通过复杂的命令参数过滤用例,而是直接在任务描述中写清楚要测什么。比如:
- “只验证登录失败的提示信息”
- “检查购物车为空时的页面展示”
- “走一遍从商品详情到提交订单的全流程”
Agent 会自行决定需要执行哪些步骤、跳过哪些无关操作。用自然语言替代过滤参数,是 AI 测试和传统测试在工作方式上的一个明显区别。
不过,这并不意味着传统测试框架的过滤概念没有价值。在实际工程中,你仍然需要一套机制来决定“哪些任务要跑、哪些任务可以跳过”。比如在 CI 中,冒烟测试任务、回归测试任务应该有自己固定的 Prompt 集合。
6.2 排查 Checklist
如果你遇到 Agent 表现不如预期,建议按下面顺序排查:
- 任务描述是否足够具体?有没有给出明确的验证点?
- 工具集是否覆盖了当前任务所需的动作?
- 工具描述是否清晰?模型能否理解每个参数的含义?
- 页面状态反馈信息是否足够?模型有没有看到关键文字?
- 最大步数设置是否合理?太少的步数会让模型仓促结束。
- 是否有稳定的页面元素标识?没有的话,模型只能依赖文本定位。
- LLM 版本和参数是否合适?不同模型对 Function Calling 的支持度不同。
7. 最佳实践与工程建议
7.1 任务描述要写清楚“验证点”
AI Agent 不是万能的,它需要明确的目标。写任务时不仅要说清操作路径,还要说清验证点:
不好的写法:登录一下看看 好的写法:打开 /login,用 admin/123456 登录,验证登录成功后页面出现“工作台”或“欢迎”,并截图验证点越具体,Agent 的判断越准确,最终结论也越可靠。
7.2 用“检查点”约束结论
你可以要求 Agent 在输出结论前,必须调用get_text获取页面文本,再依据文本内容判断测试是否通过。这样可以有效避免模型“闭着眼睛得出结论”。
示例系统提示词:
你必须在查看页面文本后给出结论,禁止在未调用 get_text 的情况下断言页面内容。7.3 控制上下文长度
每轮对话都会保留历史记录,步骤越多 token 消耗越大。建议:
- 对
get_text返回的页面文本做截断处理。 - 丢弃早期的工具结果,只保留最近几步的操作记录。
- 设置合理的最大步数,通常 8 到 15 步足够处理大部分 Web 测试任务。
- 如果任务非常长,考虑拆分为多个子任务,串联执行。
7.4 提升操作稳定性
AI Agent 依赖真实浏览器环境运行,稳定性不如传统脚本。可以从这些方面优化:
- 统一测试环境,避免本机弹窗、网络波动影响结果。
- 对关键操作增加兜底逻辑,比如等待元素出现后再点击。
- 当工具执行失败时,把错误信息返回给模型,给它一次“自我修正”的机会。
- 建议在
headless=True模式下跑回归,在有界面模式下调试。
7.5 保证测试数据隔离
AI Agent 操作真实浏览器,有可能产生真实数据。测试环境必须与生产环境隔离,推荐:
- 使用独立的测试域名或本地环境。
- 使用 mock 的外部接口。
- 测试产生的数据要可清理、可重置。
- 涉及删除、修改数据的操作,必须加二次确认或只在专用环境执行。
7.6 安全与权限边界
使用 AI Agent 执行测试时,本质上是在“放权”给模型操作浏览器。因此在生产环境之外,需要额外注意几件事:
- 只授权模型访问测试环境的 URL,不要给它访问生产系统的能力。
- 工具的访问颗粒度要控制,比如限制 Download 操作、文件写入目录等。
- API Key 和密钥存放在环境变量或密钥管理中,不要提交到代码仓库。
- 记录 Agent 的完整操作日志,方便审计和追踪问题。
7.7 结果证据留存
测试不能只看结论,还要能溯源。建议每次测试自动收集:
- 操作步骤日志。
- 关键节点的截图。
- 页面 URL 变化记录。
- 最终结论及其依据。
这些证据既方便排查问题,也可以用于向团队展示测试覆盖率。
7.8 成本控制
LLM API 是按 token 计费的,Agent 每一步都会产生对话费用。控制方法包括:
- 给可能超过步数的任务设置上限。
- 精简系统提示词和工具描述。
- 页面内容截断,减少无效 token。
- 优先使用成本更低的模型做常规冒烟测试,再把复杂任务交给更强模型。
7.9 与 CI 集成的思路
AI Agent 测试要真正产生价值,建议接入持续集成:
- 将测试任务定义成配置文件,例如 JSON/YAML。
- 在 CI Pipeline 中启动测试环境。
- 执行 Agent 测试脚本。
- 收集日志、截图、结论,生成测试报告。
- 失败时通过消息通知相关人。
接入 CI 时尤其要注意环境稳定性,建议先跑少量核心任务,验证效果后再逐步扩大覆盖面。
8. 总结与下一步学习路线
本文围绕 Argus 这个开源项目切入,完整梳理了 AI Agent 测试 Web 应用的核心思路和落地方式。我们讨论了传统自动化测试的痛点、Agent 测试的工作流程、工具集设计、上下文管理,并用 Python + Playwright + OpenAI 实现了一个可以运行的极简 AI 测试 Agent。
通过这个示例,你应该已经掌握了几个关键点:
- AI 测试 Agent 是一个“观察 → 决策 → 执行 → 再观察”的循环。
- 工具集是 Agent 操作浏览器的接口,设计粒度很关键。
- 页面状态需要有效抽取和截断,不能全量塞给模型。
- 测试结论需要有依据,不能依赖模型凭空输出。
- 工程落地时,数据隔离、成本控制、日志审计都是必须考虑的问题。
下一步,你可以从这几个方向继续深入:
- 扩展工具集:为你的业务场景补充上传文件、处理弹窗、切换 Tab、读取 Cookies 等能力。
- 接入本地模型:尝试用 Ollama、vLLM 等本地部署模型替代云端 API,解决数据隐私和成本问题。
- 设计任务编排:把复杂的端到端测试拆成多个子任务,通过一个调度器串联。
- 完善报告系统:把截图、日志、结论组装成可读的测试报告,接入飞书、钉钉或企业微信通知。
- 跑一遍真实项目:选择一个内部测试环境,用 AI Agent 完成一条核心业务链路,然后对比传统自动化的维护成本。
在实际落地时有一点忠告:AI Agent 测试目前更适合作为“补充手段”而非“唯一方案”。关键核心链路、数据一致性要求极高的场景,传统自动化仍然更可靠。先把 AI Agent 放在探索性测试、冒烟测试、页面结构易变的功能测试上,用最小成本验证效果,再逐步扩大使用范围。
最后回到 Argus 这个项目本身。如果你对开源 AI 测试 Agent 感兴趣,可以关注 Argus 的实现方式和社区进展。类似项目各有侧重,有的强调多 Agent 协作,有的侧重报表能力,有的深度集成某个测试框架。多对比、多实践,才能找到最适合你自己团队的那套方案。