1. 为什么我把 Playwright MCP 接进了日常 UI 自动化测试
Playwright MCP 是一套把浏览器自动化能力封装成 MCP 工具、让大模型通过自然语言直接驱动浏览器的方案,能做什么?简单说就是:你不再手写一堆page.click()、page.fill(),而是让模型根据你的指令去点按钮、填表单、断言页面状态。适合谁?适合已经在写 UI 自动化、但被选择器维护和脚本调试拖慢节奏的测试同学,也适合想把冒烟用例交给 AI 辅助生成的团队。
我之前的痛点很具体:本地用 Playwright 写登录页冒烟脚本,选择器一改就得全量回归;同时手上还有 Cline、Claude Code 几个工具,每个都要单独配一份模型 Key,切换一次就要改一次环境变量,维护成本高得离谱。后来我把 Playwright MCP 的浏览器能力和模型调用统一收敛到 TaoToken 的 endpoint 上,本地调试和团队协作才顺过来。
这篇就按“先跑通本地录制与断言,再把 MCP 服务端 endpoint 与鉴权改到 TaoToken”的顺序写,给出可复制的 MCP 配置片段、环境变量清单,以及一条登录页冒烟用例的验证动作与预期输出。你照着做,能拿到一个能跑、能改、能复用的最小闭环。
2. Playwright MCP 本地跑通:录制、断言与最小冒烟用例
先把本地环境跑通,这一步不碰任何远端服务,纯本地验证 Playwright MCP 能不能驱动浏览器。我用的方案是官方@playwright/mcp,通过 npx 直接拉起,省去全局安装的污染。
第一步,确认 Node 版本。Playwright MCP 依赖较新的 Node,建议 18 以上:
node -v # 期望输出 v18.x 或更高第二步,安装 Playwright 浏览器驱动。如果你之前装过 Playwright 库,这步可以跳过;没装过就执行:
npx playwright install chromium国内网络下载慢的话,可以设置镜像环境变量再装:
export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright npx playwright install chromium第三步,用录制功能生成一份可靠的登录页脚本。这是我最推荐的新手入口,codegen会把你手动操作的过程转成带语义定位器的代码:
npx playwright codegen https://example.com/login浏览器打开后,你手动输入用户名、密码、点登录,右侧 Inspector 会实时生成代码。重点看它生成的选择器——优先用getByRole、getByLabel这类语义定位器,而不是#username这种脆弱 ID。录完把代码存成login.spec.ts。
第四步,写一条最小冒烟断言。录制出来的代码通常只有操作没有断言,我补上登录成功和登出两个关键校验:
import { test, expect } from '@playwright/test'; test('登录页冒烟', async ({ page }) => { await page.goto('https://example.com/login'); await page.getByLabel('用户名').fill('testuser'); await page.getByLabel('密码').fill('testpass'); await page.getByRole('button', { name: '登录' }).click(); // 断言:登录后 dashboard 可见 await expect(page.locator('.dashboard')).toBeVisible(); await expect(page).toHaveTitle(/Dashboard/); // 登出并确认回到登录表单 await page.getByRole('button', { name: '退出' }).click(); await expect(page.locator('.login-form')).toBeVisible(); });第五步,跑起来看结果:
npx playwright test login.spec.ts --headed预期输出是浏览器自动打开、完成登录登出、终端显示1 passed。如果这一步就失败,先别急着接远端,把本地选择器和等待逻辑调稳,否则后面排查会分不清是脚本问题还是配置问题。
本地跑通后,你会得到一个纯本地的 MCP 配置,形如:
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"], "timeout": 300 } } }这份配置只驱动浏览器,不涉及模型调用。接下来才是重点:把模型侧的统一 Key 接进来。
3. 把 MCP 服务端 endpoint 与鉴权改到 TaoToken 统一 Key
本地脚本能跑,但真正让 AI 驱动浏览器时,模型调用才是 Key 消耗的大头。我之前的做法是每个工具配一份 Key,Cline 一份、Claude Code 一份,改一次要动好几个文件。统一到 TaoToken 后,Base URL、Key、Model ID 三件套只维护一份。
先说清楚 TaoToken 在这里的角色:它是一个统一的模型调用入口,提供兼容常见协议风格的 endpoint,你把手上的工具都指向同一个 Base URL,用同一把 Key,模型 ID 按需切换。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。
第一步,去控制台创建 Key。打开 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,新建一个 API Key,复制出来先存到本地环境变量,别硬编码进配置文件。
第二步,配置环境变量清单。我习惯用一个.env文件管理,内容如下:
# TaoToken 统一入口 TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_MODEL_ID=claude-sonnet-4-5 # Playwright MCP 浏览器参数 BROWSER_TYPE=chromium BROWSER_HEADLESS=false MCP_TIMEOUT=300这里TAOTOKEN_MODEL_ID按你实际要用的模型填,不同工具对模型 ID 的写法可能略有差异,以控制台里列出的为准。
第三步,改 MCP 配置。以 Cline 的 MCP 配置为例,把模型侧指向 TaoToken,浏览器侧仍用 Playwright MCP:
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"], "env": { "BROWSER_TYPE": "chromium", "BROWSER_HEADLESS": "false" }, "timeout": 300 } }, "modelProvider": { "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "modelId": "${TAOTOKEN_MODEL_ID}" } }如果你用的是 Claude Code,配置走的是settings.json,路径通常在~/.claude/settings.json,片段如下:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }注意这里三件套必须齐全:Base URL 指向https://taotoken.net/api,Key 用控制台创建的,Model ID 填你实际要调用的模型。少任何一个,请求都会在鉴权或路由阶段失败。
第四步,如果你用 Codex 系工具,配置落在auth.json,结构类似:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "claude-sonnet-4-5" }改完配置后重启对应工具,让它重新加载环境变量。这一步别偷懒,很多“配置没生效”的问题就是没重启。
4. 验证请求:登录页冒烟用例的完整动作与预期输出
配置改完,得验证模型侧和浏览器侧是不是真的串起来了。我用一条登录页冒烟用例做端到端验证,动作和预期输出都写清楚,你照着比对。
验证动作分三步。第一步,在支持 MCP 的客户端里发起一条自然语言指令,让它驱动 Playwright MCP 完成登录流程:
打开 https://example.com/login,在用户名输入 testuser,密码输入 testpass, 点击登录按钮,等待 .dashboard 元素可见,确认页面标题包含 Dashboard, 然后点击退出按钮,确认 .login-form 重新出现。第二步,观察 MCP 工具调用链。正常情况下你会看到客户端依次调用navigate、fill、click、wait_for_selector这类工具,每一步都有返回。如果模型侧没接对,这一步会直接报鉴权错误,根本走不到浏览器。
第三步,用命令行再跑一次本地脚本做交叉验证:
npx playwright test login.spec.ts --headed预期输出:
Running 1 test using 1 worker 1 passed (3.2s)同时浏览器窗口里能看到完整的登录、登出动作。如果模型侧指令能跑通、本地脚本也能跑通,说明两条链路都正常。
再补一个模型侧的连通性验证,直接打一次对话接口,确认 Key 和 Base URL 没问题:
curl -X POST https://taotoken.net/api/v1/messages \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [{"role": "user", "content": "回复 ok"}] }'预期返回里包含模型输出内容,HTTP 状态 200。这一步过了,说明统一 Key 接入是通的。你也可以在模型对话页面 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 里手动发一条消息做可视化确认。
验证通过后,把这条冒烟用例固化进 CI,每次提交自动跑一遍,选择器改动导致的回归能第一时间发现。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
接入过程中我踩过的坑基本集中在几个固定报错上,逐个对照排查能省很多时间。
401 Unauthorized:最常见。原因通常是 Key 没读到或写错。检查三处:环境变量TAOTOKEN_API_KEY是否真的导出到当前 shell;配置文件里是否用了${TAOTOKEN_API_KEY}这种引用但工具没加载.env;Key 本身是否在控制台被禁用。排查命令:
echo $TAOTOKEN_API_KEY # 应输出 sk- 开头的完整 Key如果输出为空,说明环境变量没生效,重启终端或手动source .env。
local proxy failed / connection refused:这个报错通常出现在 MCP 服务端启动阶段,不是模型侧问题。检查npx @playwright/mcp@latest能否单独跑起来:
npx @playwright/mcp@latest --verbose如果报端口占用或命令找不到,先确认 Node 版本和 npx 缓存。清缓存重试:
npx clear-npx-cache npx @playwright/mcp@latest --verbosereading 'choices' of undefined:这个报错说明模型返回结构不符合预期,多半是 Base URL 或 Model ID 填错,请求打到了不兼容的端点。核对TAOTOKEN_BASE_URL是否为https://taotoken.net/api,以及 Model ID 是否在控制台支持列表里。改完重启工具。
OAuth 相关报错:如果你用的是 Claude Code 且之前登录过官方账号,它可能优先走 OAuth 而不是 API Key。检查settings.json里是否同时存在 OAuth 配置和ANTHROPIC_API_KEY,两者冲突时以显式 API Key 为准。必要时清掉旧的凭据缓存再重启。
元素定位超时:这个不是接入问题,是脚本问题。用codegen重新录制,或把选择器换成getByRole语义定位。加显式等待:
await page.waitForSelector('.dashboard', { state: 'visible', timeout: 10000 });排查顺序建议固定为:先确认 Key 和 Base URL,再确认 MCP 服务端能独立启动,最后才看脚本选择器。顺序反了容易在错误的地方浪费时间。
6. 长期编码与 Agent 场景:把统一 Key 用成基础设施
单次冒烟跑通只是起点。如果你打算把 Playwright MCP 长期用在日常回归、Agent 自动巡检这类场景,Key 的管理方式会直接影响维护成本。
我的做法是把 TaoToken 的 Base URL 和 Key 收敛成团队级环境变量,所有工具——Cline、Claude Code、Codex、以及各种 MCP 客户端——都从同一份配置读取。这样换模型、换额度、轮换 Key 都只改一处。长期跑编码和 Agent 任务的话,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合高频调用的场景。
接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。Claude Code 相关配置参考 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite 。
一个实用技巧:把 Playwright MCP 的浏览器参数和模型参数分开管理,浏览器侧用本地配置,模型侧统一走环境变量。这样本地调试和 CI 环境可以共用同一套脚本,只在环境变量层面区分。我实测下来,这套结构跑三个月没再因为 Key 切换出过问题。