Slack 工程团队提出了一种 Agentic Testing 思路:端到端测试不再完全依赖固定选择器和逐步脚本,而是让 AI Agent 根据测试意图观察界面、执行操作,并在运行时适应 UI 或系统变化。它瞄准的是分布式系统中长期存在的难题:页面只改了结构或文案,业务能力仍然正常,测试却先红了。
这并不意味着用 AI 替换所有自动化测试。更合理的定位,是让 Agent 补足传统 E2E 测试难以覆盖、维护成本过高的用户工作流,同时继续用单元测试、集成测试和确定性 E2E 测试守住明确的行为契约。
从“执行步骤”转向“完成意图”
传统 UI 自动化通常把操作过程写死:查找某个选择器、点击按钮、等待元素出现,再断言文本。只要组件层级、CSS 类名或交互顺序发生变化,脚本就可能失效。
await page.locator("#message-input").fill("deployment complete");
await page.locator("button.send-primary").click();
await expect(page.locator(".message-row:last-child")).toContainText(
"deployment complete"
);
意图驱动测试描述的则是用户目标,例如:
登录测试工作区,进入发布频道,发送“deployment complete”,并确认频道成员能够看到该消息。
Agent 可以在运行时读取可访问性树、可见文本和当前页面状态,决定应该点击哪个控件。如果“发送”按钮换了位置,或者输入框从普通文本框改成富文本编辑器,只要用户语义仍然清晰,Agent 就有机会继续完成任务。
这种灵活性尤其适合跨多个服务的长流程。分布式系统中的页面状态可能受身份服务、实时连接、后端任务和权限策略共同影响。固定脚本往往把这些动态状态压缩成一串脆弱的等待语句,而 Agent 可以基于当前观察结果重新规划下一步。
不过,适应性也会带来不确定性。Agent 可能走不同路径、误解页面含义,甚至在界面异常时“想办法绕过去”。因此测试系统必须保留动作轨迹、截图、网络记录和最终断言,不能只接受 Agent 返回的一句“任务完成”。
Agent 不应取代测试金字塔
Agentic Testing 更适合作为测试组合中的新增层,而不是替代已有层级:
- 单元测试验证纯函数、状态转换和边界条件,速度快且定位精确。
- 集成测试验证数据库、消息队列、服务接口等组件契约。
- 确定性 E2E 测试保护支付、权限、数据删除等必须严格复现的关键路径。
- Agent 驱动测试探索容易变化的 UI,覆盖跨页面、跨服务的用户目标。
一个实用的判断标准是:如果失败必须明确指出某个字段、状态码或 DOM 属性不符合契约,就使用确定性测试;如果重点是“用户能否完成任务”,而允许交互步骤发生变化,则可以尝试 Agent。
即使操作由 Agent 决定,结果断言也应尽量确定。例如,发送消息后不要只让模型判断“看起来发送成功”,还应查询后端测试接口,确认消息记录的频道、作者和内容正确。这样能把 AI 的自由度限制在导航和操作层,把业务正确性留给可重复验证的代码。
可以这样搭一个最小意图测试工作流
下面是一个可改造的伪项目示例。这里明确假设存在一个 Agent 执行器 agent-runner,它能读取 Playwright 页面,并根据 YAML 中的目标执行浏览器操作;这不是对 Slack 内部 API 或实现的复刻。
创建 tests/send-message.yaml:
name: send a release notification
start_url: http://localhost:3000/login
intent: >
Log in with the test account, open the release channel,
send the message "deployment complete", and verify it is visible.
constraints:
- Do not create or delete channels
- Do not invite users
- Stop if authentication fails
success:
visible_text: deployment complete
limits:
max_actions: 20
timeout_seconds: 90
artifacts:
screenshots: true
trace: true
action_log: true
测试凭据应通过环境变量或密钥服务注入,不要写入意图文件:
export E2E_USER='agent-test@example.com'
export E2E_PASSWORD='replace-with-secret'
agent-runner tests/send-message.yaml
如果现有系统使用 Playwright,可以把 Agent 操作与确定性断言拆开。下面的 TypeScript 示例假设 runAgent 是团队封装的 Agent 客户端;运行前需要把导入路径和后端验证地址替换为项目中的实现:
import { test, expect } from "@playwright/test";
import { runAgent } from "../lib/agent-runner";
test("a member can send a release notification", async ({ page, request }) => {
const message = `deployment complete ${Date.now()}`;
const result = await runAgent(page, {
goal: `Log in, open the release channel, and send: ${message}`,
secrets: {
username: process.env.E2E_USER!,
password: process.env.E2E_PASSWORD!,
},
constraints: [
"Do not change workspace settings",
"Do not create, archive, or delete channels",
],
maxActions: 20,
});
expect(result.status).toBe("completed");
const response = await request.get(
`/test-api/messages?channel=release&text=${encodeURIComponent(message)}`
);
expect(response.ok()).toBeTruthy();
const messages = await response.json();
expect(messages).toHaveLength(1);
expect(messages[0]).toMatchObject({
channel: "release",
text: message,
});
});
这个结构刻意划分了责任:Agent 负责找到频道和完成交互,Playwright 与测试 API 负责验证结果。即使模型错误地把页面上的旧消息当成新消息,后端断言也会阻止测试误报成功。
让失败变得可诊断
传统脚本失败时通常能得到行号和选择器错误;Agent 测试的失败路径更加动态,因此需要更完整的可观测性。每次运行至少应记录:
- 原始测试意图、约束条件以及模型和提示词版本。
- Agent 每一步看到的页面摘要、选择的动作和选择理由。
- 操作前后的截图、浏览器 trace、控制台错误和关键网络请求。
- 动作数量、重试次数、耗时和 token 消耗。
- 确定性断言的实际值与期望值。
还要区分三类失败:产品缺陷、测试环境故障和 Agent 决策错误。若三者混在同一个“任务未完成”状态里,团队只会得到新的测试噪声。
生产数据边界同样重要。测试 Agent 应使用隔离账号和工作区,获得最小权限,并限制可访问域名和危险操作。涉及删除数据、修改权限、邀请外部用户或触发付费资源时,应使用硬编码策略阻止,而不是只在自然语言提示中要求模型谨慎。
采用时从窄场景开始
适合第一批试验的场景,是维护频繁但风险可控的 UI 工作流,例如搜索内容、修改个人偏好或在测试频道发送消息。不要一开始就把账号删除、权限变更或计费流程交给 Agent。
落地时可以使用这份检查表:
- 为每个意图设置最大动作数、超时和允许访问的域名。
- 用唯一测试数据避免把历史内容误判为本次结果。
- 把关键结果交给 API、数据库或事件记录进行确定性验证。
- 固定模型和提示词版本,升级时运行基准测试集。
- 保存完整执行轨迹,并统计误报率、漏报率、耗时和成本。
- 保留少量关键路径的固定脚本,避免所有验证都依赖模型判断。
Agentic Testing 的价值不在于让 AI 随意点击页面,而在于把容易变化的操作路径与必须稳定的业务断言分离。导航可以有弹性,结果必须可验证。守住这条边界,Agent 才可能降低 UI 测试维护成本,而不是把选择器脆弱性换成模型不确定性。