☰
Agent Skills + Playwright:让 AI 自己写 E2E 测试并执行的架构拆解
2026/10/15 15:00:07 网站建设 项目流程

1. 为什么 E2E 测试需要 Agent Skills 而不是写死脚本

E2E 测试最让人头疼的地方,不是 Playwright 的 API 有多难,而是页面永远在变。今天登录框的name属性是email,明天前端重构变成user-email;今天下单流程三步走完,明天加了个优惠券弹窗。你辛辛苦苦维护的测试用例集,两周不跑就红一片,改起来比写新的还累。

Agent Skills 这套机制解决的正是这个矛盾。它不是一个预定义的工具函数库,而是一个文件夹:里面放一份SKILL.md指令文件、若干执行脚本和参考资源。Agent 读到SKILL.md里的description字段后,自己判断"当前任务该不该加载这个技能",加载后根据自然语言描述现场写代码、执行、读结果、失败重试。换句话说,测试脚本不再由人提前写死,而是 Agent 每次针对具体页面现场生成。

这跟 MCP Tool 的定位完全不同。MCP Tool 是"可枚举的原子操作"——搜索、发请求、读文件,参数固定,调用即用。但 E2E 测试是"无法穷举的任务":页面千变万化,交互路径组合爆炸,你不可能为每个场景预置一个工具。Skill 给的是自由度和上下文效率,MCP 给的是确定性和低延迟。两者不是替代关系,而是分层协作。

我试过把 Playwright 的完整 API 文档塞进 system prompt,结果上下文直接爆掉,Agent 反而抓不住重点。而 Skill 的渐进式披露设计——SKILL.md只放高频工作流,完整 API 参考下沉到API_REFERENCE.md按需加载——上下文占用差了十倍不止。这就是为什么"让 AI 自己写 E2E 测试"这件事,必须用 Skill 模式来做,而不是堆工具定义。

本文面向需要 AI 自主生成并执行端到端测试的研发场景,交付一套可跟做的架构:从SKILL.md配置模板、Playwright 执行脚本,到通过 TaoToken 统一 Key/API 通道接入 Claude Code 的完整闭环。核心检索词是 Agent Skills 与 Playwright 协作的 E2E 自动化架构,适合测试开发、前端工程和正在搭 Agent 工具链的同学。

2. TaoToken 前置:统一 Key 与 API 通道接入 Claude Code

在让 Agent 跑起来之前,得先解决"Agent 怎么连上模型"这件事。Claude Code 默认走 Anthropic 官方通道,但很多团队需要统一管理 Key、统一计费、统一审计。TaoToken 在这里扮演的是 API 网关角色:你拿到一个 Base URL 和一个 Key,就能把 Claude Code、Cline、Codex 等工具全部指向同一个通道,不用每个工具单独配一套凭证。

先拿 Key。打开 https://taotoken.net/api-keys ,登录后创建一个新 Key,复制出来。注意这个 Key 只在创建时完整显示一次,丢了就得重建。拿到后先存到环境变量里,别硬编码进脚本:

export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

接下来配置 Claude Code。Claude Code 读取的是~/.claude/settings.json,你需要把env段指向 TaoToken 的 Base URL。这里有个关键点:Anthropic 兼容接口的路径是/api,不要多加/v1,否则会 404。配置文件长这样:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

如果你用的是 Cline 或 Roo Code 这类 VS Code 插件,配置项名字不同但逻辑一样:Base URL 填https://taotoken.net/api,API Key 填你的 Key,Model ID 填claude-sonnet-4-20250514。这三件套——Base URL、Key、Model ID——缺一不可,任何一项写错都会导致 401 或模型不存在。

Codex 用户走的是~/.codex/auth.json,结构略有差异:

{ "OPENAI_API_KEY": "sk-你的key", "OPENAI_BASE_URL": "https://taotoken.net/api" }

配好之后先别急着跑 E2E,用一次最简单的对话验证通道是否通。打开 https://taotoken.net/api 对应的模型对话页面,或者直接在 Claude Code 里发一句"你好",能正常返回就说明 Key 和 Base URL 没问题。这一步很重要,因为后面 E2E 失败时你要能区分"是模型通道挂了"还是"是测试脚本写错了"。

关于 Coding Plan:如果你打算长期用 Agent 做编码和测试任务,可以了解 https://taotoken.net/coding-plan ,它针对高频调用场景做了额度优化。但本文的重点是架构,通道配通即可,不必纠结套餐细节。

3. 可复制配置:SKILL.md 模板与 Playwright 执行脚本

这一节是全文的核心,直接给可复制的文件。目录结构建议这样组织:

playwright-skill/ ├── .claude-plugin/ │ └── plugin.json └── skills/ └── playwright-skill/ ├── SKILL.md ├── run.js ├── API_REFERENCE.md ├── package.json └── lib/ └── helpers.js

先写SKILL.md。它的 YAML frontmatter 是 Agent 召回的关键,description要写具体能力和触发条件,别写空话:

--- name: playwright-skill description: Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, verify UI behavior, or run end-to-end checks. --- # Playwright Browser Automation ## 工作流 1. 检测本地 dev server:调用 `require('./lib/helpers').detectDevServers()` - 命中 1 个直接用,多个让用户选,0 个询问 URL 2. 测试脚本一律写到 `/tmp/playwright-test-*.js`,不污染项目目录 3. 默认可见浏览器:`headless: false` + `slowMo: 100` 4. URL 参数化:检测到的地址放进 `TARGET_URL` 常量 5. 断言只用条件等待:`waitForURL` / `waitForSelector` / `waitForLoadState`,禁止固定 sleep ## 执行方式 cd $SKILL_DIR && node run.js /tmp/playwright-test-xxx.js ## 禁止事项 - 禁止直接 `node /tmp/xxx.js`,必须走 run.js 统一入口 - 禁止在脚本里硬编码 URL - 禁止使用 `page.waitForTimeout()` 做断言

run.js是"零模块解析错误"的关键。它做三件事:统一代码输入方式、防御性包装 LLM 生成的代码、chdir 到 skill 目录保证require('playwright')能解析。核心逻辑如下:

const fs = require('fs'); const path = require('path'); function getCodeToExecute() { const args = process.argv.slice(2); if (args.length > 0 && fs.existsSync(args[0])) { return fs.readFileSync(path.resolve(args[0]), 'utf8'); } if (args.length > 0) return args.join(' '); if (!process.stdin.isTTY) return fs.readFileSync(0, 'utf8'); process.exit(1); } function wrapCodeIfNeeded(code) { const hasRequire = code.includes('require('); const hasAsyncIIFE = code.includes('(async () => {'); if (hasRequire && hasAsyncIIFE) return code; if (!hasRequire) { return ` const { chromium, firefox, webkit, devices } = require('playwright'); const helpers = require('./lib/helpers'); (async () => { try { ${code} } catch (error) { console.error('Automation error:', error.message); process.exit(1); } })();`; } return code; } function cleanupOldTempFiles() { const files = fs.readdirSync(__dirname); files .filter(f => f.startsWith('.temp-execution-') && f.endsWith('.js')) .forEach(f => fs.unlinkSync(path.join(__dirname, f))); } process.chdir(__dirname); cleanupOldTempFiles(); const code = wrapCodeIfNeeded(getCodeToExecute()); const tempFile = path.join(__dirname, `.temp-execution-${Date.now()}.js`); fs.writeFileSync(tempFile, code, 'utf8'); require(tempFile);

package.json只需要声明 Playwright 依赖:

{ "name": "playwright-skill", "version": "1.0.0", "dependencies": { "playwright": "^1.48.0" } }

lib/helpers.js里放 dev server 检测逻辑,扫描常见端口:

const net = require('net'); const COMMON_PORTS = [3000, 3001, 5173, 8080, 8000, 4200]; function checkPort(port) { return new Promise(resolve => { const socket = new net.Socket(); socket.setTimeout(300); socket.on('connect', () => { socket.destroy(); resolve(true); }); socket.on('timeout', () => { socket.destroy(); resolve(false); }); socket.on('error', () => resolve(false)); socket.connect(port, '127.0.0.1'); }); } async function detectDevServers() { const results = await Promise.all(COMMON_PORTS.map(async p => ({ port: p, alive: await checkPort(p) }))); return results.filter(r => r.alive).map(r => `http://localhost:${r.port}`); } module.exports = { detectDevServers };

装依赖:cd skills/playwright-skill && npm install && npx playwright install chromium。这一步会下载 Chromium 二进制,国内网络可能需要几分钟,耐心等。

4. 验证请求:一次完整的 Agent 测试会话与成功结果

配置就绪后,验证流程分三步:先确认通道通,再确认 Skill 被召回,最后确认测试脚本能跑通并返回截图。

第一步,在 Claude Code 里发一句自然语言任务:"测一下本地登录流程,看看能不能正常跳转到 dashboard"。Agent 应该先读取SKILL.md,然后调用detectDevServers()检测本地服务。如果检测到http://localhost:3001,它会生成一个测试脚本写到/tmp/playwright-test-login.js,内容大致如下:

const { chromium } = require('playwright'); const TARGET_URL = 'http://localhost:3001'; (async () => { const browser = await chromium.launch({ headless: false, slowMo: 100 }); const page = await browser.newPage(); await page.goto(`${TARGET_URL}/login`); await page.fill('input[name="email"]', 'test@example.com'); await page.fill('input[name="password"]', 'password123'); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); console.log('Login successful, redirected to dashboard'); await page.screenshot({ path: '/tmp/login-result.png' }); await browser.close(); })();

注意断言方式:waitForURL('**/dashboard')是条件等待,不是固定 sleep。这保证了脚本天然具备 Playwright 的自动等待语义,不会因为网络慢就 flaky。

第二步,Agent 执行cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js。run.js会 chdir 到 skill 目录、清理上次的临时文件、把脚本包装后写入.temp-execution-*.js、用require()执行。控制台应该输出:

Login successful, redirected to dashboard

同时/tmp/login-result.png生成截图。Agent 读到这个输出后,判断测试通过,返回结论给你。

第三步,故意制造失败验证自我修正能力。把登录密码改成错的,Agent 执行后会捕获到waitForURL超时,退出码非零。Agent 读到错误栈后,会重新分析页面结构,可能发现是选择器写错了,改写脚本重试。这个"生成-执行-读错-重写"的循环,就是 Agent Skills 相比固定脚本的核心优势。

如果你在验证时遇到模型不返回,先检查 TaoToken 通道:打开 https://taotoken.net/api 的模型对话页面发一条消息,能回就说明通道没问题,问题在 Skill 配置。如果模型返回了但 Skill 没被召回,检查SKILL.md的description是否写得太泛,Agent 需要明确的触发词才能加载。

5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth

排障这节按真实报错来,每个都给出定位路径和修复动作。

401 Unauthorized。最常见的原因是 Key 写错或 Base URL 路径不对。先确认ANTHROPIC_BASE_URL是https://taotoken.net/api,不要写成https://taotoken.net/api/v1。然后确认ANTHROPIC_AUTH_TOKEN是完整的sk-开头字符串,没有多余空格。如果用的是 Cline,检查它读的是settings.json还是插件自己的配置面板,两处可能不一致。修复后重启 Claude Code,环境变量不会热加载。

local proxy failed / connection refused。这个报错通常出现在你本地配了代理但代理没启动,或者ANTHROPIC_BASE_URL指向了localhost而本地没有服务。检查settings.json里有没有残留的http://127.0.0.1:xxxx配置,全部清掉换成 TaoToken 的地址。另外确认系统环境变量里没有HTTP_PROXY/HTTPS_PROXY指向失效的本地端口。

reading choices / Cannot read properties of undefined。这是 Playwright 脚本层面的错误,不是通道问题。典型场景是 Agent 生成的代码里page.click()的选择器匹配不到元素,或者waitForURL的 glob 写错了。定位方法:看run.js输出的错误栈,找到具体行号,然后在浏览器里手动验证选择器。修复方式是让 Agent 重新生成,或者在SKILL.md里补充该页面的选择器约定。

OAuth / authentication_error。如果你之前用 Claude Code 登录过 Anthropic 官方账号,它可能缓存了 OAuth token,跟settings.json里的ANTHROPIC_AUTH_TOKEN冲突。解决方法是清掉~/.claude/下的凭证缓存文件,只保留settings.json里的 Key 配置。Codex 用户同理,检查~/.codex/auth.json里有没有旧的OPENAI_API_KEY残留。

MODULE_NOT_FOUND: playwright。这是run.js的 chdir 没生效,或者node_modules没装。确认执行命令是cd $SKILL_DIR && node run.js <脚本>,而不是在项目根目录直接node run.js。然后确认skills/playwright-skill/node_modules/playwright存在,不存在就重新npm install。

Skill 没被召回。Agent 完全没读SKILL.md,直接开始瞎写。检查.claude-plugin/plugin.json是否存在且格式正确,Claude Code 靠它发现插件。然后检查SKILL.md的description是否包含用户任务里的关键词,比如用户说"测登录",description 里最好有 "login flows"。

排障时记住一个原则:先分层,再定位。通道层(TaoToken Key/Base URL)、召回层(SKILL.md/plugin.json)、执行层(run.js/Playwright)三层分开验证,不要混在一起猜。

6. 从技能定义到测试落地的闭环:CTA 与长期实践

把整条链路串起来看:TaoToken 提供统一的 Key 和 API 通道,Claude Code 作为 Agent 运行时,SKILL.md定义技能边界和触发条件,run.js作为统一执行入口消除环境不确定性,Playwright 负责真实浏览器操作。四层各司其职,任何一层出问题都能独立排查。

这套架构的价值不在于"用 AI 写测试"这个表象,而在于三个可复用的工程模式。第一,渐进式披露控制上下文成本——SKILL.md只放高频工作流,完整 API 参考按需加载,比把整本文档塞进 prompt 高效得多。第二,防御性代码包装兜底 LLM 输出——Agent 生成的代码经常缺require、缺 async 包装,run.js自动补全并加 try-catch,把未捕获异常转成退出码,Agent 能据此自我修正。第三,统一执行入口消除环境不确定性——process.chdir(__dirname)解决模块解析,临时文件延迟清理避免竞态,这些细节决定了 Agent 能不能稳定跑通。

实践建议:本地探索用headless: false+slowMo: 100,执行过程肉眼可见,截图返回结果,人机都能确认发生了什么;跑 CI 时切headless: true,让脚本以退出码表达结果,截图归档做失败排查。TARGET_URL一律参数化,否则脚本无法复用。给自动化流量打标,通过自定义 Header 让后端识别"这是测试流量",还能要求返回纯文本响应降低 Agent 解析成本。

如果你要把这套模式接进 CI,把 Agent 生成的脚本沉淀为固定用例,headless 模式跑进流水线。视觉回归方面,API_REFERENCE.md里覆盖了截图对比,配合像素级 diff 可以做无人值守的 UI 回归。多 Agent 协作场景下,Skill 模式会是标准化底座——给 Agent 装"技能",比给它一堆工具参数,更接近它真正需要的自由度和可靠性。

通道配置和 Key 管理走 https://taotoken.net/api-keys ,接入文档在 https://taotoken.net/doc ,模型验证用 https://taotoken.net/api 的对话页面,长期编码和 Agent 任务可以看 https://taotoken.net/coding-plan 。先把通道配通,再把SKILL.md和run.js落地,最后用一次真实登录测试跑通闭环,这套架构就能在你自己的项目里复用了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询