ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CodeBuddy 里 Playwright MCP 太重,TaoToken 供 Key 换 CLI 批量跑

CodeBuddy 里 Playwright MCP 太重,TaoToken 供 Key 换 CLI 批量跑 1. 一条 12 步用例让我决定把 Playwright MCP 摘下来Playwright MCP 在 CodeBuddy 里跑一条 12 步的登录回归MCP 模式下要触发十几次工具调用和十几次模型推理换成 Playwright CLI 之后模型只出一次 spec剩下的交给命令行批量跑。为了把「生成 spec」这一步的调用成本单独摘出来控制我把模型供应商切到了 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_introBase URL 统一填https://taotoken.net/apiKey 用官网控制台拿到的YOUR_API_KEY占位。先说清楚这不是一次「换个工具」的折腾而是测试执行环节的成本结构问题。自动化测试子 Agent 在整个 harness 工作流里轮次最多——每个 Wave 结束要跑一遍、每次修复循环又要重跑一遍。而 MCP 的设计前提是「让模型实时控制浏览器」这意味着浏览器的每一个动作都要模型在旁边看着点一下是一次工具调用填一次输入是一次工具调用截一张图又是一次工具调用每次调用都叠加一整轮历史上下文重新计费。用例越长滚雪球越狠。真正让我下决心的是重跑这件事。某次回归里有一条用例因为接口超时挂了我想单独重跑它MCP 模式下模型得把前面 11 步重新推理一遍才能走到出错那一步——因为「当前浏览器处于什么状态」这个信息只存在于会话历史里不在磁盘上。而 CLI 模式下那条用例本来就是一个 spec 文件npx playwright test login.spec.ts一条命令就完事跟模型没有任何关系。所以这次改造的目标很明确把「理解用例、翻译成可执行代码」和「执行代码、收集结果」拆成两件事前者留给模型后者交给 CLI。改完之后我在 CodeBuddy 的自动化测试 Skill 里做了两处调整——spec 生成环节接 TaoToken 的 Key 做调用执行环节切成 Playwright CLI 多 worker 批量跑。下面把可复现的部分完整写出来。2. 先把账算清Playwright MCP 的轮次到底花在哪在没有度量之前我的感受只有「慢」和「贵」说不清具体贵在哪。后来按工具调用轮次把一条 12 步的登录用例拆开问题就很直观了。MCP 模式的调用链是这样的模型先打开页面拿到一次返回再定位用户名输入框填值再定位密码框填值再点击登录按钮再等待跳转再断言 URL再截图比对……每一步都是一次 MCP 工具调用而每一次工具调用在网络层就是一个完整的 LLM 请求——请求里带着从会话开始到现在的全部历史。第 12 步的请求体积大约是第 1 步的十几倍。把它换成 CLI 之后同一条用例的轮次结构变成1 轮模型调用产出一个 spec 文件1 轮 CLI 执行模型不参与1 轮模型读取压缩后的结果摘要。对比表如下环节Playwright MCP 模式Playwright CLI 模式打开页面 / 跳转每次 1 轮推理写在 spec 里0 轮输入、点击等交互每步 1 轮推理写在 spec 里0 轮截图与视觉断言图片回灌 contextCLI 内部比对只回摘要失败后重跑重新推理全部前置步骤直接重执行 spec多用例执行单浏览器实例串行多 worker 并行LLM 侧总轮次12 步用例12~15 轮随步数线性增长2 轮与步数基本无关这张表里最关键的一行是最后一行。MCP 模式下 LLM 轮次和用例步数是线性关系用例写到 30 步就是 30 轮推理CLI 模式下轮次和用例数量、步数都脱钩了只和「你要生成几份 spec、要看几次结果」有关。测试规模越大差距越明显。顺带说一个容易被忽略的成本截图。MCP 模式里视觉断言产生的图片会作为工具结果回灌进 context一张全屏截图编码后的体积相当可观而且它会一直留在历史里跟着后续每一轮重新计费。CLI 模式把截图和比对都放在 Playwright 进程内部完成回到模型侧的只有「哪条断言失败、差异比例多少」这样几行文本。3. 分工改造模型只产出 spec浏览器交给 CLI改造后的自动化测试 Skill 目录大致长这样思路是正文只留骨架把模板和执行脚本都外置.codebuddy/skills/auto-test/ ├── SKILL.md # 骨架触发条件、阶段划分、执行入口 ├── references/ │ ├── spec-template.md # spec 模板生成阶段按需读取 │ ├── db-rules.md # 依赖 DB 的用例才读 │ └── report-schema.md # 汇总报告字段说明 └── scripts/ ├── run-specs.sh # 批量执行入口 └── summarize.mjs # 原始报告压缩SKILL.md里只保留三件事什么时候触发这个 Skill、分几个阶段、每个阶段调用哪个脚本。spec 模板代码那几十行 TypeScript不在正文里常驻只在 Phase A 需要生成文件时read_file一次。这样做的好处是 Skill 一激活不会把整个模板和规则表全量塞进上下文正文从近 200 行压到 120 行出头。模型在这一步的产出物是一份真实文件不是会话里的文本。这点很重要——文件落在磁盘上就意味着它可以被重跑、被 diff、被版本管理而会话里的文本一旦被压缩或截断就没了。spec 本身建议遵守两条书写约定能显著降低后续维护成本// tests/specs/login.spec.ts import { test, expect } from playwright/test; test.describe(登录回归, () { test(正确账号密码可进入工作台, async ({ page }) { await page.goto(/login); await page.getByLabel(用户名).fill(process.env.E2E_USER!); await page.getByLabel(密码).fill(process.env.E2E_PASS!); await page.getByRole(button, { name: 登录 }).click(); await expect(page).toHaveURL(/\/dashboard/); }); });第一条约定是选择器优先用getByRole/getByLabel这类语义定位别用脆弱的 CSS 路径。语义选择器不容易因为布局调整而失效用例稳定性直接决定后面的重跑次数。第二条是把账号密码这类环境相关的东西放到环境变量里spec 文件本身保持环境无关同一份 spec 在本地和 CI 上都能跑。4. 把生成 spec 的调用接到 TaoTokenspec 生成是整个链路里唯一必须走模型的一步也是最值得单独控制成本的一步——它调用频率不高但单次输入很长要带上用例描述、已有页面对象、项目约定输出也不短一份完整的 spec 文件。这一环我用 TaoToken 作为统一的模型入口Key 在官网控制台创建先访问 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_console 完成账号注册与登录进入控制台创建 API Key形如YOUR_API_KEY注意只保存一次所有工具的 Base URL 统一填https://taotoken.net/apiClaude Code 走settings.json用ANTHROPIC_*系列环境变量{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: 按官网模型列表选择模型 ID } }Codex 走的是config.toml配置结构和 Claude Code 完全不是一回事不要把那套ANTHROPIC_*变量名照搬过来那样不会生效# ~/.codex/config.toml model 按官网模型列表选择模型 ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat对应地在 shell 里导出export TAOTOKEN_API_KEYYOUR_API_KEY如果你同时用多个供应商、多个模型跑不同角色比如 spec 生成用强模型、结果汇总用便宜模型用 CC Switch 管理最省事本质上是维护三件套Base URL、API Key、模型名。每个 profile 填一组三件套切换时只改变量不动项目里的任何配置文件。这样测试环境的配置不会污染日常编码环境。这里有个实践建议spec 生成这一步的 prompt 里把「项目约定」放在前面、「当前用例描述」放在后面。原因是前缀稳定才能命中缓存而项目约定在同一个仓库里基本不变用例描述每次不同。顺序反过来的话每次请求前缀都在变缓存基本白给。5. Playwright CLI 批量执行从串行到多 worker执行侧的核心诉求就两个字批量。原来一条 spec 执行完再执行下一条LLM 侧要等 N 次现在一次把整个目录喂给 CLI让它自己开多个 worker 并行跑。先配一份playwright.config.ts把报告输出固定在 JSON 上方便后续压缩import { defineConfig, devices } from playwright/test; export default defineConfig({ testDir: ./tests/specs, timeout: 30_000, fullyParallel: true, retries: 1, reporter: [[json, { outputFile: reports/pw-raw.json }]], use: { baseURL: process.env.E2E_BASE_URL ?? http://127.0.0.1:3000, trace: retain-on-failure, screenshot: only-on-failure, }, projects: [{ name: chromium, use: { ...devices[Desktop Chrome] } }], });批量执行脚本一次接收整个 spec 目录#!/usr/bin/env bash # scripts/run-specs.sh set -euo pipefail SPEC_DIR${1:-tests/specs} WORKERS${PLAYWRIGHT_WORKERS:-4} mkdir -p reports npx playwright test $SPEC_DIR \ --workers$WORKERS \ --reporterjson \ --outputtest-results \ reports/pw-raw.json node scripts/summarize.mjs reports/pw-raw.json reports/pw-summary.json cat reports/pw-summary.json只重跑上次失败的用例也有一行现成命令不需要模型参与npx playwright test --last-failed --workers4原始 JSON 报告动辄几千行直接读进上下文等于把刚省下的 token 又送回去。所以要有一层压缩只保留统计数据和失败明细的前几行错误信息// scripts/summarize.mjs import { readFileSync } from node:fs; const raw JSON.parse(readFileSync(process.argv[2], utf8)); const failures []; const walk (suite, file) { for (const spec of suite.specs ?? []) { for (const test of spec.tests ?? []) { for (const result of test.results ?? []) { if (result.status failed || result.status timedOut) { failures.push({ file: file ?? suite.file, title: spec.title, status: result.status, error: (result.error?.message ?? ).split(\n).slice(0, 2).join( ), }); } } } } for (const child of suite.suites ?? []) walk(child, file ?? suite.file); }; for (const suite of raw.suites ?? []) walk(suite, suite.file); console.log(JSON.stringify({ passed: raw.stats?.expected ?? 0, failed: raw.stats?.unexpected ?? 0, flaky: raw.stats?.flaky ?? 0, durationMs: raw.stats?.duration ?? 0, failures: failures.slice(0, 20), }, null, 2));这样模型看到的是「12 条通过、1 条失败、失败者是哪个文件哪一步、错误前两行」这样的结构化摘要而不是整份 JSON 树。修复循环里它只需要针对失败项改 spec改完再调一次run-specs.sh不必回放任何浏览器操作历史。在SKILL.md里对应地写清楚执行阶段只允许调用scripts/run-specs.sh禁止逐条调用 Playwright 命令执行完成后只读reports/pw-summary.json禁止读pw-raw.json。把这两条写进 Skill 的骨架里能防止 Agent 自作聪明地绕回 MCP 式的操作路径。6. 轮次对照改造前后到底差多少我在同一个中等需求上对比了两种模式的执行阶段表现口径是「自动化测试阶段 LLM 侧的总轮次和总输入量」用本地日志统计不涉及线上环境。MCP 模式下执行阶段覆盖 5 个 spec、约 60 个交互步骤LLM 侧累计约 68 轮调用。因为每个动作都要模型决策下一步失败重试时还要从出错点往回推理实际轮次比交互步数还多一点。进程侧的耗时也很长单个浏览器实例串行跑60 步交互加等待页面加载整体接近十分钟。CLI 模式下同一批用例生成 spec 用了 5 轮每个 spec 一轮可以并行发起执行 1 轮run-specs.sh4 个 worker读汇总 1 轮失败修复再生成 1~2 轮。总共 8~9 轮 LLM 调用执行阶段的总输入量相比 MCP 模式下降了一个数量级进程侧耗时也降到原来的三分之一左右主要收益来自多 worker 并行和没有浏览器与模型之间的来回往返。需要说明的是这些数字和具体项目、用例复杂度、页面加载速度都有关系不要当成通用结论。真正可迁移的是那条结构性规律MCP 模式下 LLM 轮次随交互步数线性增长CLI 模式下轮次与交互步数解耦。你的用例越长、回归越频繁这个差距被放大的倍数就越多。7. 落地清单与几个容易踩的坑按我实际改造的顺序整理的清单如下第一步把现有 MCP 用例改成 spec 文件。不用一次改完挑最稳定的那批回归先改跑通链路再铺开。第二步在 CodeBuddy 的自动化测试 Skill 里加执行脚本明确「执行阶段不经过模型」这条边界。这一步做完成本曲线的斜率立刻变了。第三步把 spec 生成环节接到 TaoToken。前面链接里拿到的 Key 填进对应工具的配置注意 Claude Code 用ANTHROPIC_*、Codex 用config.toml的 provider 段两套配置不通用。第四步加报告压缩脚本。不做这层压缩前面省下来的会在读报告时还回去。坑位方面我遇到的主要有三个。一是选择器太脆spec 三天两头失效失效就要重新生成反而更贵。二是把--workers开得太大本地机器扛不住用例如因资源竞争随机失败flaky 数量上升。三是报告输出目录没有加到忽略列表CI 上每次跑完都产生一堆产物文件。还有一点值得强调不要用「同一需求跑两遍对比」的方式来评估这次改造。模型每次探索路径都不完全一样噪声比优化本身还大。执行阶段的对比应该用纯命令行的方式做——同样的 specMCP 路径和 CLI 路径各跑一次看进程耗时和外部调用次数这部分是确定性的100% 可复现。8. 小结把语义判断留给模型把执行留给脚本这次改造真正解决的问题不是「MCP 不好用」而是把两种性质完全不同的工作混在了一条链路里。用自然语言描述用例、把它翻译成结构化的、可重复执行的代码这件事需要语义理解模型不可替代而打开浏览器、点击、填值、断言、截图这些是完全确定性的机械动作本来就不该占用推理轮次。拆开之后有三个附带收益。一是可重跑spec 落在磁盘上失败重试变成一条命令。二是可并行CLI 的 worker 模型让用例数量不再等于等待轮次。三是可审计报告是结构化的 JSON压缩成摘要后进上下文人和模型都能快速定位问题。同样的思路其实可以往外扩任何「模型实时控制某个工具做一串固定动作」的场景都值得问一句——这串动作能不能先落成一份可执行文件再让命令行一次性跑完能的话模型就只需要出现在两端前端把意图翻译成文件后端把结果读成结论。中间那段最贵的、重复计费的执行过程交给脚本。如果你也在做类似的测试执行链路优化建议先从一条最稳定的回归用例开始改跑通 spec 生成、批量执行、结果压缩这三步再逐步替换存量用例。需要试模型效果可以直接走模型对话页如果要把这套链路长期跑在 CI 上Coding Plan 更划算Key 在控制台创建后填进上面的配置文件Claude Code 的完整配置项在文档里都有对应说明。模型对话体验https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_chatCoding Plan 套餐https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_plan创建 API Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_keysClaude Code 接入文档https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentplaywright_cli_doc补充一句run-specs.sh里的E2E_BASE_URL指向你要测的服务地址测试数据准备和清理建议在本地或测试环境完成不要把这个脚本指向任何生产环境地址。
返回列表