从 Playwright 到 Browser Agent

Playwright 是前端工程师非常熟悉的浏览器自动化工具。它能打开页面、定位元素、点击按钮、填写表单、等待状态、截图、断言结果。

Browser Agent 也需要这些能力,但它不是 Playwright 的替代品。

更准确地说,Playwright 是 Browser Agent 的动作层和观测层之一。Browser Agent 在 Playwright 之上增加了目标理解、页面观察、步骤决策、风险控制、验证恢复和执行回放。

如果把 Playwright 看作“手”,Browser Agent 还需要“眼睛、短期记忆、判断和刹车”。

Playwright 擅长什么

Playwright 擅长确定性的浏览器自动化。

例如一个登录测试:

test('login success', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('邮箱').fill('dev@example.com');
  await page.getByLabel('密码').fill('correct-password');
  await page.getByRole('button', { name: '登录' }).click();
  await expect(page.getByText('欢迎回来')).toBeVisible();
});

这段脚本有几个前提:

  • 页面路径已知。
  • 操作步骤固定。
  • 元素名称稳定。
  • 预期结果明确。
  • 失败时交给测试报告。

这正是自动化测试的优势。它可靠、可重复、可并行、可回归。

但如果用户给出的目标是“帮我进入后台,把今天失败的订单找出来,导出前三条的客户信息”,步骤就不一定固定了。

页面可能要搜索,可能要翻页,可能要处理筛选条件,可能要关闭弹窗,可能要等待异步接口,可能要根据页面内容决定下一步。

这时,只有 Playwright API 还不够。你需要一个能在每一步读取页面并重新决策的系统。

Browser Agent 补上的能力

Browser Agent 相比 Playwright 脚本,主要补上六个能力。

目标理解

Playwright 脚本执行的是开发者写好的步骤。

Browser Agent 接收的是用户目标,例如:

打开报表系统,查找上周转化率下降最多的渠道,并导出结果。

Agent 需要把目标转成可执行步骤。它不一定一次性规划完整路线,而是可以边观察边推进。

页面观察

Playwright 脚本通常知道要找哪个元素。

Browser Agent 不一定知道。它要先观察当前页面,整理可交互元素、正文、表单、弹窗、错误信息和视觉状态,再决定下一步。

type BrowserObservation = {
  url: string;
  title: string;
  text: string;
  elements: Array<{
    id: string;
    role: string;
    name: string;
    value?: string;
    visible: boolean;
    enabled: boolean;
  }>;
  screenshotId?: string;
};

这个 observation 是 Playwright 和模型之间的翻译层。

动态决策

Playwright 脚本的下一步写在代码里。

Browser Agent 的下一步来自当前 observation 和用户目标。

{
  "thought": "当前页面是登录页,需要先填写账号密码,但密码缺失,需要请求用户输入。",
  "action": {
    "type": "request_user_input",
    "reason": "需要登录凭证"
  }
}

这里模型不是直接操作浏览器,而是提出结构化动作。执行器负责判断这个动作是否允许。

风险控制

Playwright 默认会照脚本执行。Browser Agent 必须知道哪些动作不能直接做。

读取页面通常是低风险。填写表单是中风险。点击保存、提交、发送、删除、付款、发布是高风险。

一个基本风险分级可以这样写:

type BrowserActionRisk = 'read' | 'draft' | 'write' | 'external_effect';

type PlannedAction = {
  type: 'click' | 'fill' | 'navigate' | 'extract' | 'submit';
  targetId?: string;
  value?: string;
  risk: BrowserActionRisk;
  requiresApproval: boolean;
};

高风险动作应该进入用户确认,而不是由模型自动执行。

验证和恢复

Playwright 测试有 expect,Browser Agent 也需要类似能力。

区别是:测试里的断言由开发者预先写好;Agent 的验证可能需要根据任务动态生成,再由规则和模型共同判断。

例如任务是“下载最新账单”,验证可以包括:

  • 页面出现“下载完成”。
  • 浏览器产生下载文件。
  • 文件名包含当前月份。
  • 文件大小大于 0。

如果失败,Agent 需要选择恢复策略:重新点击、等待更久、检查权限、请求用户登录或停止。

执行回放

Playwright 测试失败时可以查看 trace。Browser Agent 更需要 trace,因为用户会关心它到底做了什么。

每一步都应该记录:

  • observation 摘要。
  • 模型计划。
  • 执行动作。
  • 执行结果。
  • 截图。
  • 验证结论。
  • 错误和恢复动作。

这让 Agent 的行为可解释、可调试、可审计。

从脚本到 Agent 的架构演进

可以把演进过程分成四层。

第一层是普通 E2E 脚本。

第二层把 Playwright 常用能力封装成工具,统一错误处理、截图、等待和日志。

第三层引入页面观察和结构化动作协议,让模型不直接碰 Playwright API。

第四层才是完整 Agent:围绕目标和状态循环执行,必要时请求确认,失败时恢复,完成后返回证据。

第一步:封装浏览器工具

不要一开始就让模型调用任意 Playwright 代码。

更稳的方式是只暴露少量可控工具:

type BrowserTool =
  | { name: 'navigate'; input: { url: string } }
  | { name: 'click'; input: { elementId: string } }
  | { name: 'fill'; input: { elementId: string; value: string } }
  | { name: 'select'; input: { elementId: string; value: string } }
  | { name: 'extractText'; input: { elementId?: string } }
  | { name: 'screenshot'; input: {} };

这些工具背后可以用 Playwright 实现,但工具接口要稳定、可校验、可记录。

例如 click 工具不应该接受任意 selector,而应该接受 observation 中的 elementId。

async function clickElement(ctx: BrowserContext, elementId: string) {
  const target = ctx.currentObservation.elements.find((item) => item.id === elementId);
  if (!target) throw new Error(`Unknown elementId: ${elementId}`);

  const locator = resolveLocator(ctx.page, target);
  await locator.click();
  await waitForPageSettled(ctx.page);
}

这层封装是安全边界。模型只表达意图,执行器负责落实到浏览器。

第二步:实现页面观察器

页面观察器负责把 Playwright page 转成模型可读的结构。

一个基础观察器可以收集:

  • URL 和 title。
  • 可见标题和正文摘要。
  • role 为 button、link、textbox、combobox、checkbox 的元素。
  • label、name、value、placeholder、disabled。
  • 当前 dialog、alert、toast。
  • 必要时截图。

观察器应该控制长度。不要把所有节点都塞进去。

type ObservedElement = {
  id: string;
  role: string;
  name: string;
  value?: string;
  placeholder?: string;
  disabled: boolean;
  selector: {
    strategy: 'role' | 'label' | 'testid' | 'css';
    value: string;
  };
};

前端工程师在这里很有优势,因为你知道哪些元素对任务有意义,哪些只是布局噪声。

第三步:定义动作协议

模型输出必须结构化。

不要让模型返回“我会点击右上角搜索按钮”。它应该返回:

{
  "action": "click",
  "elementId": "el_8",
  "reason": "需要打开搜索条件面板",
  "risk": "read"
}

动作协议至少包含:

  • action type。
  • target elementId。
  • 输入值。
  • 原因。
  • 风险等级。
  • 预期结果。
type AgentBrowserAction = {
  action: 'navigate' | 'click' | 'fill' | 'select' | 'extract' | 'wait' | 'askUser' | 'finish';
  elementId?: string;
  value?: string;
  reason: string;
  expected?: string;
  risk: 'read' | 'draft' | 'write' | 'external_effect';
};

有了动作协议,执行器才能校验、拦截、记录和回放。

第四步:加上状态机

Browser Agent 不应该只是一个 while loop。

它应该有明确状态:

状态机让前端 UI 更容易展示过程,也让后端更容易恢复失败。

用户看到的不应该只是一个 loading,而应该是:

  • 正在观察页面。
  • 正在判断下一步。
  • 正在填写字段。
  • 等待你确认提交。
  • 正在验证结果。
  • 第 3 步失败,可重试。

第五步:把断言升级成验证器

Playwright 测试里我们写:

await expect(page.getByText('保存成功')).toBeVisible();

Browser Agent 里,验证器可以更通用:

type BrowserVerifier = {
  name: string;
  verify(input: {
    goal: string;
    action: AgentBrowserAction;
    before: BrowserObservation;
    after: BrowserObservation;
  }): Promise<VerificationResult>;
};

验证器可以组合规则和模型。

规则负责确定性判断:

  • 字段值是否匹配。
  • 按钮是否消失。
  • URL 是否变化。
  • toast 是否出现。
  • 下载文件是否存在。

模型负责语义判断:

  • 页面内容是否回答了用户问题。
  • 抽取结果是否符合目标。
  • 当前页面是否看起来像成功状态。

两者结合,比单纯相信模型更可靠。

Playwright Trace 给 Browser Agent 的启发

Playwright 的 trace viewer 是一个非常好的参考。

它记录每一步动作、DOM 快照、截图、网络请求和错误,让开发者能回放测试过程。

Browser Agent 也需要类似执行轨迹,只是用户不一定是测试工程师,所以呈现方式要更产品化。

一个 Agent trace 可以包含:

type BrowserAgentTraceStep = {
  stepId: string;
  observationId: string;
  action?: AgentBrowserAction;
  result?: {
    status: 'success' | 'failed' | 'skipped';
    message: string;
  };
  screenshotUrl?: string;
  startedAt: string;
  endedAt?: string;
};

前端可以把它渲染成时间线,用户点击某一步查看当时的页面截图和动作说明。

这会显著提升信任感,也能帮助开发者调试失败案例。

常见坑

坑一:一上来就让模型写 Playwright 代码

让模型直接生成并执行 Playwright 代码风险很高。它可能使用不存在的 selector、执行危险操作、绕过安全检查,也很难审计。

更好的方式是暴露有限工具和结构化动作协议。

坑二:把 E2E 测试脚本误认为 Agent

Playwright 脚本可以很复杂,但只要步骤固定、目标固定、断言固定,它仍然是自动化脚本,不是 Agent。

Agent 的关键是根据观察结果动态决定下一步,并能处理未知状态。

坑三:没有最大步数和超时

Browser Agent 很容易陷入循环,例如反复找同一个按钮、反复等待加载、反复尝试失败登录。

必须设置 maxSteps、maxDuration、maxRetries 和失败停止条件。

坑四:忽略登录态和权限隔离

浏览器自动化经常需要登录态。生产系统里不能随便复用用户 cookie,也不能让不同用户共享 context。

每个用户、每个任务应该有清晰的 browser context 隔离和凭证管理策略。

坑五:没有处理弹窗、新标签页和下载

真实网页经常出现 cookie banner、营销弹窗、权限弹窗、新窗口和文件下载。

如果工具层不处理这些浏览器事件,Agent 会在看似简单的任务里频繁失败。

坑六:把所有失败都交给模型

很多失败不需要模型判断。元素不可见、网络 500、下载失败、字段值不匹配,这些都可以用规则检测。

把确定性错误交给规则,模型只处理语义和策略,系统会更稳。

小结

从 Playwright 到 Browser Agent,不是从自动化工具跳到魔法系统,而是从确定性脚本演进到可观察、可决策、可验证、可恢复的任务执行系统。

Playwright 负责可靠地操作浏览器。页面观察器负责把网页变成模型能理解的结构。动作协议负责约束模型输出。验证器负责判断是否完成。状态机和 trace 负责让过程可恢复、可审计。

前端工程师已经掌握了很多关键基础:DOM、可访问性、自动化测试和交互状态。把这些能力和 Agent 工程结合起来,就是从 Playwright 走向 Browser Agent 的真正路径。