资讯动态

自然语言写E2E测试:Agent执行与回放缓存实战

发布时间:2026/10/18 14:00:43 来源:尧图企业网站定制
1. 当测试脚本开始“听懂人话”e2e 项目到底在解决什么第一次看到“用自然语言写 E2E 测试”这个说法我的反应是怀疑。E2E 测试这些年从 Selenium 到 Playwright、Cypress核心矛盾一直没变过写起来慢、维护起来更慢。一个登录流程光是选择器就要写七八行页面改个 class 名整条用例直接红给你看。所以当有人告诉我“你直接写‘打开首页点登录输入账号密码确认跳转到工作台’就能跑测试”我第一反应是——这玩意儿要么是玩具要么背后藏着一堆没说的坑。e2e 这个开源项目恰好就踩在这个矛盾点上。它的核心主张有两层第一层是用自然语言描述测试意图让不写代码的人也能参与用例编写第二层更有意思——把 Agent 实际跑过的操作录成“回放缓存”下次同样的用例直接走缓存不再调用模型。这两层加起来其实是在回答一个很现实的问题大模型驱动的自动化测试怎么才能既灵活又便宜。先说清楚它适合谁。如果你是小团队里唯一负责测试的人每天被回归用例压得喘不过气那这个思路值得研究如果你在做 AI Agent 相关的产品需要一套“可录制、可回放”的操作验证机制那它的缓存设计更值得抄作业如果你只是想找个能替代 Playwright 的框架那可能要失望——它不是在替代底层驱动而是在驱动之上加了一层“意图翻译”和“执行记忆”。我把它拆成三个关键词来理解自然语言用例、Agent 执行、回放缓存。自然语言用例解决的是“写”的问题Agent 执行解决的是“跑”的问题回放缓存解决的是“重复跑”的成本问题。三者串起来才构成一个完整的闭环。少了任何一环这个项目都不成立——只有自然语言没有缓存每次跑测试都在烧模型调用只有缓存没有自然语言那不就是传统的录制回放工具吗十几年前就有了。所以这篇我想聊的不是“怎么装怎么用”这种说明书式的内容而是把这三个环节各自的设计逻辑、实操边界、以及我踩过的坑讲透。尤其是回放缓存这块表面看是个性能优化实际上它决定了这套方案能不能进 CI、能不能在团队里推得动。下面按我实际折腾的顺序展开。2. 自然语言用例的解析链路从一句话到可执行动作2.1 为什么不是“直接让模型操作浏览器”很多人第一反应是既然有 Agent为什么不干脆把整个浏览器交给模型让它自己看着办我一开始也这么想试过之后发现两个致命问题。第一是不确定性同一个页面模型这次点“登录”按钮下次可能去点“注册”因为它对页面结构的理解每次都有细微差异。第二是成本每一步操作都要把当前页面状态DOM 或截图发给模型一个十步的流程就是十次调用回归测试跑一百条用例账单直接起飞。e2e 的做法是把“理解意图”和“执行动作”拆开。自然语言那一层只负责把人的描述翻译成结构化的动作序列比如“打开首页点登录输入账号密码确认跳转”会被解析成类似这样的东西[ { action: goto, target: 首页 }, { action: click, target: 登录按钮 }, { action: fill, target: 账号输入框, value: {{username}} }, { action: fill, target: 密码输入框, value: {{password}} }, { action: click, target: 提交按钮 }, { action: assert, target: 工作台标题, expect: visible } ]这个中间层是关键。它把“模糊的自然语言”固化成了“确定的动作列表”后续执行就不再需要模型反复理解意图了。你可以把它理解成编译器源代码自然语言编译成中间代码动作序列然后由执行引擎去跑。编译只做一次执行可以跑无数次。2.2 解析环节里最容易被低估的“目标定位”动作序列里每个target都是自然语言比如“登录按钮”。执行引擎拿到这个描述怎么知道页面上哪个元素是“登录按钮”这是整个链路里最容易翻车的地方。我实测下来e2e 这类项目通常走的是多路召回 打分的策略而不是单一手段。具体来说它会同时尝试几种定位方式按可见文本匹配页面上真有“登录”两个字、按无障碍属性匹配aria-label 里含 login、按元素角色匹配button 角色、按位置启发式匹配右上角那个可点击元素。每一路给出一个候选和置信度最后综合打分选最高的。这个设计的好处是容错——页面文案从“登录”改成“登 录”或者“Sign in”只要有一路能匹配上用例就不会直接挂。但这里有个坑我必须提醒多路召回会带来误匹配。我遇到过一次页面上有个“登录帮助”的链接文本里也含“登录”结果引擎把它当成了登录按钮点进去跳到了帮助页。排查了半天才发现是文本匹配的权重给高了。后来我在用例里把描述写得更具体——“页面顶部导航栏的登录按钮”加了位置限定词之后才稳定。所以自然语言写用例不是越短越好该给的上下文一定要给这跟给人交代任务是一个道理。2.3 变量注入与用例参数化真实项目里不可能把账号密码写死在用例里。e2e 支持在自然语言里用占位符比如“输入账号 {{username}}”执行时从环境变量或配置文件注入。这个机制看起来简单但设计上有讲究占位符的解析时机是在编译阶段还是执行阶段我倾向于执行阶段解析。原因是同一条用例可能要在不同环境跑测试环境用测试账号预发环境用预发账号如果编译阶段就把值写死进动作序列那缓存就没法跨环境复用了。e2e 的做法是把占位符保留在动作序列里执行时才替换这样同一份缓存可以在多个环境间共享只是注入的值不同。这个细节在文档里往往一笔带过但对实际使用影响很大。另外提醒一句敏感值不要走自然语言描述。我见过有人把密码直接写在用例描述里虽然方便但用例文件一旦进版本库就是明文泄露。正确做法是描述里只写占位符真实值放 CI 的密钥管理里。3. Agent 执行层动作序列是怎么真正落到浏览器上的3.1 执行引擎与底层驱动的分工动作序列有了接下来是执行。e2e 本身不重复造轮子底层通常对接现成的浏览器自动化驱动。这里的分工要理清楚驱动负责“怎么点”e2e 负责“点哪里”。驱动提供的是click(selector)、fill(selector, value)这种原子能力而 e2e 要做的是把“登录按钮”这个自然语言描述翻译成驱动能接受的 selector。这个翻译过程就是我上一节说的多路召回。翻译完之后执行引擎按顺序调用驱动接口每一步执行完还要做状态确认——点完登录按钮页面是不是真的跳转了输入框是不是真的填进去了如果只是发指令不确认那测试就是“假通过”页面报错它也不知道。我实测下来e2e 在每步之后会做一个轻量的可交互性检查目标元素是否存在、是否可见、是否可点击。这三个条件缺一不可。有些页面元素存在但被遮挡比如弹窗盖住了这时候点击会失败引擎需要能识别出来并报错而不是傻等超时。3.2 等待策略固定等待是万恶之源做 E2E 测试的人都知道sleep(3000)是最烂的写法但也是最常见的写法。e2e 这类项目如果做得好应该用条件等待替代固定等待。具体来说每一步执行前引擎会轮询检查目标元素是否达到可操作状态达到就立即执行没达到就继续等直到超时。这个策略的关键参数是轮询间隔和超时时间。轮询间隔太短CPU 空转浪费资源太长响应迟钝。我一般设 100ms 到 200ms 之间。超时时间要看具体场景页面跳转这种操作给 10 秒元素输入给 5 秒断言给 3 秒。e2e 允许在用例里针对单步覆盖超时这个灵活性很重要——全局超时设太长会让失败用例拖慢整个 CI设太短又容易误报。提示如果你的用例在本地跑很稳到 CI 上就频繁超时八成是 CI 机器性能差导致页面渲染慢。这时候不要盲目加超时先确认是不是资源不够。3.3 失败重试与截图留证E2E 测试最让人头疼的就是“偶发失败”。同一个用例跑十次挂一次排查起来毫无头绪。e2e 的处理方式是失败自动重试 现场留证。重试次数一般设 1 到 2 次重试前会刷新页面回到初始状态避免上一次的残留状态干扰。留证这块我强烈建议开启失败截图 动作日志。截图能让你直观看到失败瞬间页面长什么样动作日志能让你知道执行到哪一步挂的、当时的元素状态是什么。这两样东西加起来排查效率能提升好几倍。我踩过的坑是有一次失败重试通过了但没留证结果第二天又挂了还是不知道原因。后来强制开启留证才发现是某个第三方接口偶发超时导致页面加载慢。4. 回放缓存让重复执行不再烧模型调用4.1 缓存到底缓存了什么这是 e2e 最有价值的设计也是最容易被误解的地方。很多人以为缓存的是“页面快照”或者“操作录像”其实不是。缓存的是“自然语言描述 → 动作序列”这个映射关系以及每一步执行时用到的定位结果。举个例子第一次跑“点登录按钮”这条用例时引擎通过多路召回确定了页面上那个按钮的 selector 是#login-btn这个映射关系会被记下来。第二次跑同样的用例引擎直接查缓存发现“登录按钮”对应#login-btn就不需要再走一遍召回打分的流程了。如果这个映射是模型算出来的那就省了一次模型调用如果只是本地启发式算的那也省了计算时间。这个设计的精妙之处在于它把“理解”和“执行”解耦了。理解只做一次执行可以复用。而且缓存是按用例 步骤粒度存的不是整条用例一把梭。这样即使页面局部改版只有受影响的步骤缓存失效其他步骤照常走缓存不会一条用例全废。4.2 缓存失效与版本管理缓存最大的敌人是页面改版。按钮从#login-btn改成.btn-primary缓存里的映射就失效了。e2e 的处理方式是执行时校验走缓存拿到 selector 后先检查这个元素在当前页面上是否存在且可操作如果不存在就回退到完整召回流程重新定位并更新缓存。这个“校验 回退 更新”的机制很关键。它保证了缓存不会成为“脏数据”导致用例永久失败同时又能自动适应页面变化。我实测下来页面小改版改文案、改样式基本能自动适应只有结构性大改比如整个导航栏重构才需要人工介入。但这里有个坑缓存更新是覆盖式的没有版本历史。如果某次回退定位到了错误的元素比如页面上恰好有个同名的新元素缓存会被错误地更新导致后续一直错下去。我的做法是定期清理缓存或者在 CI 里对缓存命中率做监控——如果某条用例的缓存命中率突然下降说明页面有变化需要人工确认。4.3 缓存带来的成本账算一笔账。假设一条用例有 10 步每步需要一次模型调用来理解意图单次调用成本按 0.01 元算一条用例跑一次就是 0.1 元。一百条用例跑一次回归是 10 元一天跑五次就是 50 元一个月 1500 元。有了缓存之后只有首次执行需要模型调用后续全部走缓存成本直接降到接近零。这还只是模型调用成本。更重要的是时间成本。模型调用有网络延迟每步几百毫秒到几秒不等十步下来就是好几秒。走缓存的话定位是本地计算毫秒级完成。一条用例从十几秒降到两三秒一百条用例的回归时间从二十分钟降到五分钟以内这个提升对 CI 流水线来说是质变。注意缓存不是万能的。如果用例里包含随机数据比如每次生成不同的用户名那涉及这些数据的步骤没法走缓存因为输入变了定位结果可能也变。这类用例要单独标记不要指望缓存能覆盖。5. 把它接进 CI我踩过的四个坑5.1 坑一无头模式下的定位差异本地开发时我习惯用有头模式跑能看到浏览器窗口定位出问题了一眼就能看出来。但接进 CI 必须用无头模式这时候问题来了某些元素在有头模式下可见在无头模式下不可见。原因是无头模式的视口尺寸默认比较小页面布局会变原本在首屏的元素被挤到了下面需要滚动才能看到。解决办法是显式设置视口尺寸跟本地保持一致。我一般设成 1920x1080并且在用例开始前加一步“滚动到目标元素”。e2e 支持在动作序列里插入滚动操作这个在无头模式下几乎是必须的。5.2 坑二缓存目录的持久化CI 环境每次跑都是全新的容器缓存目录如果不做持久化每次都要重新走模型调用缓存等于白做。我的做法是把缓存目录挂载到 CI 的缓存卷上或者用对象存储做远程缓存。e2e 支持配置缓存路径把它指向一个持久化位置就行。但这里有个细节多分支并行跑的时候缓存会冲突。不同分支的页面可能不一样缓存混在一起会互相污染。我的做法是按分支名做缓存隔离主分支的缓存单独存特性分支用独立的缓存空间。这样主分支的缓存最稳定特性分支的缓存即使脏了也不影响主干。5.3 坑三失败重试与缓存的交互前面说了失败会自动重试但重试和缓存放在一起会有一个微妙的问题第一次失败可能是因为缓存里的 selector 失效了重试时走了完整召回定位到了新元素用例通过了但缓存没有更新。结果下次跑还是走旧缓存还是失败还是重试通过。这就陷入了“每次都失败一次再重试通过”的循环CI 日志里全是红色看着很吓人。解决办法是在重试成功时强制更新缓存。e2e 的配置里应该有这个选项如果没有可以在重试逻辑里手动触发缓存刷新。这个细节很小但不处理的话 CI 体验极差。5.4 坑四并发执行时的资源竞争CI 上为了快通常会并发跑多条用例。但浏览器自动化本身是资源密集型的并发太高会导致每个实例都变慢反而更容易超时。我实测下来并发数控制在 CPU 核数的 1.5 倍左右比较合适。比如 4 核的机器并发 6 个实例既能跑满资源又不会互相拖累。另外如果多条用例操作的是同一个后端账号并发时会有数据竞争。比如两条用例同时用同一个账号登录后登录的会把先登录的踢下线。这类用例要么串行跑要么给每条用例分配独立账号。e2e 支持给用例打标签按标签分组串行执行这个机制要善用。6. 这套方案的真实边界什么时候不该用它6.1 不适合的场景说了这么多好处也得说说它不适合什么。第一视觉回归测试。e2e 关注的是功能流程不是像素级对比。如果你要验证页面样式有没有跑偏它帮不上忙得用专门的视觉测试工具。第二性能测试。它的执行链路里有多路召回、缓存校验这些额外开销测出来的时间不能反映真实用户感知。第三需要精确控制的底层操作。比如模拟键盘组合键、拖拽排序这种自然语言描述起来很别扭不如直接写代码。6.2 自然语言的“描述精度”天花板自然语言写用例有个天然的上限你能描述多精确它就能执行多精确。但人的描述往往是模糊的而模糊在测试里是致命的。我见过有人写“点击那个按钮”结果页面上有五个按钮引擎随便选了一个用例“通过”了但测的是错的东西。所以我的经验是自然语言用例适合描述“做什么”不适合描述“怎么做”。流程层面的东西用自然语言元素定位这种细节该给具体描述就给具体描述甚至可以直接写 CSS selector 作为兜底。e2e 允许在自然语言里混入精确选择器这个灵活性要用起来。6.3 维护成本的转移而非消失最后说个扎心的真相自然语言用例并没有消除维护成本只是把它转移了。以前页面改版你要改选择器现在页面改版你要改自然语言描述或者等缓存自动失效后重新召回。工作量可能小了但不是零。而且自然语言描述的维护更“隐性”——选择器失效会直接报错描述失效可能只是匹配到了错误的元素用例还是绿的但测的东西已经不对了。所以我的建议是定期做用例审查尤其是缓存命中率下降的用例人工确认一下它测的还是不是你想测的东西。自动化测试最怕的不是失败是“假通过”。7. 我实际用下来的一些零散心得折腾了几周有几个不成体系的体会随手记下来。第一先从最稳定的核心流程开始比如登录、下单、支付这三条主链路跑通了再往外扩。一上来就想把所有用例都迁过来大概率会卡在某个边缘场景上然后对整个方案失去信心。第二缓存要定期清理我一般两周清一次避免脏数据累积。清理后第一次跑会慢一些但能保证后续的准确性。第三用例描述尽量用团队统一的词汇比如“工作台”就统一叫“工作台”不要一会儿叫“首页”一会儿叫“控制台”否则缓存没法复用召回也容易出错。还有一点别指望它完全替代代码写的测试。我现在是混合用核心链路用自然语言写方便产品和运营也能看懂、能提意见边缘场景和需要精确控制的用代码写稳定可靠。两套并行各取所长。这个组合目前跑下来最舒服CI 时间可控维护负担也能接受。如果你也在被 E2E 测试的维护成本折磨e2e 这个思路值得花一个下午试试。不用全量迁移挑一条最烦人的用例用自然语言重写一遍跑几次看看缓存命中率和稳定性。体感对了再往下推体感不对就及时止损别硬上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑