做了五年多的自动化测试,带过几个从零搭建测试体系的项目,我越来越确认一件事:e2e测试是软件质量保障里最容易被低估、也最需要认真对待的一环。很多人觉得e2e就是“用工具模拟用户点几下页面”,等到真正接手一个产品、团队、或者独立负责质量线的时候才发现,这套逻辑的深度远不止浏览器自动化。这篇文章我想把e2e从概念到落地拆开讲清楚,包括工具选型、用例设计、数据准备、CI接入、以及我踩过的那些坑。不管你是刚接触测试自动化的新人,还是正在苦恼e2e用例老挂的工程师,这里面的东西应该能直接拿来用。
1. e2e测试的本质:它到底在测什么
1.1 为什么我们不能只靠单元测试
先聊一个基础问题:既然我们已经写了大量的单元测试和接口测试,为什么还需要e2e测试?我见过太多团队把单元测试覆盖率堆到80%以上,结果上线前一个简单的登录跳转问题都没拦住,原因是单测和接口测试根本覆盖不到这个场景。
单元测试验证的是“函数输入输出是否正确”,接口测试验证的是“服务端接口返回是否符合契约”,但它们都无法回答一个问题:系统拆开跑没问题,合在一起跑真的没问题吗?举一个我实际遇到的例子:一个下单流程,后端接口单独测每个都通过,前端页面也正常渲染,但用户点击“提交订单”后,前端因为某个字段大小写不一致导致校验失败,接口压根没被调用。这种问题只有把真实浏览器、真实页面、真实前后端链路串起来才能暴露,这就是e2e测试存在的意义。
所以我的定义很简单:e2e测试是以真实用户视角,从入口页面开始,走完整条业务链路,验证系统各模块协作后业务结果正确的测试方式。它测的不是“某段代码”,而是“整个系统”。
1.2 e2e测试的边界:哪些不该交给e2e
搞清楚“e2e该测什么”之前,先得明白“什么不该测”。这也是e2e测试最容易跑偏的地方。很多团队写e2e写着写着,把接口返回的数据校验、异常分支、甚至CSS样式都扔了进来,结果用例又慢又脆弱。
我自己遵循两条边界。第一,业务规则校验优先下沉到单元测试。比如“折扣不能超过原价”“订单金额必须为正”,这类纯逻辑规则在单元测试里覆盖成本更低,也更稳定。e2e只验证真实用户能感知的关键路径。第二,一个e2e用例只走一条关键用户旅程,不要贪多。一个用例聚焦从登录到下单一气呵成,可以把中间步骤的细节校验尽量去掉,只关注最终业务结果和沿途最核心的状态节点。
1.3 什么样的项目一定需要e2e测试
不是所有项目都值得投入e2e。如果是纯内容展示型网站,或者生命周期极短的活动页,e2e的投入产出比很低。但下面这几类项目,我建议尽早把e2e建起来:
- 核心业务涉及支付、下单、预约等资金或交易链路的产品,哪怕一个小改动都可能造成资金损失。
- 多系统协作的SaaS服务,涉及前端、网关、多个后端服务、甚至外部第三方接口的联动场景。
- 频繁迭代、且改动波及面大的成熟产品,e2e在这里能充当“最后一道护栏”,让迭代更敢动重构。
- 多人协作的前后端分离架构项目,前后端版本各自演进,e2e是契约一致性的最终验证者。
2. 工具选型:Playwright、Cypress与Selenium的取舍
2.1 主流e2e测试工具横向对比
说到落地,第一个要决策的是工具选型。我这几年前后用过Selenium、Cypress、Playwright,有资格做一个务实的比较。先看表格:
| 维度 | Selenium WebDriver | Cypress | Playwright |
|---|---|---|---|
| 原理 | WebDriver协议 | 浏览器内注入运行 | CDP协议直接操控浏览器 |
| 跨浏览器支持 | Chrome/Firefox/Safari/IE等全平台 | 仅Chrome系和Firefox | Chromium/WebKit/Firefox |
| 自动等待机制 | 无,需显式等待 | 内置自动重试 | 内置,智能度高 |
| 网络拦截与模拟 | 支持但麻烦 | 仅支持同域请求拦截 | 原生支持,路由拦截强大 |
| 多标签页/多用户 | 支持较弱,需切句柄 | 不支持多标签页,本质是单上下文 | 原生支持多页面、多浏览器上下文 |
| 并行执行 | 依赖外部机制 | 依赖运行时扩展 | 内建workers并行机制 |
| Trace调试能力 | 弱 | 有截图/录像,有限 | 内置Trace Viewer,记录完整操作轨迹 |
| 学习曲线 | 陡峭,封装繁琐 | 平缓,API直观 | 适中,API现代且易读 |
Selenium是资历最老的方案,生态庞大、兼容性广,但它的设计还停留在“用协议驱动浏览器”的年代,稳定性依赖测试者的封装水平。Cypress给我最大的困扰只有一点:它跑在浏览器内部,本质上不支持真正意义上的跨浏览器和多个页面上下文测试,这让很多真实的多系统联调场景无法覆盖。
2.2 为什么我最终在主力项目中选择了Playwright
我现在的新项目基本都优先推荐Playwright,原因集中在四个点。
第一是它的自动等待机制真正做到“智能”。不是简单的sleep固定几秒,而是会根据元素的可见、稳定、可操作状态自动重试,这让脚本的稳定性上了一个大台阶。第二,一条语句就能创建独立的浏览器上下文,相当于一个平行世界,每个用例天然隔离,不需要每次重新登录、清理数据。
第三,网络路由拦截的能力非常强。我可以在一行代码里mock一个第三方接口的响应,也可以强制让某个CDN资源超时,模拟弱网状态,这在模拟“支付回调异常”“第三方服务宕机”这类场景时几乎是降维打击。第四,Trace Viewer能记录下测试用例执行时的完整细节,包括每个操作的DOM快照、网络请求、控制台日志。用例失败时,直接在trace里看现场,排查效率是传统截图方式的十倍都不止。
2.3 用profile管理多套e2e场景配置
顺带提一点管理实践:在较大型的e2e测试体系里,用例数量多了之后,一套配置同时跑所有用例是很蠢的做法。我的方案是用profile区分不同场景。这里的profile不是一个复杂概念,本质上就是一组命名配置,用于切换不同的测试目标集、环境地址、账号体系、以及运行参数。
我常用的划分方式就是一个test-profile的配置文件列表,例如profile1对应“冒烟配置”,执行频率高、耗时短,只覆盖登录、首页、核心下单链路;profile2对应“回归配置”,执行频率低、覆盖面广;profile3对应“性能基线配置”,跑一些针对性场景。这样CI可以按需触发不同profile,而不是每次全量执行几十分钟。
下面是一个典型的profiles配置示例,用TypeScript枚举即可:
// profiles.ts export const TestProfiles = { smoke: { name: 'profile1-smoke', grep: '@smoke', workers: 4, timeout: 30000, retries: 1, outputDir: 'test-results/smoke', }, regression: { name: 'profile2-regression', grep: '@regression', workers: 6, timeout: 60000, retries: 2, outputDir: 'test-results/regression', }, performance: { name: 'profile3-perf', grep: '@perf', workers: 1, timeout: 120000, retries: 0, outputDir: 'test-results/perf', }, } as const;实际执行时,通过环境变量注入选择profile,如PROFILE=smoke npm run test:e2e,然后在配置文件里读取它来决定测试过滤和并发参数。这套模式用下来,维护成本降低了很多,CI的反馈速度也快了很多。
3. 实操:从零搭建一套稳定可维护的e2e测试体系
3.1 项目初始化与环境准备
工具选型确定了,就得动手搭建。下面以Playwright + TypeScript为例,走一遍从零到可运行的完整流程。这套流程我在不止一个项目里验证过,直接抄作业基本不会出差错。
首先初始化项目并安装依赖:
mkdir e2e-project && cd e2e-project npm init -y npm install -D @playwright/test typescript @types/node npx playwright installnpx playwright install会下载对应浏览器内核。国内网络环境下这一步偶尔会慢,可以考虑配置镜像或提前下载好浏览器压缩包到缓存目录,方法是在系统环境变量里设置PLAYWRIGHT_DOWNLOAD_HOST指向可用的镜像源。install完成后运行npx playwright --version确认安装成功。
接着在tsconfig.json里配置TypeScript支持,在playwright.config.ts里做基础配置:
// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', timeout: 60000, retries: 2, workers: 4, reporter: [['list'], ['html', { open: 'never' }]], use: { baseURL: process.env.E2E_BASE_URL || 'https://staging.example.com', trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure', viewport: { width: 1440, height: 900 }, locale: 'zh-CN', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, ], });这里重点解释几个必配项。retries用于用例失败自动重试,网络抖动、偶发超时导致的失败会被自动消化,但注意不要设太大,否则失败用例的排错成本反而变高。trace: retain-on-failure作用是失败时保留trace文件供排查,这是后来排查问题的利器。outputDir默认是test-results,建议按profile指定不同子目录,避免多次执行互相覆盖。
3.2 编写第一个真实业务链路用例
配置就绪后,从真实的登录-加购-下单链路入手,写第一个用例。这里不要先写那种只有登录跳转断言的小儿科用例,从一开始就模拟真实复杂度,后面维护才不吃亏。
// tests/order.spec.ts import { test, expect } from '@playwright/test'; test.describe('下单流程', () => { test('用户从登录到成功提交订单', async ({ page }) => { // 登录 await page.goto('/login'); await page.getByLabel('邮箱').fill('test-user@example.com'); await page.getByLabel('密码').fill('TestPass@123'); await page.getByRole('button', { name: '登录' }).click(); await expect(page.getByText('欢迎回来')).toBeVisible(); // 搜索并进入商品详情 await page.getByPlaceholder('搜索商品').fill('机械键盘'); await page.getByRole('button', { name: '搜索' }).click(); await page.getByText('机械键盘 87键').click(); await expect(page).toHaveURL(/\/product\/\d+/); // 加入购物车并结算 await page.getByRole('button', { name: '加入购物车' }).click(); await page.getByRole('button', { name: '去结算' }).click(); await expect(page.getByText('订单确认')).toBeVisible(); // 填写收货信息并提交 await page.getByLabel('收货人').fill('张三'); await page.getByLabel('手机号').fill('13800138000'); await page.getByLabel('详细地址').fill('北京市朝阳区某街道某号'); await page.getByRole('button', { name: '提交订单' }).click(); // 订单结果断言 await expect(page.getByText('订单提交成功')).toBeVisible(); await expect(page).toHaveURL(/\/order-result\/\d+/); }); });这个用例里值得注意的细节是定位方式。Playwright推荐getByRole、getByLabel这种用户可感知的定位器,而不是CSS选择器或XPath。因为后两者跟结构强耦合,前端重构一个class名用例立马就挂了;而getByRole、getByLabel更接近真实用户的感知方式,页面结构调整对它们影响有限。这也是e2e用例少返工的关键点之一。
toBeVisible这样的断言内置了自动重试,它会轮询等待直到条件满足或超时。所以不要在断言前后加waitForTimeout(3000)这类硬编码等待,那是造成脚本慢和不稳定的头号来源。实在需要等待的场景,优先用expect相关的自动重试,而不是sleep。
3.3 测试数据准备:API seeding与数据库清理
e2e测试最烦的是什么?直接通过UI准备数据。每执行一次用例都在UI上创建一条数据,慢且不稳定。正确做法是通过后端的API直接预置数据,也就是所谓API seeding。
Playwright的请求上下文可以直接发送HTTP请求,不需要额外依赖。看一个示例:
// helpers/seed.ts import { request } from '@playwright/test'; export async function seedOrderData(baseURL: string, token: string) { const context = await request.newContext({ baseURL, extraHTTPHeaders: { Authorization: `Bearer ${token}`, }, }); const resp = await context.post('/api/test/orders', { data: { userId: 'test-user-001', items: [{ sku: 'KB-87', qty: 1 }], address: { name: '张三', phone: '13800138000', detail: '测试地址' }, }, }); if (!resp.ok()) { throw new Error(`Seed数据创建失败: ${resp.status()} ${resp.statusText()}`); } await context.dispose(); }我坚持的原则是:能在准备阶段用API做的绝不在用例阶段用UI做。优惠券、商品库存、历史订单这些前置条件全部通过API接口预置,测试用例只需要关注核心的用户操作行为。用独立的测试账号数据时,确保各用例使用不同的数据,不要互相复用,这是消除用例间干扰的最直接手段。
数据清理就放在globalSetup里做。Playwright支持全局setup脚本,在跑所有用例之前执行一次。我一般在里面通过API或直连数据库清理测试环境里的脏数据,保证每次执行时基线干净。
3.4 接进CI并配置失败重跑的自动化动作
本地能跑只是第一步,e2e测试的价值最终要体现在CI门禁上。我把这套东西接进GitHub Actions之后,每次PR自动跑冒烟用例,合入主分支跑全量回归。下面是一个可直接用的工作流配置:
name: E2E Tests on: pull_request: branches: [main] push: branches: [main] jobs: e2e-smoke: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx playwright install --with-deps - run: npx playwright test --grep "@smoke" env: E2E_BASE_URL: ${{ secrets.E2E_STAGING_URL }} TEST_ACCOUNT_PASSWORD: ${{ secrets.TEST_ACCOUNT_PASSWORD }} - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/ retention-days: 7几个容易踩的坑在这里提醒一下。npm ci而不是npm install,保证依赖版本完全按锁文件安装,避免某次构建意外升级依赖导致用例挂掉。npx playwright install --with-deps会自动安装浏览器以及运行所需的系统依赖,这一步不能省略。上传报告这一步在失败时触发,方便人直接下载HTML报告和trace文件分析。
如果环境里没有现成的staging环境,CI里mockserver或容器化测试环境也可以,但成本会上去。这里还有一个建议:e2e用例切切实实打到后端多系统联调的staging环境上,才好端到端地暴露集成问题。如果你的CI没有一套完整的staging环境,先把环境搭起来,再谈用例编写。
3.5 环境隔离:多套环境如何共用一套用例
很多产品同时存在dev、staging、pre-production等多套环境。如果为每套环境单独维护一套用例,维护成本直接翻倍。我的做法很简单:用例里不硬编码任何环境相关的数据,全部通过环境变量或配置注入。
比如在playwright.config.ts里,用process.env.E2E_BASE_URL来读取当前环境地址,测试账号密码同样从环境变量读取。用例代码里永远用相对路径和变量引用,这样同一套用例在dev、staging、pre-production都能跑,区别只在CI里传入的环境参数不同。还建议把环境相关的域名白名单加到配置文件里。
4. 常见问题与排查技巧实录
4.1 脚本偶发失败与元素定位不稳定的处理
做e2e的人一定经历过“昨天还全绿,今天一跑挂五个”的崩溃时刻。我复盘下来,偶发失败的主要原因集中在三方面:前端渲染时机、元素定位方式不当、以及测试数据污染。
元素不稳定最典型的场景:点击一个按钮没生效。原因往往是按钮处于半加载状态或被遮罩层挡住。要解决的不是加等待,而是改用更贴近用户行为的定位器,同时让Playwright的自动等待去判断元素是否可交互。
具体建议是优先使用getByRole和getByLabel这类语义化定位器。一个常见反例是:开发给按钮加了个新外层div,导致CSS层级选择器挂掉;而getByRole('button', { name: '确认' })根本不关心外层结构,因为用户看到的就是一个叫“确认”的按钮。我在团队里定过一条规约:用例里禁止出现div > span > button这种多层级CSS路径。
另一个偶发失败源是页面跳转后的及时渲染。处理方式是做“目标状态断言”,比如跳到商品详情页后不要立刻点“加购”,先断言商品标题可见,让自动等待先生效再操作。
4.2 数据污染:用例之间互相影响的根源
用例A创建了一份订单,用例B默认列表里恰好出现了A的数据,于是一个断言开始随机失败。这类数据污染问题是e2e测试维护成本高的根源。
数据污染的核心解法是用例间数据彻底隔离。我给团队定的原则是:每个用例在准备阶段通过API创建自己的专属数据,数据里带入用例标识。例如用户名用test-user-{{用例id}}-{{时间戳}},这样即使数据没清理干净,也不会被其他用例误查到。
不能绕过这一点的场景,就强化清理流程。执行顺序上注意:不依赖用例执行顺序,每个用例在beforeEach里独立准备数据、独立断言结果。如果项目里有账号体系,尽量在用例里动态创建测试用户,而不是所有人共享同一个账号。我也是在动手维护了一个月的e2e用例后,才理解这个看似多余的要求是长期相安无事的前提。
4.3 验证码与第三方登录这样处理更务实
很多业务的登录流程都有验证码、短信验证码、甚至扫码登录。这些交互环节天然不适合e2e自动化。不可能为了测试真去收短信。
我见过最差的处理方式是让开发去掉验证码,这会让线上验证逻辑形同虚设。务实的做法是预留测试后门:只在测试环境生效,当请求头里带有特殊的测试标识时,跳过验证码校验,直接返回固定验证码。这个后门必须在生产环境关闭。
第三方登录的e2e就更麻烦些。如果应用支持GitHub或微信登录,在e2e环境里通常mock掉这个第三方跳转,直接返回一个预先构造好的授权回调。我的做法是在测试环境里架一个简单的mock授权服务器,页面点“微信登录”时重定向到mock服务器,它自动回调一个有效token给应用。这样既验证了应用侧处理授权回调的逻辑,又不依赖第三方外部服务。
4.4 Trace Viewer:让失败排查不再靠猜
这里必须认真推荐Playwright的Trace Viewer。启用方式非常简单,在配置里设置trace: 'retain-on-failure'即可。用例失败后,会在test-results目录生成zip文件。
打开它你能看到什么?不仅仅是错误截图和报错信息,而是失败瞬间整个页面的DOM快照、每个步骤执行时的页面状态、浏览器控制台输出、网络请求和响应。我经常遇到的场景是:一个用例在CI挂掉,本地跑又通过。如果是传统方式,基本靠猜或者本地反复重试。用Trace Viewer,我能直接回放进放失败时的每一步,看到请求返回了什么、DOM变了没有、是几行几列哪一步超时,三个小时压成一秒定位。
实际上排查用法是这样的:跑完失败用例后执行npx playwright show-trace test-results/.zip,就能打开一个可视化的回放界面,拖动时间轴看每一步。对比通过和失败的trace,差异往往一眼就能发现。这套能力是Playwright相比老一代工具最有价值的突破。
4.5 profile配置在CI里的推荐执行策略
最后再展开说说profile配置策略,因为这是我提到“e2e”时最想分享的先进实践。很多团队的e2e测试跑得很痛苦:回归太慢,每次合并都要跑半小时,等的人焦虑;又或者压根不敢跑,因为用例太不稳定。
用profile拆分场景后,策略就清晰了。PR阶段只跑profile1冒烟集,控制在3分钟以内,用--grep @smoke过滤。主分支push阶段跑profile2回归集,用--grep @regression,时长控制在15分钟以内。每周或发布前跑一次profile3性能基线集,用--grep @perf,记录关键链路的耗时作为性能基准。
实际配置只需要在CI工作流里使用不同的--project或--grep参数。设置好后执行时间分布合理,反馈既有速度又有深度。这样做的附加价值是:每个profile都有明确的owner团队,profile1挂了快查入口流程;profile3挂了找性能负责人定位。职责清晰之后,e2e体系的稳定性会有质的提升。
5. e2e用例设计中的关键细节与进阶实践
5.1 用例分层与打标签:让不同维度的测试需求各取所需
我在前面的profile配置里反复提到@smoke、@regression、@perf这些标签,这是Playwright里用@tag标注用例、再用--grep过滤执行的机制,也是当前组织大型e2e测试集最高效的手段。
一组合理的标签体系可以是这样的:
@smoke:冒烟级用例,覆盖核心链路的冒烟,进可跑、退可调@regression:中大型回归场景,比如完整下单、退款流程@perf:针对关键接口和流程的耗时统计场景@login、@order、@pay:按业务模块打的功能标签@optional:非关键路径的用例,可以接受偶尔失败而不阻断CI
实际操作中,只需在test()的title里写明标签,测试title建议如下:
test('@smoke @login 用户使用正确凭证登录成功后跳转首页', async ({ page }) => { // ... }); test('@regression @order @pay 用户完成下单并支付成功', async ({ page }) => { // ... });执行某个profile时,配置文件里的grep会负责过滤。对普通工程师来说,本质是给每条用例贴上“它是什么、跑在哪、挂了对谁有影响”的标签,方便分类而治。
5.2 视觉回归:e2e用例里该不该做快照截图
这是很多团队纠结的问题。Playwright原生支持toHaveScreenshot()做视觉对比。我的态度是谨慎使用。原因很简单:视觉对比用例极其容易因为非业务因素失败,比如字体加载延迟、动效未结束、甚至不同运行环境的渲染差异。如果冷静下来做视觉回归用例,动不动就会变成“噪音制造器”。
我的实践优先级是:先把功能逻辑层做好,再考虑视觉的一致性。视觉对比建议放在profile3这种非门禁的执行集里跑,或者只在关键页面(首页、登录页核心交互页)做快照比对,并且每个视觉用例都要写清楚排除区域,比如动态时间、随机广告位。
如果一定要把视觉比较放进门禁,可以考虑Playwright的快照配置里设置maxDiffPixelRatio: 0.01这种合理的容差值。但说实话,视觉回归更适合让视觉专用工具去做,e2e的主职还是业务结果的验证。
5.3 测试报告与失败通知
报告作为工程可观测性之一,质量提升也慢不下来的。Playwright默认生成HTML报告,打开后可以按用例、按项目、按profile查看执行结果,还能直接看accessibility快照和trace。
我的做法是配置HTML报告输出权重,并接入IM机器人的通知。CI流水线结束之后,自动解析playwright-report里的结果汇总,把通过率、失败用例列表、失败链接推到团队群里。谁引入了脆弱用例,谁改动了关键流程导致回归,人人可见,质量责任立刻落实到人。
一条能实际用到的命令是:CI结束之后执行npx playwright show-report playwright-report/在本地查看。如果是CI环境,就用html报告器的输出目录配置缓存,让团队成员可以从CI页面下载。报告即时可见,反馈链路更短。
5.4 从“跑起来”到“跑得稳”:稳定性的四个强调
我用了快三年e2e,总结出稳定性提升的四个方向供参考:
杜绝硬编码等待,能用断言等待和网络等待就不用sleep,硬等会让用例整体时长拉长且更易受环境波动影响。追求定位器可读性和稳定性,getByRole、getByLabel优先,基于文本内容次之,CSS路径再退一步,XPath能不用就不用。数据独立性优先于复用性,各用例独立创建数据,不依赖于其他用例执行完的状态。失败案例从trace数据拿起,无论如何不要先改断言去“让用例通过”。
能做到这四条的团队,e2e测试体系基本就从“能用”跨到“好用”了。
6. 我在项目实践中积累的三个额外经验
这一节不写系统性知识,纯粹分享几条我在实际项目里吃过亏或尝到甜头的经验。
第一条与“慢”和解。e2e跑得慢是正常的,它在做集成验证,本身就比单元测试和接口测试重。不要盲目追求e2e的数量,很多团队指标是“e2e用例数从50涨到200”,但其中150条都只是在不同页面重复点击同样的按钮,边际收益极低。资深工程师的价值在于“做少而精的链路验证”。测一个购物车页面,一条用例同时走“加购-改数量-移除-再结算”比写三四条各自独立的用例更稳更快。
第二条是“把e2e跑得快的价值看得跟测试覆盖一样大”。开发提一次PR要跑20分钟e2e,反馈周期长了,大家就会想办法绕过它。于是我们做了改造:把profile1冒烟控制在3分钟以内,开发每个PR只需要等3分钟,很快就形成everyone有心跑的习惯,这些在尝试强制全员跑半小时e2e时是绝对做不到的。
第三点是关于个人经验里的最优先建议:投入周期里花80%时间把关键的10%链路打造成“稳定得无聊”的用例,远比追求90%的路径覆盖更重要。一个每两周挂一次的单点用户量十万级的核心流程,团队会慢慢不相信e2e;反而是如果一条最核心链路半年没挂过,大家才愿意把trust交给它,后续增补用例也顺理成章。
另有一个长期的小技巧,我始终在手边留着一篇针对“Playwright网络等待策略”的笔记。记得刚开始用Playwright时,我以为waitForLoadState('networkidle')是万能的。经验证明,高频发请求的页面会让networkidle等待极久甚至超时,正确做法是明确等待页面里某个请求事件完成或者用断言等待最终结果。如果你踩到这类问题,翻一翻配置和trace会很快明白原因。
7. 一条实用的落地清单(直接去用)
结合前面所有的思考,我给出一条可以直接照抄的执行清单。在团队从零开始落地e2e时,按这个顺序推进会顺很多:
- 圈定核心链路清单,不超过10条,聚焦登录、核心浏览、加购、下单、支付回调、退单、成长链路这些资金与体验敏感路径。
- 确定profile划分,先把profile1冒烟建出来,再拓展pressure和regression配置。
- 安装并配置Playwright,配置好trace保留方案,确定报告输出和retry策略,用
--grep配合profile执行。 - 建立API seeding和数据库清理机制,从第一天就避免数据污染。
- 接入CI,先跑冒烟profile,通过之后逐步扩充回归、性能基线profile。
- 定期核查稳定性,对每条偶发失败的用例看trace找根因,不将就。
这个过程快的话两周能出一版,慢的话四到六周也能逐步成型。投入的回报在第一个上线夜被保护住的时候,所有人都能感受到。