十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Windsurf 跑 Playwright MCP 查前端 bug,Key 用 TaoToken

Windsurf 跑 Playwright MCP 查前端 bug,Key 用 TaoToken 1. Windsurf 跑 Playwright MCP 查「第二条记录不初始化」模型通道先挂到 TaoTokenWindsurf 跑 Playwright MCP 查「第三方账单标准化」编辑页第二条记录不初始化这个前端 bug模型通道直接挂在 TaoToken 上——打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 Key在 Windsurf 模型设置里把 Base URL 填 https://taotoken.net/apiPlaywright MCP 照样用 npx 以 stdio 起。现象是这样的编辑页左侧是一串 collapse 配置项点第一条正常展示映射下拉和上传表头文件名点第二条下拉是空的「上传表头文件」那一栏也读不出文件名。后端接口/masterdata/third_bill_config/detail?id588返回里明明有两条configItemDTOS第二条的thirdBillConfigColumnDTOS也带着完整列定义人工 Review 前端代码看不出毛病。下面按「起 MCP → 接模型 → 复现 → 定位 forEach 中断 → 修一行 → 回控制台看消耗」的顺序走一遍。1.1 现象为什么固定卡在「第二条」而不是某一列第一条记录展开后thirdBillConfigDetailDTOS[0].thirdBillConfigColumnDTOS里的每一项都带着自己的结构映射下拉能正常读dataConfigDTOS上传文件名也能从originalBillFilePath推导出来。第二条记录展开时下拉列表直接是 empty文件名区域也是 empty但接口 JSON 里originalBillFileName和originalBillColumnCode都有值。这个差异说明问题不在接口也不在模板渲染的静态结构而在「第二条数据进入渲染前有没有被完整地走一遍初始化」。人眼 Review 最容易漏掉的就是这类代码看起来每一行都写了但循环在运行时提前中断第一条初始化完第二条压根没轮到。1.2 人工排查为什么在这儿失效前端 Review 时读的是「顺序执行的代码」而forEach遇到抛错会直接终止整个循环后面的索引不再进入回调。你在静态文件里看到的.forEach是完整的运行时的行为却是「跑一半就没了」两者不冲突。后端同事查了数据来源接口回复是第二条返回正常、字段都在于是排障就卡住了两边都说自己没问题现象只出现在第二条。之前也猜过变量名冲突、空值初始化、next方法覆盖originalBillFileColumn这类方向但改完都不见根因代码反而越补越厚。Playwright MCP 的价值就在这儿让 LLM 在真实浏览器里点开第二条、读 console、执行 evaluate把「运行时到底跑到哪一行」这件事摊开来看。2. Windsurf 里把 Playwright MCP Server 用 npx 起来Playwright MCP Server 的思路是把浏览器自动化能力包装成 MCP 工具交给 LLM 调用导航、点击、填表、截图、执行 JavaScript、抓 console 日志甚至发 HTTP 请求做接口验证。它不要求你先把页面结构写成测试脚本LLM 可以边看 DOM 边决定下一步。原文用的是executeautomation/playwright-mcp-server在 Windsurf 或 Claude Desktop 里以 stdio 方式启动这个路径仍然是最省事的。我们这一节只做两件事把服务装好、把 Windsurf 的 MCP 配置写对再确认工具列表在对话里能被识别。模型通道的事留到下一节先让 MCP 跑起来。2.1 Node 版本先过 18 这道坎这台机器上的 Node 曾经是 13.x装 MCP Server 时 npm 会刷出一串notsup Unsupported engine for modelcontextprotocol/sdk提示想要的版本是node 18。虽然包最终能装上但运行阶段很容易在 SDK 里出幺蛾子建议先把 Node 升到 18 或以上再动 MCP。node -v npm install -g executeautomation/playwright-mcp-server全局装完可以playwright-mcp-server --help看一眼命令是否可用。Windows 上如果提示「不是内部或外部命令」检查一下 npm 全局 bin 目录是否在 PATH 里这一步过不去后面 Windsurf 里写 npx 也照样起不来。装完不急着配先确认命令本身能跑。2.2 在 mcp_config.json 里按 stdio 写 playwrightWindsurf 的 MCP 配置在~/.codeium/windsurf/mcp_config.jsonWindows 是%USERPROFILE%\.codeium\windsurf\mcp_config.json也可以在 Windsurf 的 MCP 面板里点「编辑配置」直接打开。stdio 模式下command是npx参数走-y加包名这样不需要预装也能拉最新版本{ mcpServers: { playwright: { command: npx, args: [-y, executeautomation/playwright-mcp-server] } } }注意 JSON 里不能有多余逗号args必须是数组。保存后在 Windsurf 的 MCP 面板里刷新应该能看到playwright这个名字和它展开的工具列表playwright_navigate、playwright_click、playwright_evaluate、playwright_console_logs这些都在里面。看不到列表时先看面板有没有报「server failed to start」再看日志文件。2.3 浏览器二进制与 stdio 日志落点第一次使用时Playwright MCP Server 会自动检测并下载 Chromium、Firefox、WebKit 的二进制包控制台会显示下载进度。自动装失败可以手动补npx playwright install chromium浏览器缓存位置在 Windows 的%USERPROFILE%\AppData\Local\ms-playwright、macOS 的~/Library/Caches/ms-playwright、Linux 的~/.cache/ms-playwright。stdio 模式下有个细节容易误判MCP Server 的日志不会打到 Windsurf 的输出面板里而是写在~/playwright-mcp-server.log排查启动失败时直接看这个文件比反复重启快得多。3. 给 Windsurf 换模型通道Base URL 填 https://taotoken.net/apiPlaywright MCP 负责「手」模型负责「脑子」。手装好了脑子这次不分散在几家官方通道上统一从 TaoToken 走在 Windsurf 的模型设置里选兼容 OpenAI 协议的自定义端点Base URL 填https://taotoken.net/apiAPI Key 粘贴从控制台创建的那把。这样 Windsurf 侧只有一处模型出口MCP 工具调用消耗的 token 也集中在一本账上后面对消耗的时候不用来回切页面。注意 Base URL 末尾不带/v1这是本篇最容易填错的一处。3.1 创建 Key去 TaoToken 控制台拿一把打开 TaoToken 注册登录进入控制台的 API Keys 页面创建一把 Key复制出来先放在本地临时文本里等下要粘进 Windsurf。这把 Key 在本文里统一用占位符YOUR_API_KEY表示任何截图、笔记、配置文件里都不要出现真实明文。同一个页面顺手看一眼模型广场的当前列表把准备用的模型标识复制下来Windsurf 里的模型名要和它对上不要凭印象手写。3.2 Windsurf 模型设置里三个字段的填法Windsurf 不同小版本对「自定义模型」这一栏的叫法不太一样认准能填 Base URL 和 API Key 的表单即可。三个字段的对照关系如下字段填什么说明Base URL / API Endpointhttps://taotoken.net/api末尾不要加/v1也不要加其他路径API KeyYOUR_API_KEY从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 控制台创建Model ID以模型广场当时列表为准在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场复制模型标识填完保存回到 Cascade 对话框发一句最简单的「你好」能收到回复说明通道是通的。这一步不要跳过直接开 MCP 排障模型通道不通的话Windsurf 连工具调用都不会发起你会在 MCP 面板里看到一堆无意义的超时。3.3 这一节最容易填错的两个地方第一是把 Base URL 写成https://taotoken.net/api/v1多出来的/v1会让请求打到不存在的路径表现通常是 404 或者「模型不存在」。第二是把官网落地页地址粘进了 Base URL 框落地页是给人点的配置里要填的是接口地址https://taotoken.net/api两者不要混。如果发消息时返回 401先回控制台确认 Key 没被删、没复制多空格再检查是不是把 Key 填到了其他字段。4. 让工具清单对上前端 bugconsole_logs 与 evaluate 怎么分工MCP 起来、模型通了之后真正开始抓 bug。Playwright MCP 的工具集里跟这次排障关系最大的是三类导航与点击类负责把页面点到「第二条记录展开」这个状态playwright_console_logs负责拿页面 console 输出playwright_evaluate负责在页面上下文里直接读运行时数据。三者的分工是点击负责制造现场console_logs 负责看执行轨迹evaluate 负责验证组件实例里的字段到底有没有值。用自然语言描述任务LLM 会自己决定调哪个工具、调几次。4.1 先把页面点到「第二条 collapse 展开」给 Windsurf 的描述可以写得很具体比如打开第三方账单标准化编辑页id 用 588等待左侧配置列表渲染出两个 collapse 项点击第二个 collapse 的标题然后截图并把页面上所有下拉框的可见文本读出来。对应到工具调用大致是playwright_navigate→playwright_click→playwright_get_visible_text或playwright_screenshot这一串。这一步的目的是复现不用急着解释先让 LLM 把「第二条展开后确实是空的」这句话用工具结果确认下来。playwright_navigate - 打开编辑页 playwright_click - 点击第二条 collapse 标题 playwright_get_visible_text - 读取展开区域文本 playwright_screenshot - 存一张现场图4.2 playwright_console_logs 抓出「只打印了一条」之前的排查里加过console.log用来确认forEach到底跑了几轮。把这一步交给 MCP让 Windsurf 在点击第二条之后再调用playwright_console_logs把日志按关键字过滤比如搜init column或你实际打的标记文本。预期结果是只看到第一条记录的输出第二条一条都没打。这个现象非常关键它把问题从「第二条某个字段没渲染」收窄成「第二条根本没进入初始化流程」排障方向一下子从十个猜测变成一条线。4.3 playwright_evaluate 读 dataConfigDTOS 到底是不是 undefined光看日志还不够得确认运行时字段状态。playwright_evaluate可以在页面上下文里执行一段 JS 再返回结果思路是顺着 Vue 实例或 DOM 上挂的组件引用读到第二条明细的列配置数组检查其中第一项的dataConfigDTOSconst items document.querySelectorAll(.collapse-item); if (items.length 2) return only one collapse item; const vm items[1].__vue__; const detail vm?.configItemDTOS?.[1]?.thirdBillConfigDetailDTOS?.[0]; return JSON.stringify({ columnCount: detail?.thirdBillConfigColumnDTOS?.length ?? no columns, firstField: detail?.thirdBillConfigColumnDTOS?.[0]?.standardEnglishName ?? null, dataConfigType: typeof detail?.thirdBillConfigColumnDTOS?.[0]?.dataConfigDTOS });实际项目里挂实例的字段名不一定是__vue__把这段当成模板改就行核心是让 LLM 拿到「dataConfigDTOS是undefined还是[]」这个事实。如果返回dataConfigType:undefined那根因基本锁定了。5. 从复现到根因forEach 断在第一条有了日志和 evaluate 两路证据接下来是定位到具体那一行。这类 bug 的排查顺序和普通前端调试一样先确认循环有没有跑完再确认抛错发生在哪最后确认是哪个字段没初始化。用 MCP 的好处是每一步的中间结果都留在对话里LLM 不会像人一样「看到第一条正常就顺手往下改」它会等你把证据补齐再给结论。下面按实际用到的三次工具调用还原一下。5.1 先证明「第二条没进循环」而不是「进了但没渲染」当时试过几种猜测内层forEach的参数item和外层循环变量重名改成column之后无变化fileColumns初始化为null改成[]之后无变化next方法里把originalBillFileColumn覆盖成空串改成条件赋值之后下拉出现了但「上传表头文件」仍然空。这些尝试的共同问题是都在下游堵漏没有确认上游循环有没有走完。真正的转折是把console.log打进循环体第一行然后发现第二条记录根本没触发。5.2 try-catch 兜出来的 TypeError确认循环中断之后下一步是搞清楚抛了什么。让 Windsurf 把playwright_evaluate和日志结合或者在代码临时加一层try-catch把异常对象打出来得到的关键信息是TypeError: Cannot read properties of undefined (reading length)访问点落在item2.dataConfigDTOS.length。第二条记录里新 push 进去的列配置对象没有dataConfigDTOS这个属性读取.length直接抛错forEach随即终止后面的列全部没初始化下拉和文件名自然都空。5.3 修在 push 那一行而不是下游补代码根因确认后修复只有一行在往thirdBillConfigColumnDTOS里 push 新列的时候把dataConfigDTOS一起初始化成空数组和已有列保持同样的结构。for (const pair of extraColumns.split(,)) { const [standardColumnName, standardEnglishName] pair.split(:); item1.thirdBillConfigColumnDTOS.push({ standardColumnName, standardEnglishName, originalColumnCode: null, separateSettlement: false, amountSymbol: false, isSet: false, valueType: 1, dataConfigDTOS: [] }); }改完之后刷新页面点第二条 collapse下拉有值上传表头文件名也正常显示。回头再看之前那几处「补丁」全部可以撤掉——它们绕过了现象没有碰到根因。这也是这次排障最值得记的一条数据初始化异常时先确认循环是否完整执行再去下游加兜底。6. 修完别急着关窗口回 TaoToken 对一下这次会话页面修好只是前半程。这次排障里Windsurf 通过 MCP 发起了不少工具调用每一次都要经过模型通道消耗是实打实的。趁着会话还在回控制台对一遍这次调试的调用记录既能确认通道填得对也能给后面做预算。更重要的是把「读 console → evaluate → 定位」这套流程沉淀成可复用的提示词下次遇到类似的初始化异常可以直接套。6.1 用量、模型名和调用记录三件事一起核打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 登录控制台先看用量页里的近期调用时间范围选今天应该能看到这段时间的请求模型名和你填进 Windsurf 的一致。如果一条记录都没有不是你这次没消耗而是请求根本没走到通道上回去检查 Base URL 是不是被改回了别的地址。再看 Key 的使用情况确认没有异常来源顺手把临时放在本地的 Key 备注改清楚方便下次区分。6.2 下一步从这次会话带走的三个动作第一个动作把 Playwright MCP 的提示词模板存下来比如「打开 X 页面点击第 N 个 collapse用 playwright_console_logs 过滤关键字再用 playwright_evaluate 读目标字段类型」。第二个动作换一个前端 bug 试试同一套流程比如上传组件不刷新、下拉联动丢值验证这套工具组合的适用面。第三个动作如果这类排障以后会经常做去 Coding Plan 看看额度方案是否够用Key 不够了就在 控制台 API Keys 再建一把。想先在网页上试一条消息确认模型通不通可以直接去 模型对话。如果之后还要把这套通道接到别的命令行工具上Claude Code 接入文档 里的环境变量对照可以当参考虽然这篇的场景是 Windsurf但 Base URL 和 Key 的填法思路是同一套。这次的教训不在 MCP 有多神而在于「让工具把运行时的证据摆出来」这件事比反复读代码有效得多。dataConfigDTOS少初始化一次整条循环就断在第一条第二条记录看起来像「接口有问题」其实是前端自己没走完。下次再遇到「第一条正常、第二条空白」这种规律性现象先怀疑循环中断再去查字段。
返回列表