尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

把测试用例变成可执行文档:基于Playwright的UI自动化实践

发布时间:2026/9/29 7:25:38

资讯中心
01
ARTICLE

把测试用例变成可执行文档:基于Playwright的UI自动化实践

把测试用例变成可执行文档:基于Playwright的UI自动化实践
把测试用例变成“可执行文档”这个概念我琢磨了小半年起因特别朴素。那是迭代到一半开发改了登录流程把“点击登录按钮”的文案换成了“安全登录”测试这边的用例文档还停在旧版本。测试同学照着文档跑了半小时跑完发现全废回头骂骂咧咧改Excel。更要命的是开发压根不知道有这份文档你拿着文档去找他他反问“什么用例你测了啥”。问题的根源不是大家不写文档而是文档和代码是两套系统。文档动不起来代码看不懂意图两边一旦对不上文档就成了没人信的废纸。后来我换了个思路把测试用例本身做成“可执行文档”——用例就是代码代码就是文档跑起来是自动化测试打开来是给开发看的业务描述。技术栈我选了Playwright这条路线在团队里跑通之后开发开始主动来问“这个场景的用例怎么跑”这是以前写两年Excel用例都没发生过的事。这篇文章聊聊我具体是怎么改造的。适合正在被测试用例编写、管理、复用和维护折腾的测试同学也适合想引入UI自动化、但不想把用例写成一堆“只有自己能懂”的脚本的团队。1. 传统测试用例文档的困境不是写得不够是“动不起来”1.1 开发为什么不看测试用例文档传统用例的形态大家都熟编号、模块、前置条件、操作步骤、预期结果再配一列优先级。这种文档在需求评审阶段挺管用一旦进入持续迭代就变成了静态资产。开发不爱看我观察下来有三个核心原因。第一阅读成本高。一份两百条用例的Excel开发真想看完基本等于把整个业务逻辑在脑子里重放一遍他手头还有一堆代码要看真没这个精力。第二描述容易失真。用例写的是“点击登录按钮”页面重构后按钮文案变成了“登录”文档没人同步用例本身就在误导人。第三也是最关键的——文档里的用例“动不起来”。开发看完一条用例不能马上知道这个步骤在当前代码里能不能走通文档不会告诉他必须等测试真正执行完才知道。这三点叠在一起测试用例在开发眼里就不是资产而是负担。你问他“登录校验失败的用例看了吗”他潜意识里想的是“又来了一堆Excel谁看得完”。1.2 文档与代码两张皮的代价我在几个不同项目组待过复杂迭代里的典型场景是这样的A组改了登录逻辑B组的用例文档里还引用着旧行为C组的自动化脚本跑挂了debug半天发现是文档里的预期结果和产品经理最新确认的不一致。问题全出在“两张皮”——业务描述是一套系统自动化脚本是另一套系统维护的人不同更新节奏也不同。测试用例文档还有个隐性成本它跟代码之间没有强制绑定关系改代码的人不知道要去同步文档写文档的人不知道代码已经变了。哪怕团队约定“每次需求变更都要更新用例”在赶进度的时候这种约定通常是第一个被牺牲的。所以我把目标定成了“一张皮”用代码承载用例用命名、注释和报告让代码变成文档。每条用例既是一个能独立运行的测试又是一段能被人读懂的业务故事。这就是“可执行文档”这个说法的由来——它不只是一个漂亮的比喻而是我实际的工作方式。这里要强调一下我不是让团队抛弃所有文档。需求背景、验收标准、需求单号这类信息依然保留只是换了一种存放方式——放在用例的annotation和代码注释里让“代码可以引用”而不是躺在共享盘里没人看。2. 重新设计用例的形态从“步骤清单”到“三层可执行文档”2.1 三层拆分行为层、实现层、数据层动手写代码之前我先重新回答了“一条测试用例到底包含什么”这个问题。过去是一条条操作步骤现在我把用例拆成三层行为层面向人用例标题、业务背景描述、需求编号、关联缺陷、验收断言。这一层解决的是“这个用例在验证什么业务”的问题。实现层面向机器一步步可执行的Playwright操作比如点击、输入、断言。这一层解决的是“怎么在页面上复现这个业务”的问题。数据层面向环境测试账号、URL、业务参数、超时时间。这一层解决的是“换个环境、换个项目组还能不能用”的问题。这个拆分可以拿菜谱来类比。菜名是行为层告诉客人这道菜是什么烹饪步骤是实现层告诉厨师怎么把菜做出来食材清单是数据层决定了这道菜在不同季节、不同市场能不能凑齐原料。一道菜菜名取得再优雅食材清单错了厨师也没法动手。过去写测试用例大家习惯把这三层混在一起。比如“打开登录页面https://xxx输入账号admin输入密码点击登录断言出现xxx”看着挺顺细想全是问题账号写死在步骤里换个环境就要改点击哪个按钮用了CSS selector开发根本不想看断言结果写得模棱两可页面显示“用户名或密码错误”和“账号不存在”到底哪个算通过也不清楚。分层之后行为层保证可读性实现层保证可执行性数据层保证可维护性。三者各管一摊缺一不可。2.2 为什么选Playwright作为执行引擎确定目标后选型几乎是顺理成章的。我当时对比了三个主流方案纠结了大概两天。能力SeleniumCypressPlaywright自动等待需要手动处理内置但事件循环限制多内置web-first断言自动重试失败现场还原手动截图/日志截图视频Trace Viewer完整回放多浏览器支持需要自己配置driver主要面向Chromium系原生支持Chromium/Firefox/WebKit多标签页/多用户支持但不稳定限制明显原生支持context隔离测试报告需要额外集成一般内置HTML报告自带annotation和步骤树调试体验一般较好codegenTrace调试效率最高对我来说决定性因素有两个。第一个是web-first断言。比如expect(locator).toBeVisible()Playwright会自己等待元素可见再判断不用我在代码里塞一堆sleep。以前用Selenium最烦的就是元素加载慢导致的随机失败现在这个基础问题直接从框架层面解决了我的用例里几乎看不到“等待三秒”这种代码。第二个是Trace Viewer。用例失败以后可以把整个操作过程回放出来每一步的DOM快照、网络请求、控制台日志都在。开发不用脑补现场直接看回放就知道问题在哪。我后面会专门讲这个因为它是“让开发看得懂”的关键一环。另外Playwright的用例组织方式——test.describe、test.step、annotations——很自然地对应我上面说的行为层。写出来的代码可读性非常强这一点我在下一章展开。3. 落地第一个“可执行文档”Playwright项目结构与书写规范3.1 目录结构按业务模块组织用例我建议第一版项目结构保持简洁按业务模块组织即可。别一上来就搞复杂的多包架构那会让开发更不敢碰。tests/ login/ login.spec.ts # 登录相关用例就是登录模块的“文档” register.spec.ts order/ create-order.spec.ts # 下单相关用例 common/ fixtures.ts # 公共fixture、登录态等 pages/ login-page.ts # Page Object页面元素与操作封装 order-page.ts data/ users.json # 测试账号等数据 env.json # 环境配置 playwright.config.ts # Playwright配置文件这里有个关键决策spec文件本身要“自解释”。项目组其他人点开login.spec.ts应该能在五分钟内看懂这条用例在测什么、数据从哪来、失败去哪看而不是去猜文件里的page对象做了什么。为此我宁可让spec里多几行描述性代码也不搞那种“所有步骤都封装进方法、spec里一行注释都没有”的极简风格。3.2 用例怎么写才像“文档”直接看一段我实际在用的登录用例这是“可执行文档”最直观的样子import { test, expect } from playwright/test; import { LoginPage } from ../pages/login-page; import users from ../data/users.json; // 行为层这个用例要验证的业务目标 test.describe(登录模块账号密码登录, () { test(未注册手机号登录时页面提示“账号不存在”, async ({ page }) { // 业务背景见需求单REQ-2024-001登录共通化改造后新增的校验分支 test.info().annotations.push( { type: 需求单, description: REQ-2024-001 }, { type: 前置条件, description: 用户未注册且当前处于登录页 } ); const loginPage new LoginPage(page); await loginPage.goto(); // 实现层一步步操作每一行都对应业务动作 await test.step(输入未注册手机号, async () { await loginPage.fillPhone(users.unregisteredPhone); }); await test.step(输入密码并点击登录, async () { await loginPage.fillPassword(Test123456); await loginPage.clickLogin(); }); // 断言业务目标对应的验收结果 await test.step(页面提示账号不存在, async () { await expect(loginPage.errorTip).toBeVisible(); await expect(loginPage.errorTip).toHaveText(账号不存在); }); }); });你发现没有这份用例从头到尾没有出现“为什么要等三秒”“这个按钮的class是什么”这类噪音。test.step起的名字都是业务动作开发在报告里看到的是“输入未注册手机号→输入密码并点击登录→页面提示账号不存在”这样一条完整的故事线。这就是“可执行文档”和“纯自动化脚本”的分水岭。有一点要提醒不是所有用例都值得做成这个粒度。登录、下单、支付这种核心流程值得。偶尔冒烟的边界场景可以合并成一条用例里的多个断言别让用例库膨胀到没人维护。3.3 annotation与附件让报告自带上下文再补充两个让用例真正“文档化”的细节。第一个是annotation。我习惯在每条用例里带上需求单号、缺陷链接、前置条件。这样测试报告本身就是一个可追溯的文档出了问题开发在报告里点一下就能跳到需求单不用再到处问“这条用例是谁写的、对应哪个需求”。代码里的写法就是上面案例的test.info().annotations.push那一行。这是Playwright官方支持的能力报告里会渲染成清晰的键值对。这个功能我以前用TestRail管理用例时也做过但报告和用例代码分离维护起来很割裂。现在直接写进用例里顺手得多。第二个是attachment。Playwright允许在失败时自动附加现场信息。我在fixture里配置了失败自动截图和网络请求快照但真正让开发觉得“这东西很懂我”的是Trace。配上trace后失败用例的报告里有一段完整的操作回放跟录屏很像但比录屏多了每一步的DOM快照和网络日志。开发可以自己点开看前后拉一拉问题定位就完成了一大半。4. 让开发“愿意跑”命令行、报告与调试体验4.1 开发最需要的三个操作“能看懂”是第一步第二步是“能跑起来”。我观察开发同学的诉求其实就三个只跑一条用例、只看失败的用例、失败之后能快速定位。我把对应的命令整理好写进项目README开发头一回打开就能用# 只跑一条用例用标题里的关键字过滤 npx playwright test --grep 账号不存在 # 只看失败的用例 npx playwright test --last-failed # 失败时自动记录trace方便回放 npx playwright test --trace on这个--grep功能是我最推荐的。它允许直接用用例标题里的业务关键词来筛选开发想验证某个bug修复没修复敲一条命令就够了。我在package.json里还配了几个别名脚本比如npm run test:login对应只跑登录模块npm run test:smoke对应跑冒烟回归把常用场景再简化一步。操作步骤降低之后开发使用门槛一下子就下来了。我见过一个开发他第一次跑用例把命令复制粘贴进去跑完看到绿色的passed跟我说“哦这比我想象的简单多了”。4.2 报告如何“说人话”Playwright的HTML报告默认就把每条用例的操作步骤展开成一棵树树上的节点就是test.step的名字。开发打开报告看到的不是一堆selector和断言代码而是“打开登录页→输入账号→点击登录→校验提示”这种业务流程。更重要的是报告中还能展示annotation需求单号、前置条件、失败时的截图、网络请求列表和trace回放入口。有一次开发私聊我说他以前判断UI自动化报告靠猜现在靠看。报告本身就是文档——这句话我印象很深。报告还有一个细节可以在playwright.config.ts里配置自定义报告。我们团队用HTML Report就够但如果你有多项目组协同可以考虑把报告导出成统一目录接进内部展示页面。不过第一版别做太重先让报告“能看”再谈“好看”。4.3 从“你测了什么”到“我能自己看”这里要分享一个心态上的转变。以前我总想着“用例要写得让开发看懂”其实还不彻底因为开发始终是被动接受信息的一方。可执行文档真正起作用的时刻是开发自己敲下第一条命令、自己打开报告、自己看完trace回放的那个瞬间。他不再是“听你汇报测试结果”而是“自己去看业务行为是否正常”。为了这个目标我会刻意在用例评审时拉上开发一起过一遍spec代码。不需要逐行讲语法只需把test.describe和test.step的名字读一遍他们就能理解用例的意图。有时候开发还会主动指出“你这条用例的前置条件和最新需求不一致”这就是协作效率提升的实锤。这也带来一个隐含要求用例描述必须准确。如果开发照着报告里的步骤去复现发现和实际页面行为对不上那这份“文档”就失信了。所以我会把用例的验证标准写得非常具体不能用“页面正常展示”这种模糊断言要多用toBeVisible、toHaveText这种明确的web-first断言——既保证稳定性也保证“文档”的可信度。5. 复杂迭代下的复用与维护把用例当产品管理5.1 复杂迭代中用例为什么容易烂把用例做成“可执行文档”之后新问题来了项目组多、业务交织、需求变化频繁用例库一段时间不整理就开始腐烂。典型症状是某条用例依赖了另一条前置用例的结果某条用例的数据写死在脚本里某条用例的操作步骤被业务改得面目全非但没有人同步更新。我后来想明白一件事用例不是一次性写出来就完事的东西它跟产品功能一样需要持续维护。所以我把“用例管理”变成了“用例产品管理”定了三条规矩每条都是踩坑踩出来的。5.2 参数化与数据驱动一份用例跑多套环境第一条规矩是数据必须外部化。测试账号、URL、业务参数一律从json或环境变量读取不能写死在spec里。举例我们有两个环境测试环境地址是https://test.internal.example预发布地址是https://staging.internal.example。我在data/env.json里维护{ test: { baseURL: https://test.internal.example, adminAccount: admin_testexample.com }, staging: { baseURL: https://staging.internal.example, adminAccount: admin_stagingexample.com } }对应在playwright.config.ts里通过环境变量决定用哪份配置。这样项目组之间共享同一套用例环境不一样就换一个配置不需要把用例复制N份。我做过一个很傻的事曾经把预发布环境的账号写死在用例里结果测试环境跑挂了。排查半天发现是账号在测试环境没有权限。后来改成从env.json读取类似问题再没出现过。这个例子也侧面说明数据层抽离不是锦上添花是避免事故的必要手段。另外建议在config里加一个校验如果环境变量没设直接报错提醒配置者检查避免有人漏配置导致脚本在错误环境上乱跑。我就被这种“看起来好像跑了实际跑错地方”的情况坑过一次。5.3 标签体系与回归策略第二条规矩是标签要规范。Playwright支持给test.describe或test挂tag比如login、smoke、p0。这条看起来很基础但在多项目组协同的时候特别好用。我用一套固定的标签体系模块名比如login、order、payment标识业务模块优先级p0核心流程、p1重要、p2一般回归级别smoke冒烟每次发版都跑、full全量回归然后CI里按需要过滤比如冒烟只跑smoke全量回归跑full。开发本地也可以只跑自己改动模块的用例比如只跑login。这个标签体系说白了就是给用例做索引让“可执行文档”在几十个spec文件里依然能被快速定位。实际操作中我会在用例库里专门建一个tag说明文档写清楚每种tag的含义和使用场景。不然一个月之后你自己也会忘了p2和p3的区别是什么。5.4 公共逻辑抽取与用例独立性第三条规矩是页面操作封装到Page Object但用例逻辑不要过度抽象。我的做法是页面元素和单步操作封装成方法比如输入手机号、点击登录这种动作放Page里但用例里的业务顺序、断言标准必须留在spec里让每条用例自包含、可独立运行。用一句话概括封装动作不封装业务。动作封装是为了让用例好读业务不封装是为了让用例清晰、可追踪。如果一个操作被多个用例共用我倾向于做一个fixture作为公共前置而不是在用例之间互相调用。这样每条用例都是独立的“文档章节”互不依赖跑到哪条失败就知道哪条出问题排查成本最低。我踩过的坑是曾经图省事在用例A里调用了用例B的登录步骤结果B先挂了A也跟着失败排查的人花了一个小时才发现根因在B。后来所有共用步骤都放进fixture再没出过这种连环爆炸。6. 下一步AI辅助生成“可执行文档”的探索6.1 为什么想到用Agent来做这件事细节维护多了以后我开始想一个问题可执行文档能不能由机器自动生成现在我在试LLM Agent方向比如“基于langchain开发一个能读取测试用例自动生成UI自动化测试脚本的agent”。这个思路其实和我做的“可执行文档”天然契合——可执行文档本身就是人和机器都能读的中间产物。我不认为AI能一步到位地生成稳定可跑的测试脚本。一个没有上下文、没有页面结构信息的AI生成的代码大概率是“看起来对、跑起来废”的。但AI绝对可以作为“翻译引擎”把业务描述翻译成Playwright骨架代码再由测试工程师补充数据和断言细节。我随手写过一个小Demo思路是让AI读取Markdown格式的用例描述输出一个spec文件骨架。效果是骨架的框架代码基本能用但断言覆盖和业务注解需要人补充。这个结果符合预期定向工具比通用对话更能减少返工。6.2 我尝试的路线让LLM读取结构化用例产出Playwright脚本我给LLM的准备材料很简单一份结构化的用例描述步骤期望结果加一些项目里已有的Page Object命名。Prompt里的关键约束是“不要实现Page对象内部细节只生成spec层的用例骨架”这样生成结果的可控性会好很多。下面是一个简化的Prompt骨架你是一名资深UI自动化测试工程师使用Playwright和TypeScript。 请根据下面的业务用例生成一个test.describe块。 要求 1. 用例标题用业务目标描述不用操作细节 2. 每个操作步骤用test.step包裹step名称用业务语言 3. 页面操作一律调用已有的Page Object方法不要直接写selector 4. 断言使用web-first断言例如toBeVisible、toHaveText 业务用例 【模块】登录 【前置】用户已注册且密码正确 【步骤】打开登录页 → 输入已注册手机号 → 输入密码 → 点击登录 【期望】跳转到首页右上角显示用户昵称生成的骨架我再手动补充数据文件和Page Object调用。实践下来初稿能节省50%左右的框架代码时间但“每条用例的断言是否覆盖到位”“业务背景注解是否准确”这类问题依然需要人把关。所以我把这个能力定位成“AI辅助起草”而不是“AI全自动维护”。可执行文档的价值不在于省掉人而在于让人把精力放到真正的业务验证上。6.3 边界与建议先别急着全自动如果你想尝试这条路线我的建议是先把现有测试用例库里最核心的几十条用例规范成“可执行文档”跑通维护流程再引入AI辅助。原因很简单AI生成脚本很像让一个实习生照着手册干活手册本身清晰实习生才能干对手册乱糟糟AI只会把乱糟糟放大。另外AI生成的脚本有一条硬性门槛必须走完整的review流程至少要有人跑通一遍、确认断言有效才能进入主用例库。这一步可以靠playwright test运行结果来把关但最终的用户反馈和业务匹配度还得靠人来判断。说回最初的问题把测试用例变成“可执行文档”最值钱的不是自动化覆盖率上去了多少而是协作模式变了。以前开发和测试之间靠“文档截图”沟通现在靠“可以直接跑的用例自带现场的报告”沟通。我自己的体会是改造的前两周最痛苦因为要把旧用例按新规范一点点重写但从第三周开始收益就上来了——开发开始主动跑用例产品也愿意在需求评审时翻测试报告测试用例本身变成了团队共同维护的资产。如果你想开始别贪多挑一条核心业务流程按上面的样式写三个用例跑通报告和命令行的体验再逐步铺开。等开发第一次自己用--grep跑出一条用例、然后在报告里看到完整trace回放的时候你就知道这事成了。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。