最近两个月我几乎把下班后的时间都扔在了一个项目上——给浏览器做一个AI代理工具让AI模型直接指挥浏览器完成那些重复得要命的前端操作。先说清楚这里的代理不是网络层面的代理而是操作代理把你的鼠标键盘动作交给一段脚本由脚本在浏览器里代为执行再由AI来调度这段脚本该怎么走。我做了这个工具之后以前每天要花两三个小时做的数据录入、页面巡检、用例回归现在只要写一句自然语言指令比如把这20个商品按模板创建好分类选测试分类价格随机300到500剩下的工作就交给AI拆步骤、浏览器执行、结果校验。这篇文章不是讲原理的论文也不是产品的宣传稿就是把我自己从零搭这个AI浏览器代理工具的真实过程、架构设计、核心代码、踩过的坑全部摊开来讲。适合的人群很明确做自动化测试的、搞RPA的、每天被浏览器重复操作折磨的运营或研发同学。哪怕你完全没碰过AI接口只要会一点JavaScript和Python跟着这篇文章的思路也能搭出一个能用的最小版本。1. 先想明白这个工具到底要解决什么痛点1.1 传统浏览器自动化的三个老大难我做了很多年自动化用的最多的就是各种自动化测试框架。工具本身都很成熟但真正跑起来永远绕不开三个问题。第一个是选择器脆弱。今天写好的CSS选择器明天前端加了个样式类名脚本就废了。更别提现在前端框架普遍动态渲染元素的class和id频繁变化光维护选择器就能耗掉一半精力。第二个是流程变化快。业务需求一调整页面流程从三步变四步脚本就得从头改。第三个是多步骤状态依赖。页面之间的跳转、弹窗的时机、接口返回的快慢每一步之间都有隐含的顺序脚本一旦在中间断了很难自动恢复。这就像你拿着一张非常精确的纸质地图去走一条每天都在修路的街道——地图本身没错但现实已经变了。我需要的不是一张更精确的静态地图而是一个能在我身边看着路况、随时告诉我前面封路了左拐的导航。传统自动化框架是前者按图索骥而AI代理工具的方向是后者动态决策。1.2 我对AI浏览器代理工具的能力定义做这个东西之前我花了不少时间想清楚它的边界。不是所有浏览器自动化都能做也不是所有场景都适合AI介入。我最终定义出的核心形态是这样的一个浏览器扩展负责采集页面信息、接收指令、执行操作一个AI决策模块负责理解用户用自然语言表达的目标结合当前页面状态转换成一条一条的可执行操作指令一个结果反馈回路每执行一步就把页面变化反馈给AI让AI决定下一步怎么走或者什么时候停。它不是一个通用爬虫因为我对内容提取的需求很少它也不是一个RPA平台因为我不需要复杂的流程编排和人工配置流程节点。它要解决的核心问题只有一个把人盯着页面反复点击变成人用一句话描述目标AI看着页面把事办了。这个定位让我在做技术选型的时候少走了很多弯路。我知道自己不需要做调度引擎也不需要做复杂的规则引擎只需要把一个最小闭环跑通感知页面、理解指令、执行操作、反馈调整。1.3 和现有自动化测试框架的关系我日常工作里大量使用pytest这类测试框架也用过不少UI自动化库。很多人可能会问有了这些为什么还要搞AI代理工具我自己用下来的感受是现有框架强在可预期、可重复、可断言AI代理强在面对不确定性时能随机应变。这两者不是替代关系而是互补关系。举个例子我在用pytest跑接口自动化的时候测试步骤是固定的、参数是明确的断言逻辑也是预先写好的这时用框架非常合适。但当我需要去做页面巡检比如打开所有菜单看看有没有页面报错时菜单层级、页面数量、每个页面的渲染特点都不一样用传统脚本写出来极其痛苦而AI代理只需要一句话的指令就能开始干活。所以我最后做出来的架构里AI代理工具和pytest框架是打通的AI代理负责处理灵活的页面操作pytest负责跑固定的断言逻辑和生成测试报告两者通过日志和文件系统交换信息。2. 核心架构拆解三层各干各的活2.1 感知层浏览器端的事件监听与页面快照架构的第一层是感知层跑在浏览器扩展的content script里职责是让AI看得见当前页面。我之前踩过一个很直接的坑一开始想直接把整页HTML扔给AI模型结果页面稍微复杂一点token就超限了而且大模型面对一堆CSS类名和嵌套div根本提取不出有效信息。后来我把感知层改成了结构化摘要的模式。content script在页面加载完成后主动扫描页面上的可交互元素包括按钮、输入框、链接、下拉框、文本区域以及带role属性的自定义组件。对每个元素提取它的标签名、id、name、placeholder、可见文本、href、在页面中的坐标还有元素是否可见、是否可点击这些状态信息。然后经过一轮筛选把真正有价值的元素放进一个JSON数组里。这个数组就是我喂给AI的页面快照。它不需要包含所有DOM信息只需要让AI知道当前页面有哪些东西可以操作、大概是什么状态。快照生成逻辑里我加了一个上限比如最多收集80个关键元素超出部分优先保留表单控件、按钮和链接因为这类元素才是操作的主要对象。2.2 决策层AI模型把自然语言变成操作序列决策层是整个工具的大脑。我的实现方式是准备一套固定的提示词模板把用户指令和页面快照一起塞进大模型接口要求模型返回一个结构化的操作序列。这套提示词的写法很关键。我在模板里明确告诉模型三件事第一你是一个浏览器操作助手你的环境是一个真实的网页第二当前页面摘要如下包含若干可操作元素第三用户的目标是某某请输出一个JSON数组数组每个元素代表一步操作操作类型限定为click、input、scroll、wait、assert这几种。最重要的是我在提示词里加了硬性约束只准输出JSON不要任何解释性文字。因为如果模型在JSON前后加了Markdown代码块或者解释语句我在代码里解析的时候就会多一道清洗逻辑而且容易出错。模型返回的操作序列大概是这样的格式[ {op: click, target: btn_create, remark: 点击创建商品按钮}, {op: input, target: name_input, value: 自动化商品-001, remark: 填写商品名称}, {op: select, target: category_select, value: 测试分类, remark: 选择分类}, {op: click, target: btn_submit, remark: 提交表单} ]决策层的另一个设计是两步一确认。不是让AI一口气生成30步操作从头执行到尾而是先生成前几步执行完把页面新快照反馈回去让AI再生成后续步骤。这个设计大幅提高了成功率因为页面在操作过程中状态一直在变AI基于过期的快照做决策必然会出错。2.3 执行层操作指令的落地与结果校验执行层也在浏览器扩展里收到决策层下发的操作序列后通过chrome.tabs.sendMessage把指令发给对应标签页的content script然后由content script调用真实的DOM API完成操作。这里有一个很重要的原则所有操作必须在真实的浏览器环境里执行不能用模拟请求替代。原因很简单现在很多前端框架的事件绑定是原生事件监听单纯的JavaScript赋值触发不了业务逻辑更别说很多页面有复杂的埋点和联动逻辑。所以执行层面对操作类型分别做了不同的实现。点击操作不是简单地调el.click()就完了。我会先做三步预处理把元素滚动到可视区域、检查是否有遮挡层、等待元素变成可点击状态。输入操作则遇到了React框架的坑我后面会专门讲。校验操作会在关键节点插入assert例如判断某个元素是否出现、文本是否匹配、按钮是否变成禁用态校验不通过就触发打断机制。2.4 为什么这套架构能扛住页面改版很多人会问一个问题页面改版之后这套东西是不是也废了我的答案是比传统脚本抗打很多但不是完全免疫。关键在于AI代理工具的核心逻辑不依赖某个具体选择器。页面改版后感知层会自动采集新页面上的元素只要按钮还在、输入框还在AI就能通过元素的文本、位置、placeholder这些语义特征重新定位到它们。传统脚本里写死的css选择器通常是最先失效的而语义特征基本是稳定的。比如一个登录按钮可能class从btn-primary变成了btn-secondary但它的可见文本还是登录。感知层采集的是语义信息决策层匹配的也是语义信息这就相当于把认路牌升级成了认目的地。当然如果业务逻辑真改了比如登录从表单改成扫码那AI也救不了这个边界要承认。3. 动手搭建关键模块的落地实现3.1 浏览器扩展的整体骨架我用的是Manifest V3规范开发扩展兼容Chrome和Edge。扩展一共三个核心文件manifest.json声明权限和脚本注册background.js负责和AI接口通信content.js负责页面感知和操作执行。{ manifest_version: 3, name: AI Browser Agent, version: 0.1.0, permissions: [storage, activeTab, scripting], host_permissions: [all_urls], background: { service_worker: background.js }, content_scripts: [ { matches: [all_urls], js: [content.js], run_at: document_idle } ] }这里有一个关键决策host_permissions用了all_urls。一开始我想限制成几个固定域名结果发现它的使用场景是各种内部系统域名根本列不全索性放开了。权限放开之后扩展会在每个页面都注入content script对平时浏览会有一些性能影响所以我在content.js里做了个开关只有显式开启自动化模式时才执行页面扫描和监听逻辑。background.js承担的是AI接口转发的职责。content script不能直接跨域调用大模型接口因为浏览器的CORS会拦截所以我把AI请求统一放在background的service worker里通过chrome.runtime.sendMessage进行通信。如果你们公司有内网部署的模型服务也建议走这条链路把API密钥放在background里相对安全一些不要暴露在页面上下文里。3.2 让AI看得懂页面上下文构造的逻辑AI决策的质量一半取决于模型本身另一半取决于你怎么把页面信息喂给它。页面快照不能太原始也不能太精简。我最终采取的格式是这样的把可交互元素按列表结构整理每个元素包含编号、类型、关键属性和描述。{ current_url: https://example.com/products/create, page_title: 创建商品, elements: [ {id: 1, tag: input, name: product_name, placeholder: 请输入商品名称}, {id: 2, tag: button, text: 保存}, {id: 3, tag: select, name: category, options: [测试分类, 正式分类]} ] }这些信息不是随便收集的。我特意规整成AI容易理解的格式每个元素有一个数字id后续操作指令里的target都引用这个id而不是直接写选择器。为什么这样做因为模型生成文字的能力很强但生成准确选择器的能力很弱很容易编出一个根本不存在的东西。数字id是从快照里来的模型只需要说点击元素1执行层去查id就能找到真实的DOM节点准确率提升非常明显。另外提示词里我还加入了页面的操作痕迹信息比如刚刚执行过什么操作、页面上有没有出现toast提示。这相当于给AI增加短期记忆避免它在多步操作中迷失方向。3.3 操作执行从JSON指令到真实浏览器动作执行层的实现是整套系统中最容易出错的地方。我直接贴一段简化版的操作执行函数展示如何把一个AI返回的动作变成真实的DOM操作async function executeAction(action) { const targetElement getElementByIdInSnapshot(action.target); if (!targetElement) { return { success: false, error: element_not_found }; } switch (action.op) { case click: targetElement.scrollIntoView({ block: center }); await waitForElementClickable(targetElement); targetElement.click(); break; case input: targetElement.focus(); const setter Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, value ).set; setter.call(targetElement, action.value); targetElement.dispatchEvent( new Event(input, { bubbles: true }) ); break; case scroll: window.scrollBy({ top: action.value || 300, behavior: smooth }); break; case wait: await sleep(action.value || 1000); break; } await sleep(300); return { success: true }; }这段代码里最值得关注的是input操作。如果你用常规的element.value xxx然后dispatchEvent的方式去给React或者Vue的受控组件赋值页面上的值和框架内部state不会同步后续提交时数据还是空的。必须通过原生setter修改值再触发input事件框架才能感知到变化。这个细节我当初排查了很久也是传统自动化脚本里最容易踩的坑之一。执行完每一步操作后content script会返回一个结果对象包含成功状态和错误信息。background收到结果后有两种分支成功就继续执行下一个动作失败就把错误信息追加进上下文让AI模型重新规划后续操作最多重试两次。这个自我纠错机制让整个过程在真实复杂页面上的成功率从不到70%提升到了接近90%。3.4 和pytest等自动化框架的联动方式AI代理工具如果脱离测试体系就只是一个好用的RPA工具称不上自动化能力。所以我把它的操作过程和pytest做了打通。我在pytest里写了一个fixture每次跑用例之前先启动带扩展的浏览器实例然后读取一条用户指令比如打开商品列表页下载前20条商品数据。AI代理执行完操作后会把完整的操作记录、页面摘要、截图存档全部写入一个统一的JSON文件。pytest的断言逻辑不关心AI是怎么操作的只关心最终结果是否满足预期比如文件是否存在、数据条数是否够20。这样一来原来需要人工去点半天才能准备好的测试数据现在就变成了一条自动执行的用例。# conftest.py 简化示例 def run_ai_agent_task(instruction: str, output_dir: str): # 调用扩展的调度接口传入自然语言指令 # 等待完成后从output_dir读取执行结果 result read_agent_result(output_dir) assert result[status] success, AI代理任务执行失败 return result这个联动的价值在于我可以用pytest写断言、失败重跑、报告生成这些成熟能力而把怎么操作页面这块完全不稳定的部分甩给AI去实时决策。现在我的日常流程基本就是pytest里定义了一批需要准备数据的场景setup阶段调用AI代理自动造数据测试完成后AI代理再自动清理数据。4. 跑通真实场景三个可以直接抄作业的案例4.1 场景一批量创建测试数据有一次需要在一个内部后台创建20个测试商品用于后续分页测试。我写的指令是进入创建商品页面创建20个商品名称从压测商品001递增到压测商品020价格设为300到500之间的随机整数库存随机50到200分类选自动化测试保存后检查页面是否出现保存成功提示。如果保存失败截图保存到本地。AI模型返回的操作序列包含了打开页面、点击创建按钮、逐个填写字段、每次保存后判断提示文本、失败则截图的完整路径。整个执行过程大约花了6分钟最终结果18个成功2个失败。我查看失败截图后发现失败原因是分类下拉框的选项没有等接口返回就点击了保存导致表单校验拦截。后来我在提示词模板里加了一条点击保存前必须先确认下拉框选项已完成加载重新执行后成功率变成了100%。这个例子说明一个问题AI代理不是万能的但它的错误是可调试的。你只要告诉它失败原因它下一次执行就能避开同样的坑。4.2 场景二全站菜单遍历巡检这是一个典型的用脚本写很麻烦但用自然语言很轻松的场景。前端项目有十几个一级菜单每个菜单下有两到五级子菜单总共几十个页面。我需要确认每个页面都能正常打开、页面标题不为空、控制台没有报错、关键按钮都存在。传统脚本的做法是维护一张菜单路径表然后逐条去点。问题在于菜单路径一旦变了表就废了。AI代理的做法就简单了指令是遍历左侧导航菜单的所有层级进入每一个菜单对应的页面等待页面加载完成检查页面标题是否为空、页面是否出现500错误文案最后把所有页面的访问结果汇总成表格。AI会自己判断菜单怎么展开、子菜单怎么点击、加载完成后怎么校验。整个巡检跑了大约15分钟最后生成的表格里有每个页面的URL、标题、状态码、页面报错情况。以前这个巡检我一个月才敢跑一次因为没有两三个小时做不下来现在每周跑一次毫无压力。4.3 场景三失败用例的自动复现与诊断pytest跑完UI测试之后经常有失败的用例。以前的做法是人手动跑到对应页面重放步骤看看到底哪里出了问题。现在我把这个环节也交给了AI代理pytest失败时把失败步骤、当时页面的DOM摘要、报错信息打包传给AI代理AI代理重新打开对应页面按步骤重放每走一步截一张图最后把所有截图和观察到的页面现象写入一个Markdown报告。这个报告对排查问题特别有用。有一次用例失败原因是页面上出现了一个非预期的弹窗遮挡了提交按钮。AI代理在重放时不仅截图了还在报告的描述里写了一句页面右下角出现了引导弹窗已尝试点击遮罩层关闭未成功。这种观察能力是传统脚本无法提供的。5. 踩坑记录三段完整故障排查与分析5.1 点击总是失败从选择器到元素的五百里路最早跑通最小闭环的时候我最常遇到的一个报错就是目标元素无法点击。第一次排查我确认了DOM里确实存在这个元素选择器也定位到了但click事件就是没有触发页面跳转。后来我在控制台手动执行了一遍targetElement.click()发现弹窗正常弹出了说明问题不在事件本身而在于执行时序。页面上的按钮是异步加载的AI决策的时候元素还不存在等执行的时候元素虽然加进来了但事件监听还没绑定完毕。解决方法是执行点击之前先做三重保障用MutationObserver监听DOM变化等待元素出现、判断元素是否可见、判断元素是否被其他元素遮挡。如果遮挡先点击遮罩的关闭按钮或者用Escape关闭弹窗。这个看似笨办法的组合把点击操作的成功率从70%提到了95%以上。5.2 AI返回的操作序列和页面实际状态不一致有个很典型的问题AI模型基于旧快照生成了操作序列但执行到第三步的时候页面已经跳转了后续几步全部作废。最开始我的方案是一次性生成所有操作然后顺序执行结果发现流程稍微长一点就各种翻车。后来我改成两步一确认。AI先生成操作序列但只执行前两三步执行完立刻重新拉取页面快照连同执行结果一起喂回给模型让模型基于新状态给出后续操作。这个改动看着简单效果非常显著。我把执行过程中的上下文截断、状态过期、意图漂移这三类典型故障基本都消灭了。现在我的所有AI代理流程默认都采用这种增量决策方式你可以把它理解成开车导航不是出发前一次性规划完全程而是每过一个路口就重新计算一次路径。5.3 浏览器扩展权限和页面之间的拉扯还有一个排查了很久的问题扩展在部分内部系统页面上完全不生效控制台报错Extension context invalidated。这个报错的原因通常是扩展重新加载后旧页面里的content script已经失效但后台还在尝试发送消息。解决方式是给消息发送增加一个异常捕获如果检测到扩展上下文失效就重新注入content script或者提示用户刷新页面。另一个权限问题是页面本身处于iframe中。很多后台系统的内容区域是iframe嵌套的主帧的content script默认访问不到iframe内部。我调整了content script的注册配置加上了all_frames: true并且在进行元素定位时不能直接通过id匹配需要在指定的iframe上下文里查找。这个问题光定位就花了我大半天时间排查的方法是先确认元素在哪个frame再逐个frame搜。权限这块还有个小建议如果你是本地开发测试用加载已解压的扩展程序模式最方便如果要在团队内共享建议打包成crx文件分发但要注意每次更新扩展版本后旧版本在页面里已经注入的content script会失效需要设计成可以在新版本加载后统一刷新已打开的标签页。6. 能力边界与下一步最值得投入的方向6.1 当前技术下它做不好的事做这个项目的过程中我越来越清楚AI浏览器代理工具的边界在哪里。验证码是绕不过去的门槛不管是图形验证码还是滑块验证在无头环境下都是大麻烦安全考量决定这类行为不应该被自动化。复杂拖拽交互也比较吃力比如画布上的元素拖拽、表格行拖拽排序AI生成的坐标往往不够精准操作容易失败。多标签页的强协同也还不成熟比如在A页面复制数据、B页面粘贴并联动修改这种跨标签页的状态同步目前的实现比较笨拙。最后是那些需要灰度判断的事情比如根据用户反馈判断这个页面排版是否合理AI代理能收集信息但最终决策还是得人来做。边界意识很重要。不要试图让AI代理去干它不擅长的事否则成功率会直线下降然后你会对整个方案失去信心。我现在的原则是能用自然语言一句话描述清楚、步骤不超过15步、中间没有强校验码的任务交给AI代理其他的还是用传统自动化框架写死逻辑更可靠。6.2 夯实经验库让代理越用越顺手我目前最有成就感的不是AI本身多聪明而是它积累的操作记忆越来越多。每次AI代理成功完成一个任务我会把完整的操作序列和页面快照归档到一个经验库文件里。下次遇到类似任务时我会把历史经验作为few-shot示例放进提示词AI模型就能直接参考上次的操作路径而不用每次都从零开始推理。比如填写商品表单这类任务第一次可能要尝试三四轮才能走通第十次的时候就基本一步到位了。经验库的价值在于它把AI的随机应变和传统自动化的稳定可复用结合在了一起。页面不变化的时候经验库提供精确路径页面变化的时候AI再随机应变地调整路线。6.3 给同样在折腾这类工具的同学几点实在建议最后说几句个人的体会不算总结就是踩过坑之后想说的话。第一不要一上来就追求全自动。我在中间态加了一个操作预览功能AI生成操作序列后先把计划展示出来快速浏览一眼没问题再放行执行。这个模式虽然没有全自动那么爽但在办公场景下大大减少了误操作的风险也让老大们更愿意信任这套东西。第二上下文窗口是你最宝贵的资源。不要一股脑把整个DOM、整份文档都塞给模型宁可多写几行摘要逻辑把页面信息压缩成AI真正需要的内容也不要让模型在无关信息里大海捞针。控制好上下文的长度AI的决策质量和稳定性都能提升一截。第三AI代理做出来的结果一定要有校验环节。无论是页面提示语、接口返回状态还是结果文件的完整性至少要有一个明确的断言。没有校验的自动化就是盲人骑瞎马跑的越多错得越隐蔽。第四日志要尽量详尽。每步操作的时间、页面状态、截图都记录下来。因为AI代理的行为是概率性的不可能每次一样出了问题没有日志你根本无从判断是模型决策错了还是执行环节错了。有了日志你就能像调传统脚本一样去调AI提示词和上下文构造方式。这个项目做到现在我的感受是AI浏览器代理工具这个方向真正打开了一扇门它让自动化的门槛从会写代码降到了会说话但又没有丢掉工程体系的严谨性。这篇文章里写的东西都是我这个月实实在在跑过的代码和流程。如果你想动手做一个类似的工具希望这篇文章能帮你少踩几个我踩过的坑。