Skip to content
围炉聊科技微信公众号二维码关注公众号,获取新文章推送
添加我为微信朋友扫描二维码,添加我为朋友

Playwright Test Agents 三件套实测 ——智能体基建系列

Playwright 在 1.56 版本(2026-05)正式内置 AI 测试组件 Playwright Test Agents。它把"写 E2E 测试"这件事拆给三个智能体分工:planner 定计划、generator 写代码、healer 修 bug。

1.00

1. Test Agents 是什么

先说官方定位(避免和圈内其他"AI 测试工具"混淆):

  • 版本:v1.56+ 内置,不是第三方插件,装 @playwright/test 就有

  • 启用npx playwright init-agents --loop=codex,生成三个 TOML 定义文件(--loop 用于指定对接的 AI Agent 客户端类型:codex / claude / vscode / opencode,不同值输出到对应客户端目录,实测 codex 落在 .codex/agents/

  • 依赖:所有 agent 都跑在同一个 MCP server 上(npx playwright run-test-mcp-server)——注意这是 Test Agents 专属 MCP 服务,和独立包 @playwright/mcp 不是同一个程序,别混淆

三件套的职责和沙箱权限:

Agent职责产出沙箱权限
planner探索站点、设计测试计划test-plan.md 测试计划read-only
generator按计划逐条生成测试代码tests/*.spec.tsread-only
healer运行并修复失败的测试修好的测试代码workspace-write

权限设计:planner 和 generator 在 MCP 沙箱里配置为 read-only——即不授予文件写入工具能力(注意这是 MCP 工具权限,不是操作系统文件只读),只有 healer 拥有 workspace-write 工具权限。这是刻意的——让 AI 先看、先写草案,最终能不能落盘、怎么改,由 healer 这一个"有写权限的 agent"把关,降低 AI 乱改代码的风险。

2. 底层逻辑:它到底是怎么工作的

Test Agents 不是把截图丢给 LLM 猜,而是通过 Model Context Protocol(MCP) 把 LLM 与真实浏览器连起来,分三层协作。

1.00

2.1 三层架构

第一层:Playwright engine(引擎层)。基于 Chrome DevTools Protocol(CDP)的浏览器自动化,和标准 Playwright 测试完全同源。这保证了 agent 产出的东西就是"真测试",不是脚本玩具——能跑、能断言、能进 CI。

第二层:LLM layer(决策层)。用大模型(GPT、Claude 这类)解释 DOM 结构、页面路由和应用行为。这一层的关键设计是:模型读的是结构化的 accessibility snapshot(无障碍快照),不是原始截图

第三层:Orchestration loop(编排循环)。协调引擎和模型的交互,循环反复:发送页面上下文 → 接收模型指令 → 执行浏览器动作 → 重复直到任务完成。

2.2 为什么用无障碍快照,而不是截图

老式的 AI 测试工具大多靠截图:模型看一张渲染出来的图,"猜"页面上有什么。但截图有天然的视觉歧义:

  • 按钮是不是被弹层挡住,截图给不出确定答案

  • 两个长得一样的 "Submit",分别属于哪个表单,截图无法区分

AOM(Accessibility Object Model)换了个思路:不给像素,给语义。每个元素提供干净的结构化信息:role、name、state、层级位置。比如 Role: button, Name: Checkout 这个描述,远比 div.checkout-btn-v3 稳定——前端改样式它不受影响。

顺带一个有趣的巧合:ARIA roles 和 labels 本来是给屏幕阅读器这类辅助技术设计的,结果恰好对 AI agent 也完全适用。为残障用户设计的语义标准,成了喂给大模型的最好格式。

3. 拿抖音实测:在 codex 里把三件套跑通

3.1 测试目标与版本

选抖音测试是因为它比其他 demo 站更能暴露 agent 的能力边界:公开内容多、交互复杂(推荐流、视频详情、评论、搜索、登录墙)、有真实反爬。

环境与版本(实测自测)

工作目录D:\Workbench\playground\playwrightagents
Playwright1.62.1(@playwright/test ^1.62.1)
系统Windows 11
测试目标https://www.douyin.com/
编排方式全部 agent 在 codex 对话中执行

3.2 反爬和登录态

3.2.1 反爬结论

抖音站点 headless 必被拦,不管带不带登录态

打开方式结果
headless 无痕验证码中间页(被拦截)
headless + 登录 profile验证码中间页(被拦截)
headed + 登录 profile正常进入

想测抖音,必须 headed + 已登录 profile。这个结论直接决定后面 codex 的引导方式——必须在对话里明确让 agent 用有头模式

3.2.2 登录态是怎么生成的:saveprofile.js

要给测试"已登录"身份开局,我用了一个独立的 saveprofile.js 脚本:打开真实浏览器 → 手动扫码登录 → 把登录态导出成 storageState 文件。完整脚本:

js
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

const PROFILE_DIR = "douyin-profile";
const STATE_FILE = path.join(PROFILE_DIR, "state.json");

(async () => {
    if (!fs.existsSync(PROFILE_DIR)) fs.mkdirSync(PROFILE_DIR, {recursive:true});

    const browser = await chromium.launch({headless:false});
    let context;
    if(fs.existsSync(STATE_FILE)){
        context = await browser.newContext({storageState:STATE_FILE});
    }else{
        context = await browser.newContext();
    }
    const page = await context.newPage();
    await page.goto("https://www.douyin.com");

    if(!fs.existsSync(STATE_FILE)){
        console.log("请手动扫码登录,登录完成按回车保存状态");
        await new Promise(resolve=>process.stdin.once('data',resolve));
        await context.storageState({path:STATE_FILE});
        console.log("登录态已保存");
    }

    await new Promise(resolve=>process.stdin.once('data',resolve));
    await browser.close();
})();

执行过程(实测现象):

node saveprofile.js
→ 弹出 Chromium 浏览器窗口,打开抖音网页版
→ 控制台提示:请手动扫码登录,登录完成按回车保存状态
→ 浏览器扫码登录抖音网页版,确认账号已经登录成功
→ 回到终端窗口,按一下回车键
→ 自动创建 douyin-profile 文件夹,生成 state.json
→ 再次按回车,浏览器关闭

要点:脚本会复用已有的 state.jsonstorageState 方式),cookie 过期后重跑一次即可,不需要改任何测试代码。

踩坑(本版实测新增):虽然后面在 codex 里明确指定了 douyin-profile 作为 profile 目录,抖音还是会跳出登录、要求验证手机号后才进入登录态——登录态方案对强反爬站点是有边界的,这条后面在 codex 里还会遇到。

3.3 按步骤实测:六步跑通三件套

Step 1:安装最新版 Playwright

bash
npm install -D @playwright/test@latest
npx playwright install chromium

Step 2:初始化 agents(init-agents --loop=codex

bash
npx playwright init-agents --loop=codex

实测输出(原样):

PS D:\Workbench\playground\playwrightagents> npx playwright init-agents --loop=codex
 🎭 Using project "" as a primary project
 📝 specs\README.md - directory for test plans
 🌱 seed.spec.ts - default environment seed file
 🤖 .codex\agents\playwright_test_generator.toml - agent definition
 🤖 .codex\agents\playwright_test_healer.toml - agent definition
 🤖 .codex\agents\playwright_test_planner.toml - agent definition
 ✅ Done.

这一步生成三件套的定义文件 + seed 测试:

  • seed.spec.ts:所有 agent 活动的基础起点

  • .codex/agents/playwright_test_planner.toml:planner 的定义

  • .codex/agents/playwright_test_generator.toml:generator 的定义

  • .codex/agents/playwright_test_healer.toml:healer 的定义

  • specs\:计划文件目录(init 声明,计划实际由 codex 生成在根目录)

每个 TOML 都声明了角色、沙箱权限,以及它依赖的 MCP server。以 planner 为例(真实文件摘录):

toml
name = "playwright_test_planner"
description = "Use this agent when you need to create comprehensive test plan for a web application or website"
sandbox_mode = "read-only"
# ...developer_instructions...

[mcp_servers.playwright-test]
command = "cmd"
args = ["/c", "npx", "playwright", "run-test-mcp-server"]
enabled_tools = ["browser_click", "browser_snapshot", "browser_navigate",
                 "browser_evaluate", "planner_setup_page", "planner_save_plan", "..."]

generator 的 enabled_tools 里有 generator_setup_page / generator_read_log / generator_write_test;healer 的是 test_run / test_debug / test_list / test_generate_locator,且 sandbox_mode = "workspace-write"——三件套里只有它具备写权限。

Step 3:配置 seed 测试

修改 seed.spec.ts,把起点指向抖音:

ts
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
  test('seed', async ({ page }) => {
    await page.goto('https://douyin.com');
  });
});

Step 4:运行 Planner(在 codex 中执行)

在 chatgpt 新建 codex 项目,输入:

使用 douyin-profile 作为 profile目录,使用seed.spec.ts作为基础探索抖音网站生成测试计划

实测现象(本版重点踩坑):

  1. 必须引导有头模式:planner 默认倾向 headless,但 3.2.1 已经验证过抖音 headless 必被拦——需要在对话里明确要求它用真实浏览器窗口
  2. 即使指定了 profile,抖音仍会跳出登录:要求验证手机号后才进入登录态,这个环节需要人工介入一次
  3. planner 用真实浏览器逐步探索页面(精选/推荐/关注/搜索/视频详情/我的等),最终生成 test-plan.md

产出的 test-plan.md 按功能域编号的完整计划(真实文件摘录):

markdown
# 抖音网站测试计划

## 探索基线
- 基础用例:`seed.spec.ts`
- 入口:`https://douyin.com`
- Profile:`douyin-profile`
- 已复用状态:`douyin-profile/state.json`
- 探索结果:有头模式访问后进入 `https://www.douyin.com/jingxuan`,页面可正常加载精选内容。

## 阻塞与前置条件
1. 使用真实浏览器打开 `douyin-profile`;若出现验证码,由人工完成拖动拼图。
2. 如需播放、互动或搜索,先完成登录并保持同一登录态。

## 测试范围

| ID | 场景 | 核心检查 | 优先级 |
|---|---|---|---|
| ACC-01 | 首页访问 | 页面成功加载;主导航、推荐内容、搜索入口可见 | P0 |
| ACC-02 | 验证码拦截 | 触发验证时停留在验证页;刷新不造成死循环 | P0 |
| NAV-01 | 主导航切换 | 推荐、关注等频道切换后 URL、标题和内容区同步变化 | P1 |
| FEED-01 | 推荐流浏览 | 首屏视频卡片可见;滚动加载新内容 | P0 |
| FEED-02 | 视频播放 | 播放区域出现;暂停/继续/音量/进度操作正常 | P1 |
| SEARCH-01 | 关键词搜索 | 登录弹层关闭后输入关键词并提交;结果页展示 | P0 |
| SEARCH-02 | 搜索结果筛选 | 视频/用户等筛选项可切换 | P1 |
| VIDEO-01 | 视频详情 | 详情浮层打开;互动区和评论列表可见 | P0 |
| USER-01 | 用户主页 | 头像、昵称、作品列表加载;验证作者跳转规则 | P1 |
| AUTH-01 | 登录态 | 一键登录后完成登录;退出后回到未登录状态 | P0 |
| ACTION-01~04 | 点赞/关注/评论/分享 | 状态与数量变化;不误触发外部发布 | P1/P2 |
| MINE-01/02 | 我的主页/内容管理 | 个人信息、收藏、观看历史等入口可用 | P1/P2 |
| FOLLOW-01/02 | 关注页 | 关注列表、直播中列表、视频控件可用 | P1 |
| ERROR-01 | 弱网/接口失败 | 可理解的失败状态和重试入口 | P1 |
| RESP-01 | 响应式布局 | 1440×900、768×1024、375×812 下无关键控件遮挡或溢出 | P2 |

效果:计划把"探索基线"(入口、profile、已复用状态、实际确认的导航/入口/弹层行为)和"阻塞与前置条件"(人工验证码、测试账号隔离)都记录下来了——这正是 planner 的价值:把 QA 探索式测试的系统性观察固化成了可执行的测试范围。

:planner 是 read-only,只出计划不动代码;而且计划里涉及登录的用例依赖登录态,所以跑 planner 之前必须先按 3.2.2 把 state.json 准备好。

Step 5:生成测试(在 codex 中执行)

拿到计划后,继续在 codex 对话里让 generator 干活,输入:

请使用生成的测试计划来生成Playwright测试用例并把他们保存到 tests/checkout/

codex 反馈(原样):

已生成正式测试:douyin.spec.ts 共 10 条用例,使用 douyin-profile 持久化目录并以有头模式运行;9 条通过,1 条暴露 375px 横向溢出问题。 运行:npx playwright test tests/checkout/douyin.spec.ts --workers=1

产物是 tests/checkout/douyin.spec.ts(146 行),覆盖 ACC/NAV/FEED/SEARCH/VIDEO/USER/MINE/FOLLOW/ACTION/RESP 全部场景。关键代码(真实文件摘录):

ts
const test = baseTest.extend<{ profileContext: BrowserContext }>({
  profileContext: async ({}, use) => {
    const context = await chromium.launchPersistentContext(PROFILE_DIR, {
      headless: false,
      viewport: { width: 1440, height: 900 },
    });
    await use(context);
    await context.close();
  },
  ...
});

要点:generator 生成的用例用 launchPersistentContext 复用 douyin-profile 目录(而不是在 config 里配 storageState),并以有头模式、1440×900 视口运行——这正是 3.2 反爬结论在代码层面的落地。

实测结果:10 条用例里 9 条通过,1 条失败——375px 窄屏下页面横向溢出(RESP-01 响应式布局)。这不是测试写错,而是抖音站点的真实问题,正好留给 Step 6 的 healer 处理。

风险:generator 生成的用例会真实执行点赞、关注、收藏、评论等业务操作——它不是在模拟,而是真的点。因此跑这类用例建议使用隔离测试账号。

Step 6:Heal 修复与验证

对 `/tests` 中用例运行 `playwright test healer` 修复失败用例并验证修复结果。

healer 的工作流是:先跑一遍找出失败用例 → 对每条失败用例 debug 定位(看控制台日志、网络请求、页面快照)→ 做根因分析(选择器问题 / 时序问题 / 断言问题 / 应用真的坏了)→ 改代码 → 重跑验证。

最后 codex 反馈(原样):

已按 Playwright Test Healer 流程修复并验证: 修复搜索提交时序,限定搜索用例最多重试 2 次。 /tests 全量执行成功退出:9 条通过,搜索首次偶发失败后重试通过。 RESP-01 保留为预期失败,用于持续监测已知的 375px 横向溢出。

修复落地的两处代码(真实文件摘录):

ts
test.describe('搜索提交稳定性', () => {
  test.describe.configure({ retries: 2 });   // 修复搜索时序:限定最多重试 2 次

  test('SEARCH-01 / SEARCH-02 搜索关键词并切换结果分类', async ({ page }) => {
    ...
  });
});
ts
test('RESP-01 三种视口下页面不发生横向溢出', async ({ page }) => {
  test.fail(true, '已知站点问题:375x812 页面存在横向溢出');  // 保留为预期失败,持续监测
  ...
});

面对 RESP-01 这个"站点真 bug",healer 没有硬把用例改成通过、也没有静默跳过,而是用 test.fail(true) 把"已知失败"显式固化下来——跑测试时这条用例预期会失败,一旦将来抖音修复了横向溢出,测试反而会"转绿报警",从而持续监测这个已知问题。

收尾:全量验证

到这一步,"planner 定计划 → generator 写用例 → healer 修失败"的闭环在 codex 里完整跑通了,最终产物是 10 条用例、9 通过 + 1 预期失败(持续监测)。

踩坑汇总(本版实测回顾)

  1. 生成测试计划是在 codex 中执行的——对话里要指定 profile 目录和 seed 文件,且要明确引导有头模式(抖音 headless 必被拦)
  2. 虽然指定了 douyin-profile,抖音仍会跳出登录并要求验证手机号后才进入登录态,需要人工介入一次
  3. 登录态用 saveprofile.js + storageState 生成,douyin-profile/state.json 含真实登录凭据
  4. init-agents --loop 要和你的平台目录对上(codex → .codex/agents/,本次没有 .claude/skills);升级 Playwright 后必须重新跑一次 init-agents,agent 定义文件不会自动跟随版本更新
  5. 对"站点真 bug"(如 RESP-01 的 375px 溢出),healer 用 test.fail(true) 保留为预期失败持续监测,而不是硬改或跳过
  6. generator 生成的用例会真实执行点赞、关注、收藏等操作,建议用隔离测试账号

4. 结论与使用建议

基于本轮实测,能下结论的有四条:

  1. AI 写 E2E 测试是真实可用的方向,机制上它读语义快照而非截图,天生比老式"截屏猜"方案稳;而且这一版验证了整个流程可以在 codex 对话里闭环——人只负责下指令和兜底登录,计划/代码/修复都是 agent 干的
  2. 反爬站点是最大的边界。抖音这种站,headless 一律被拦,必须 headed + 登录态,且对话里要明确引导有头模式;即便带 profile 也可能被要求验证手机号
  3. 断言过时和时序抖动是 AI 能帮上大忙的场景。healer 把搜索时序问题用"限定重试 2 次"修复、把站点真 bug 用 test.fail 固化为持续监测
  4. 别指望全自动。登录、验证码、手机号验证、滑块这些环节还是需要人工介入

✅ 适合

  • 内部业务系统、后台管理系统——页面相对稳定、无强反爬,全流程在 codex 里跑通收益明显

  • 页面频繁改版、选择器频繁失效的项目——healer 自动修复断言过时,价值最直接

  • 需要快速补齐测试覆盖的项目

⚠️ 慎用

  • 有强反爬、验证码、复杂人机校验的公开站点(如抖音)——需要 headed + 登录态 + 人工介入,自动化收益大打折扣

  • 对测试正确性零容忍的核心支付链路——建议保留人工 review