UI 自动化:Playwright 在业务回归测试中的实践

如果只是写一两条页面脚本,Selenium、Playwright 甚至录制回放工具都能把页面点起来。真正拉开差距的,不是“能不能点”,而是当 UI 自动化进入下面这些真实场景之后,谁更容易被做成一个长期可维护系统:

  • 每个版本都要跑 smoke
  • 每天凌晨跑回归
  • 同一套系统要覆盖多个角色
  • 失败后希望不重跑也能先判断大概问题
  • 后续还要进 Jenkins、进平台、进告警链路

我真正开始偏向 Playwright,不是在它“新”的时候,而是在我越来越清楚地看到:UI 自动化最贵的地方,从来不是写第一条 case,而是后面的等待、隔离、留痕、调试、失败分类和长期治理。Playwright 的价值恰好在于,它把这些高频成本尽可能提前做成了框架内能力。

一、为什么会把 Playwright 放在新项目的第一选择

判断 UI 框架时,不太看“语法是否更顺手”,而更看它会不会在半年后让项目还像一个系统,而不是一堆长脚本。

Playwright 在这方面有几个非常现实的优势。

1. 自动等待显著降低了基础噪声

现代前端页面里最常见的问题,不是元素永远不存在,而是:

  • 元素短时间不可见
  • 节点挂载了但还不能点
  • 页面骨架先渲染,数据后渲染
  • 表格列表先显示容器,再更新内容

如果框架等待太原始,后面脚本里就会迅速堆满:

  • sleep
  • 自定义轮询
  • 各种临时等待 patch

脚本不是不能跑,而是会越来越不可信。Playwright 至少把这部分基础成本压低了很多。

2. Browser Context 特别适合真实权限场景

真实后台系统很少是单角色单链路,更多情况是:

  • 管理员创建任务
  • 审核人审批
  • 普通用户查看结果

以前做这类场景,经常会被 cookie、session、localStorage 污染折腾。Playwright 的 Context 模型让多角色、多会话并行执行自然很多。

3. 失败现场留痕能力足够完整

UI 自动化最麻烦的一件事,不是失败本身,而是失败之后维护者什么都看不到。Playwright 的:

  • trace
  • screenshot
  • video
  • step 信息

对真实维护成本的影响,远比“这个 API 写起来是不是更优雅”大。

二、在项目里通常怎么搭 Playwright,而不是只写零散脚本

如果是你这种偏平台和工程化的方向,更推荐直接用 TypeScript 方案,而不是只写一些零散 JS 文件。

真实项目里,我常见的组合是:

  • @playwright/test
  • TypeScript
  • dotenv 或环境配置文件
  • Jenkins / GitLab CI
  • HTML report + trace + video
  • 测试数据工厂或接口造数脚本

原因也很直接:

  • @playwright/test 的 runner 足够稳定
  • TypeScript 对页面对象、locator 和任务模型约束更好
  • 后续多人协作和平台化接入更自然

三、我最反对的两种 Playwright 项目写法

1. 流水账 test 写法

test 文件里全是:

  • 点击
  • 输入
  • 等待
  • 截图
  • 断言

这种写法第一条很快,后面维护成本极高。

2. “我全都用 UI 入口造数据”

看起来更像真实用户链路,但一旦进入回归体系,执行速度和失败点都会被显著放大。很多后台系统更实际的做法,是接口造数 + UI 验证展示和交互。

四、更倾向的项目目录和层次

如果是一个准备长期跑回归的 Playwright 项目,更适合拆成下面这种结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ui-e2e/
├── playwright.config.ts
├── tests/
│ ├── smoke/
│ ├── regression/
│ └── probe/
├── pages/
│ ├── login.page.ts
│ ├── task-list.page.ts
│ ├── task-form.page.ts
│ └── review.page.ts
├── components/
│ ├── table.component.ts
│ ├── dialog.component.ts
│ ├── toast.component.ts
│ └── date-picker.component.ts
├── fixtures/
│ ├── auth.fixture.ts
│ ├── env.fixture.ts
│ └── artifact.fixture.ts
├── data/
│ ├── builders/
│ ├── factories/
│ └── templates/
├── utils/
│ ├── waits.ts
│ ├── tracing.ts
│ ├── screenshots.ts
│ └── classify.ts
└── reports/

这里最重要的不是目录漂亮,而是职责边界明确:

  • 页面负责页面语义
  • 组件负责复杂控件
  • fixture 负责环境和上下文
  • data 负责测试数据准备
  • utils 负责公共治理能力
  • tests 只表达业务意图

五、怎么设计角色隔离,而不是把所有登录态混在一起

Playwright 很强的一点,是它天然适合做多角色场景。但如果设计不好,也一样会乱。

通常会分三层来处理。

1. Storage State 层

为不同角色单独维护:

  • admin-state.json
  • reviewer-state.json
  • operator-state.json

2. Context 层

每条业务链路里,不同角色使用不同 Browser Context,而不是在同一个上下文里来回切身份。

3. Fixture 层

通过 fixture 把环境、角色、产物保存路径、trace 策略注入进去,而不是让每个 test 自己拼。

这样做的价值是:登录态、任务上下文和失败产物天然绑定

六、一个真实业务回归案例,怎么拆才不会越来越重

假设要覆盖“任务创建 -> 提交审核 -> 审核通过 -> 普通用户查看结果”这条后台核心链路,不适合写成一条从头点到尾的超长 E2E,而更适合按职责拆开。

场景 1:管理员创建任务

  1. 用管理员 storage state 打开任务页
  2. 点击新建任务
  3. 填写标题、类型、时间范围
  4. 提交并断成功提示
  5. 断言列表里出现新任务

场景 2:审核人审批任务

  1. 新开 reviewer context
  2. 进入待审核列表
  3. 搜索任务名
  4. 打开详情并审批通过
  5. 断言状态切换成功

场景 3:普通用户查看结果

  1. 新开 operator context
  2. 进入结果页
  3. 搜索任务
  4. 断言最终状态是“已通过”

这样拆的好处是:

  • 每条脚本失败面更小
  • 多角色边界更清晰
  • 后续 smoke 和 regression 组合更灵活

七、测试数据怎么处理,才不会让 UI 自动化背锅

很多 UI 自动化失败,看上去是页面问题,实际上是:

  • 没有可用数据
  • 测试对象状态不对
  • 名称冲突
  • 环境里已有脏数据

所以通常会把测试数据准备显式化,而不是让 case 暗中依赖环境状态。

常见做法是两种。

1. 接口造数

在 Playwright 执行前,先调用接口准备:

  • 任务
  • 审批单
  • 用户状态

这种方式快、稳定、适合 nightly regression。

2. UI 完整造数

更贴近用户真实路径,但速度慢、失败点多。通常只把它保留给少量高价值 smoke。

所以对很多后台系统来说,更实际的方案通常是:

接口造数 + UI 验证展示和交互 + 少量核心链路保留完整 UI 入口

八、失败现场会保留什么,才能不靠重跑猜问题

如果这个 Playwright 项目准备长期跑,通常会要求失败时至少保存:

  • 当前页面截图
  • trace
  • video
  • 当前 URL
  • 当前角色
  • 最近一步操作说明

如果再深一点,还会把:

  • console error
  • 网络失败摘要
  • 页面关键请求耗时

一起保留下来。

这样 Jenkins 一红,维护者不用立刻本地重跑,先看 trace 就能大致判断:

  • 是定位失败
  • 是弹窗遮挡
  • 是接口慢导致页面没稳定
  • 还是页面逻辑真有问题

九、Playwright 怎么接进 Jenkins 和平台,而不是只在本地跑

如果是长期项目,更适合把 Playwright 设计成天然能被两类系统消费。

1. Jenkins

负责:

  • 定时任务
  • smoke / regression 调度
  • 构建号上下文
  • 产物归档

2. UI 自动化平台

负责:

  • 任务创建
  • 环境、角色、浏览器管理
  • 历史趋势
  • 失败分类
  • 告警判断

所以 Playwright 项目最好一开始就能输出结构化结果,而不只是 HTML 报告。否则后面进平台会很别扭。

十、为什么 Playwright 不是“天然稳定”,而是“更容易被做稳定”

这点非常重要。这里很容易被误解成:换成 Playwright 后脚本自然就稳了,这不现实。

它只是把几个基础问题处理得更顺手,但下面这些事情仍然需要自己补齐:

  • 页面对象分层
  • 稳定定位策略
  • 测试数据治理
  • 等待条件显式化
  • 高噪声 case 清理
  • 失败现场分类

否则再好的框架,也会被写成一堆“比以前现代一点,但一样脆”的脚本。

十一、怎么判断一套 Playwright 回归进入了“可维护状态”

更值得看的真实信号是:

  • 新增 case 时,主要是在复用页面对象和业务动作,而不是复制点击脚本
  • 多角色任务执行时,不会频繁出现登录态污染
  • 失败后不用本地先复现,也能从 trace 和 screenshot 看出大概问题
  • 每日 smoke 的稳定性足够高,已经能作为发布参考
  • 页面变更后,改动更多集中在 pages/components/,而不是全局搜 test 文件
  • 高噪声 case 能被识别并逐步治理,而不是一直留在任务里污染结果

如果这些结果没有出现,说明 Playwright 只是“换了一个更新的语法”,还没真正进入工程化状态。

十二、结语

Playwright 在业务回归测试里的真正价值,不是把浏览器点起来,而是让 UI 自动化最昂贵的几件事更容易治理:等待、隔离、留痕、调试、多人协作和平台接入。对新项目来说,它通常是更合理的起点;但真正决定它值不值钱的,仍然是你有没有把它组织成一套长期可维护、可被团队信任的系统。

本章延伸阅读