Playwright 现在已经是很多团队做端到端测试的主流选择。它自动等待、跨浏览器、debug 工具都很顺手,比起早期的 WebDriver 方案确实舒服太多。但真正把测试铺开到复杂业务场景时,总会遇到几个麻烦:外部接口不稳定、页面跳转容易丢上下文、移动端适配问题在桌面端发现不了。这篇文章想围绕 Playwright 的三个高级用法展开:网络拦截、多页面测试和移动端模拟。它们不是炫技,而是用来解决实际工程问题的。

很多团队会遇到一个问题:基础测试用例跑通了,但一到复杂场景就不知道怎么写。不是 API 不熟练,而是对 Playwright 的页面模型和网络模型缺少一套系统的理解。这篇内容会从三个常见的方向切入,聊聊它背后的设计思路和在实际工程里的取舍。
网络拦截:把不可控的网络请求变成可控的测试条件
端到端测试最怕的不是代码写错,而是环境抖动。你调用第三方支付接口偶尔超时,开发环境返回的数据不完整,或者某个公共 CDN 资源不稳定,测试就会闪红。这时用例本身没有问题,问题在外部依赖。Playwright 的 route API 就是用来处理这类场景的。
网上关于 route 的介绍,大多数是直接给一段 fulfill 的 mock 示例。但在真实项目里,你需要先弄清一个前提:是拦截整个 context 还是单个 page。context.route 对该上下文内所有页面以及后续新打开的页面生效,page.route 只作用于当前页面。在多页面测试中,如果你只写 page.route,点击后打开的新页面不会继承这个规则。很多测试脚本陷入时好时坏的怪象,根因就在这里。
const context = await browser.newContext();
await context.route('**/api/order/**', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 1, status: 'paid' })
});
});
这个模式在测试里很常用,但要注意一个度:如果你把所有请求都 mock 掉,测试就变成了契约测试,失去了端到端验证前端与后端集成的意义。我的建议是只拦截两类请求:一类是第三方依赖,另一类是用于构造故障场景的请求。业务核心链路里的接口,能走真实服务就走真实服务。
route 的处理方式其实有三种,不只是 fulfill。
| 处理方式 | 用途 | 示例场景 | 注意事项 |
|---|---|---|---|
| continue | 修改后继续原始请求 | 注入 token、替换 CDN 域名 | 真实服务仍然会被调用,需要保证其可用性 |
| fulfill | 伪造响应 | 依赖不稳定接口、模拟异常数据 | 响应结构要和真实接口保持一致 |
| abort | 中止请求 | 模拟断网、资源加载失败 | 要确认页面是否有兜底逻辑,避免连带崩溃 |
一个容易被忽略的细节是路由匹配顺序。多个 route 规则如果同时命中同一个请求,Playwright 会按照注册顺序的逆序处理,也就是后注册的先执行。如果你在测试里既想对全局请求做日志记录,又想 mock 某个特定接口,注意不要让全局的 continue 抢先 fulfill。建议把更具体的规则往后注册,或者通过判断 URL 提前分流。
多页面测试:页面切换的坑都在 context 管理上
另一个高频场景是多页面。用户点击“去支付”“查看详情”,浏览器打开新标签页,传统测试框架处理起来很别扭。Playwright 的设计里,page 不是孤立存在的,它由 BrowserContext 统一管理。这个 context 才是会话状态的容器。所以当你处理新开页面时,不应该去猜测窗口序号,而是监听 context 的 page 事件。
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.click('text=打开新窗口')
]);
await newPage.waitForLoadState('domcontentloaded');
console.log(newPage.url());
这里用 Promise.all 是有讲究的。如果你先点击,再等待 page 事件,事件可能在监听注册之间已经触发,你就会错过它。反过来,你先设置监听,再点击,就能在事件发生前挂好钩子。这是一种常见且稳定的 async 事件处理模式。
多页面还有一个容易忽略的地方:每个页面对象是独立的,你不能用旧 page 去操作新页面。很多测试脚本在这个位置出 bug,因为代码里还留着原来的 page 变量。另外,所有页面共享 context 的 cookie 和 localStorage,登录态会自动同步。这对多页面流程是好事,但如果测试用例之间互相干扰,解决方案不是去清理页面,而是为关键场景单独创建 context。
多页面测试典型的问题,我整理了一下:
- 新页面还没加载完就去操作元素,导致选择器匹配不到
- 使用了全局 page 变量,而不是当前活动页面的引用
- 忽略了 popup 事件的监听,导致新窗口没有正确关闭
- 多个测试复用一个 context,导致 cookie 和存储污染
最后一个问题尤其隐蔽。比如测试 A 在支付页面设置了一个优惠券状态,测试 B 打开的却是同一个 context,就可能会继承这个状态。如果你不想费心去清理,在测试中为每个用例创建独立的 context 是最稳妥的做法。
移动端模拟:改视口只是第一步
移动端模拟是另一个容易被低估的功能。很多人以为把 viewport 改成 375×812 就等于模拟 iPhone。实际上,响应式网站会根据 UA、isMobile、hasTouch 等特征决定渲染哪种版本。只改视口,浏览器仍会以桌面版 UA 请求页面,很多站点会直接返回电脑版内容。
Playwright 提供了一整套设备描述符,分布在 devices 数组中。你可以直接取用,也可以在此基础上覆盖自己需要的参数。下面这段配置模拟了一台 Galaxy S9+ 的设备特征,同时开启地理位置权限,并往页面注入了定位信息。
const { devices } = require('playwright');
const galaxy = devices['Galaxy S9+'];
const context = await browser.newContext({
...galaxy,
permissions: ['geolocation'],
geolocation: { longitude: 116.39, latitude: 39.9 },
hasTouch: true
});
const page = await context.newPage();
这里的 hasTouch 在大多数设备描述符里已经包含,但如果你自己构造移动端配置,很容易漏掉。hasTouch 决定浏览器是否触发触摸事件,isMobile 则会影响 meta viewport 的解析。这两个字段加上 deviceScaleFactor,共同决定了渲染行为。在你排查移动端截图和布局差异的时候,先从这几个字段入手。
设备模拟不是没有局限。一些移动端特有的系统行为,比如软键盘弹出、手势返回、原生弹窗,模拟器并不会完整复现。更现实的问题是,桌面浏览器中的触摸事件和真机上的物理滚动、像素密度仍有差异。所以设备模拟更适合做功能回归和前端适配的初筛,最终上真机或云测设备依然有必要。
常见误区与落地建议
最后聊几个我在代码评审里经常见到的误区,如果能避开,你的测试稳定性和维护性都会有明显提升。
- 滥用 waitForTimeout,固定等几秒。应该优先使用 Playwright 的自动等待和 expect 的轮询断言,让测试的节奏跟随真实状态,而不是拍脑袋的数字。
- 在移动端模拟中只设置 viewport,忽略 userAgent 和 isMobile,导致拿到的还是桌面版页面。
- 网络拦截规则留在全局,测试用例间互相污染。更好的做法是在 beforeEach 中注册规则,在 afterEach 中卸载,保证每个用例从干净的网络环境开始。
- 在多页面测试里仍然用 page.waitForTimeout 去等待新页面,而不是监听 page 事件。
落地方面,我的建议是:
- 把设备模型、路由规则这些公共配置抽成 fixture 或者独立的常量文件,而不是散落在每个测试里。
- 为网络拦截命名场景。比如 mock 订单、mock 支付,测试用例只需要选择场景,不需要重复写 route 回调。
- 在 CI 中适当控制并发,因为每个 context 都会占用真实浏览进程,worker 数过高会带来资源竞争。
- 配合 trace 查看器分析多页面跳转和网络请求时序,很多时候误报其实来自浏览器内部行为,而不是测试逻辑。
我自己的经验是,高级技巧的核心不是熟悉更多 API,而是理解底层模型:context 管理页面和网络状态,设备模拟负责还原终端环境。模型清楚了,各种 API 只是不同问题下的对应解法。
回到最初的问题。网络拦截把不可控的依赖变成可验证的输入,多页面测试把复杂的用户路径拆成可追踪的页面流转,移动端模拟则让前端适配问题提前暴露。这三个能力单独使用都能解决一类问题,组合起来可以让你的测试环境更接近真实用户环境。Playwright 真正厉害的地方不在于某个 API,而是它给了你一层对浏览器行为的抽象,让你有能力把不确定性变成测试中的可控变量。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/756/