ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LangChain + Playwright 智能体开发测试全过程与工程排坑指南

2026/8/30 12:18:46 拓冰建站 浏览量
LangChain + Playwright 智能体开发测试全过程与工程排坑指南 打开需求文档的那一刻我其实有点犯难。业务方想要的并不是一个简单爬虫也不是传统 UI 自动化脚本而是让一个“智能体”听懂自然语言自己去内网系统里查数据、填表单、把结果整理出来。调研了一圈最后选了 LangChain Playwright 这个组合。第一版 demo 跑通的时候确实很兴奋一个自然语言任务真的被拆成了“打开页面、输入条件、点击查询、读取结果”几个动作全程不需要人干预。但兴奋劲过去以后真正折磨人的才刚刚开始元素偶发找不到、LLM 会突然调用错误工具、登录态过期了它还在傻傻重试、一个超时问题能排一个下午。这篇文章就是想把这套组合的完整开发测试过程讲透包括怎么搭、怎么测、怎么排坑以及为什么“能跑通 demo”和“能稳定生产使用”之间隔着一条很大的工程鸿沟。我后面会不断重复一个判断LangChain 负责“想”Playwright 负责“做”但真正决定成败的是“想”和“做”之间的翻译层、容错层和可观测层。这也是我把标题定为“开发测试全过程”的原因——如果你不把测试、排查、边界条件放进来这个 Agent 永远只能活在示例代码里。1. 先搞清楚这个组合真正解决的是哪类重复劳动很多人在看到 LangChain Playwright 的第一反应是这不就是用 AI 代替写自动化脚本吗其实不太对。传统 UI 自动化脚本解决的是“步骤固定、页面结构稳定”的任务而 Agent 解决的是“目标明确、但路径需要动态决策”的任务。两者的本质区别在于传统脚本是“我告诉你怎么做”Agent 是“你告诉我要什么结果我自己决定怎么做”。1.1 为什么不是 Selenium 一堆 if else我早年也写过不少 Selenium 脚本最痛苦的地方是选择器一改全链路崩。后面演化出 Page Object、数据驱动本质上都是把“页面变化”和“业务逻辑”隔离开。但这样做依然有一个上限所有分支都要预先写死遇到预期之外的情况只能报错。LangChain Agent 带来的变化是它可以根据当前页面的文本内容、URL、控件状态自己决定下一步调用哪个工具。比如同一个“查询订单”任务页面可能因为用户权限不同出现不同布局Agent 会先读取页面信息再决定点击“查询”还是展开“高级搜索”。这种能力传统脚本很难低成本实现。Playwright 在这里的角色不是被替代而是提供 Agent 的“手和眼睛”。LangChain 是大脑Playwright 是肌肉和感官。单纯用 Playwright 写的脚本灵活性不够单纯用 LangChain 也不具备操作浏览器的能力。合在一起才组成一个能“感知网页 → 做出决策 → 执行动作 → 再感知”的闭环。1.2 它真正解决的是“有规则但需要看懂页面”的流程我后来给业务方梳理应用场景发现最合适的任务有这几个特征操作对象是网页且页面内容包含大量自然语言信息。任务本身有明确目标比如“查询某个项目的上线状态”“把这份列表里的数据填进表单”。页面结构会变但不会频繁到完全无法跟踪。操作过程允许偶尔失败或者允许中间插入人工确认。比如“从报表系统里找到 6 月销售额超过 50 万的客户并导出联系方式”。传统脚本要写死查询条件、循环翻页、解析表格Agent 则可以阅读页面上的表格判断当前页是否已经满足目标再决定是否翻页、是否需要点击筛选条件。这比写死逻辑要省心很多。1.3 不要高估“智能”AI 只是决策层稳定性还得靠工具层这里必须泼一盆冷水。LLM 确实能理解页面语义但它的输出天然不稳定。同样一个任务第一次调用的工具顺序可能是“打开页面→输入内容→点击”第二次可能就变成“打开页面→点击→输入”。如果工具没有做好防御Agent 就会在错误的状态下继续执行。所以我在设计时给自己定了一条铁律所有脏活、累活比如等待元素出现、重试点击、处理 iframe、捕获新标签页、截图留痕全部用 Playwright 原生能力来实现尽量不让 LLM 去处理那些偏底层的细节。AI 只负责“决策”不负责“执行细节”。这样才能把不稳定性关进笼子里。2. 动手前的环境准备和最小可运行流程选型确定以后我建议按“最小链路优先”的顺序来推进。不管你的目标多复杂第一步是让 Agent 能打开浏览器、能调用一个简单工具、能看到返回结果。这个链路如果不通后面所有花哨的规划都是空中楼阁。2.1 环境要求Python 版本、依赖包和浏览器我本地的开发环境是 Python 3.10。实际用到的主要依赖pip install langchain langchain-openai playwright如果你用的是其他大模型可以把langchain-openai换成对应的集成包比如langchain-anthropic、langchain-ollama等。核心思路不变。装完 Python 包之后还要安装 Playwright 的浏览器内核python -m playwright install chromium或者用 Node 环境执行npx playwright install chromium。这里要注意很多新人第一次跑脚本报错不是因为代码问题而是浏览器内核没有安装。如果你在 Windows PowerShell 里面执行playwright命令提示“无法将‘playwright’项识别为 cmdlet、函数、脚本文件或可运行程序”大概率是 Python 脚本目录没有加入 PATH或者只是 pip 装到了当前用户的 site-packages 但没有暴露命令行入口。解决办法是优先用python -m playwright形式来执行而不是直接敲playwright。2.2 最小可运行示例一个大模型驱动的“打开页面”工具下面这份代码是我用来验证链路的最小示例。它只做一件事Agent 拿到一个 URL调用工具打开页面并返回页面标题和开头一段文本。from playwright.sync_api import sync_playwright from langchain.agents import tool, AgentExecutor, create_react_agent from langchain_openai import ChatOpenAI from langchain import hub tool def open_page(url: str) - dict: 打开指定URL返回页面标题和前200字文本。 with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(url, timeout30000, wait_untildomcontentloaded) title page.title() content page.locator(body).inner_text()[:200] browser.close() return {title: title, preview: content} llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt hub.pull(hwchase17/react) agent create_react_agent(llm, [open_page], prompt) agent_executor AgentExecutor(agentagent, tools[open_page], verboseTrue) result agent_executor.invoke({input: 请打开 https://example.com 并告诉我页面标题}) print(result)这段代码只是示意不同版本的 LangChain 在 API 上可能略有差异落地时要以你自己安装的版本为准。但这个结构很典型tool定义工具create_react_agent构建 AgentAgentExecutor负责循环调用。注意一个细节open_page函数是一个同步函数每次运行都会启动一个全新的浏览器实例。这对最小 demo 没问题但真实场景里不建议这么做因为启动浏览器的成本非常高。后面会讲如何用常驻浏览器上下文优化。2.3 从最小示例到真实任务的差距比想象中更大这个最小示例跑通以后你会觉得“这不就完了嘛”。但一旦把任务换成真实的业务系统差距立刻就出来了。登录很多内部系统需要账号密码、可能有 MFA。Cookie 和会话保持每一次都新开浏览器登录态全丢。iframe页面里嵌了子页面直接定位元素会失败。新标签页点击链接可能在new_page里打开需要监听事件。文件下载下载路径、文件重命名、等待下载结束。弹窗可能是系统弹窗也可能是浏览器原生window.alert。这些都不是 LangChain 能帮你解决的需要靠 Playwright 的一整套上下文管理能力。所以我在进入业务开发前会先做一个“中间层”把常用浏览器操作封装成独立的工具让 Agent 只面对一个简单接口。3. 关键流程拆解Agent 怎么决定调用 Playwright很多人看 Agent 的代码总觉得像魔法。其实拆开来看LangChain Agent 的核心机制并不神秘给定一个任务目标LLM 根据系统提示和工具描述生成一个“动作”执行工具后把结果喂回 LLMLLM 再决定下一步动作直到它认为任务完成。整个过程就是一个循环。只有把每个环节拆清楚你才知道出错时该去查哪里。3.1 工具描述比工具实现更重要在 Agent 场景里工具实现只决定“能不能做”工具描述则决定“它会什么时候做”。如果你把工具描述写得含糊LLM 可能会在不需要的时候调用它或者不调用。我经常举一个例子同一个“获取页面文本”的功能可以写成两类描述。模糊描述获取当前页面的文本内容。清晰描述获取当前浏览器页面的文本内容通常用于查看页面信息、判断当前页面状态、提取表格或列表数据。如果页面正在加载请先等待不超过10秒。第二种描述给了 LLM 足够的信息它会更恰当地调用这个工具。更重要的是工具描述里还可以写清楚“什么时候不可以用”比如不要在点击按钮后立即调用应等待页面加载完成。这些边界信息能显著减少 Agent 的幻觉式调用。3.2 任务是靠“工具返回结果”反馈推进的以“查询订单状态”为例一个典型的 Agent 执行流程是Agent 调用open_page(url)传入业务系统地址。工具返回页面标题、输入框占位符、按钮文本。Agent 分析页面认为需要输入订单号调用fill(selector, value)。页面可能因为异步请求更新了状态Agent 调用get_text()查看结果。如果结果没有出现Agent 可能调用wait_selector()或者重新获取页面文本。如果页面出现“登录过期”字样Agent 可能会调用login工具。这里每一步看似是 Agent 在“思考”但其实完全依赖工具返回的内容质量。如果get_text()返回的是尚未渲染完成的页面Agent 就会基于错误信息做决策。所以工具内部一定要做好等待和重试。3.3 用 LangGraph 还是 LangChain AgentExecutor很多人在搜索时都会看到langgraph和langchain的区别。简单说LangChain 早期的 AgentExecutor 是一个线性 ReAct 循环适合大多数简单场景而 LangGraph 把 Agent 流程改造成了一张有节点的图可以更精细地控制状态流、分支循环和人工介入点。我在真实项目里的感受是如果流程很简单比如“打开页面 → 查找信息 → 返回答案”用 AgentExecutor 就够了。如果流程有很多阶段比如“先登录 → 再查询 → 再填写下游表单 → 最后发邮件”每个阶段的状态都需要保留最好用 LangGraph。它能让你显式定义“登录节点”“查询节点”“填单节点”出现异常时可以直接跳到人工处理节点而不是让 LLM 在循环里打转。所以不是 LangGraph 比 LangChain 高级而是它们解决不同复杂度的问题。新手建议先把 AgentExecutor 玩熟再考虑上 LangGraph。4. 开发测试过程中的真实踩坑和排查链路这一部分我把它叫做“铁人三项”。因为 Agent 开发的前期很爽中后期全在跟异常搏斗。如果你没有一套系统性的排查方法很容易一天时间耗在一个看起来毫无逻辑的报错上。4.1 统一排查链路现象 → 输入 → 环境 → 参数 → 边界我给自己规定了一个排查顺序遇到问题不急着改代码先按这个链路走看现象是直接报错还是无输出是卡住不结束还是输出不完整把现象原样记录下来。看输入传给 Agent 的任务文本是否清晰传给工具的参数是否符合格式URL 是否拼接错误文件路径是否存在看环境浏览器能不能启动依赖版本有没有冲突环境变量是否配置登录态是否过期看参数超时时间是否设置太短等待策略是否合理最大迭代轮次是否足够并发数是否太大看边界条件页面空数据怎么处理元素找不到怎么办重复点击会不会产生副作用这个操作是否可以重试这个框架解决了我至少 80% 的问题。尤其是“输入”这一层我会先确认工具函数收到的参数是什么。因为在 Agent 场景里很多问题不是工具执行错误而是 LLM 生了错误参数。4.2 常见坑等待、iframe、新标签页和登录态这里单独列几个我在实际开发中最常遇到的问题顺便给出解决方案思路。等待问题。Playwright 有自己的自动等待机制但 Agent 的工具函数往往是自定义的。如果工具在page.goto()之后立刻返回内容可能拿到的是半加载状态的页面。建议在关键操作后加显式等待比如page.locator(.result-table).wait_for(statevisible, timeout10000)或者用expect机制等待特定条件成立。iframe 问题。很多业务系统都把表格嵌在 iframe 里。直接用page.locator定位不到需要先定位 frameframe page.frame_locator(#main-frame) frame.locator(#search-input).fill(2024-06)新标签页问题。Agent 点击一个链接后可能需要等待新页面出现。用context.expect_page()配合点击动作with context.expect_page() as new_page_info: page.click(a[target_blank]) new_page new_page_info.value new_page.wait_for_load_state()登录态问题。最粗暴的方式是在工具里每次启动浏览器时手动登录但效率太低。可以把首次登录后的存储状态保存下来之后直接复用context browser.new_context(storage_statestate.json)建议把登录操作单独做成一个工具或者放在 Agent 流程的入口节点。4.3 单靠 print 调试不够要用 Trace Viewer 和截图Agent 调用链很长光靠print日志很难还原完整过程。Playwright 提供了 Trace Viewer可以直接记录浏览器内部的每一个操作、网络请求、DOM 快照。我在每次任务失败时都会生成一份 trace然后反复回放。context browser.new_context(record_video_dirvideos/, traceon)打开 Trace Viewer 的命令是python -m playwright show-trace trace.zip有了 trace你至少能快速判断是元素没有出现还是 Agent 根本没调用点击工具还是点击后页面跳转到了错误 URL。这些信息在 Agent 调试里是救命级别的。测试阶段我还建议一个“黄金用例集”准备 10 到 20 条覆盖正常、边界、异常场景的任务文本比如“查询一个不存在的订单”“页面弹出提示‘无权限’”“等待超时”等。每次代码改动后都跑一遍对比成功率。这个方法和我以前做自动化测试时维护回归用例集的思路一致只不过这里的断言更加宽松——只要 Agent 最终给出合理结果或明确报告失败都算通过。5. 从能跑到好用还需要补哪些工程能力如果你只是做一个 demo上一章的环境和踩坑已经够用了。但要把这个 Agent 交给同事或放进生产环境你会发现还缺很多东西。这也是我认为“Agent 开发”最容易被低估的部分。5.1 权限、并发与资源隔离浏览器是重资源。一个 Chromium 实例通常要占几百 MB 内存如果同时运行多个 Agent会直接把开发机拖垮。所以我在设计时不会为每个任务临时启动浏览器而是维护一个浏览器实例池每个 Agent 任务独占一个context任务结束就关闭context浏览器可以复用。这样做有两个好处一是内存可控二是不同任务之间不会共享 Cookie 和 localStorage避免数据串味。并发数也要保守。建议最开始只开 2 到 3 个并发跑测试观察稳定后再逐步增加。很多“莫名失败”的 Agent 问题其实是并发过高导致浏览器超时、资源不足。5.2 日志、状态和监控Agent 和普通脚本最大的不同是它的执行路径不固定。你无法用单一线程日志来还原过程所以需要给每个任务分配一个唯一 ID专门记录任务输入和最终输出。LLM 每次调用的 token 数量和耗时。每次工具调用的名称、参数、返回摘要。每一步的时间戳和页面截图路径。有了这些数据你才能回答三个问题这个任务为什么失败这个任务为什么这么贵这个 Agent 今天跑得是否正常我通常会用结构化日志把关键事件写入 JSON 或者数据库方便后续分析。如果只靠人眼翻控制台迟早会疯掉。5.3 安全边界和人工审批这是我最想强调的一点。当 Agent 能操作浏览器时它就有了“执行动作”的能力。不是说代码写不好会造成多大的破坏而是它可能会执行错误动作而且执行得很快。我在设计高风险工具时会加一道人工确认闸门。比如“发送邮件”“删除数据”“提交订单”这类工具可以做成先让 Agent 生成动作草稿暂停等待用户确认后再执行。这里的实现很简单只要让 Agent 在调用工具前调用一个ask_user_approval(description)工具然后抛出一个特殊异常等待人工审批通过后继续。同时不要让 Agent 访问不受限的地址。如果只是内部系统建议网络层面加上访问白名单避免它被恶意 prompt 诱导去操作外部页面。5.4 从单任务到批量的演进路径真正有价值的场景往往是批量任务。比如每天上午跑一遍“检查所有项目的发布状态”这比单次查询更值得自动化。但我不建议直接写一个大循环去调 Agent而是分层处理先把单条任务封装成标准函数入参是一个对象出参是结果对象内部包含 Agent 执行和错误处理。再写调度器从队列里读取任务并发执行记录成功失败。加上重试机制对可重试失败超时、网络抖动最多重试 2 次对不可重试失败登录失败、参数错误直接进入人工处理队列。最终加上监控报警成功率低于阈值时通知维护人员。这四步走完Agent 才从一个“好玩的小玩具”变成“业务里一个靠谱的螺丝钉”。6. 适用边界和长期使用建议写了这么多最后想认真聊聊边界。我见过有些人调通 demo 后就觉得这个方案可以取代所有 UI 自动化测试也有一些人在遇到几个不稳定后直接放弃。这两个极端都不太对。6.1 适合谁不适合谁适合的场景页面里充满自然语言信息需要理解上下文才能操作。操作步骤本身不固定或者页面经常微调。任务数量多但单个任务相对简单失败可重试。已经有 Playwright 基础熟悉浏览器自动化能力边界。不适合的场景页面结构高度反自动化包含大量动态混淆或者频繁变更选择器。操作流程要求毫秒级实时响应Agent 的决策循环可能太慢。操作不可逆且没有人工审批条件。任务需要复杂的视觉判断比如识别不规则图表。完全没有日志和可观测性要求只是临时跑一次。如果你属于不适合的类别不要硬上。传统脚本可能反而更合适。6.2 学习路线建议如果你是从零开始接触这个方向我的建议是分三步走先用纯 Playwright 做几个小自动化任务理解选择器、等待、iframe、上下文等基本概念。再用 LangChain 做几个没有浏览器的 Agent 示例理解工具调用和 ReAct 循环。最后把两者结合先做单任务再做批量。不要一上来就啃 LangGraph也不要一上来就想着让 Agent 自动完成十几个操作。复杂度要慢慢加。6.3 一条值得长期记住的原则这个组合真正的价值不是替代人类操作浏览器而是把“人通过浏览器完成的信息判断和重复操作”拆成两个部分人可以专注于异常和决策Agent 负责执行那些看得见、摸得着、有规律的动作。所以我在评估一个候选任务时会问三个问题这个任务能不能被写清楚目标和边界如果 Agent 失败了风险有多大能不能接受这个任务是不是重复发生值得投入工程成本这三个问题都通过才值得用 LangChain Playwright。如果只是心血来潮建议先跑一个 demo 体验一下然后继续做你自己的主线业务。开发 Agent 的乐趣在于你像在搭一个“能自己看电脑的助手”但真正的挑战也在于你不会想让它在你睡觉时候乱点网页。把工程护栏立好才是对它和对你自己的负责。