首页 / Playwright 入门教程 / 网络请求拦截 route

Playwright 入门教程

网络请求拦截 route

本教程共 59 篇 · 第 30 篇 · 更新于 2026-08-04 · 约 12 分钟阅读

Playwright网络拦截routefulfillabort请求修改

本节目标:学完能拦截页面发出的任意网络请求,并决定让它放行、改掉还是直接拦下。

写自动化测试,最怕页面依赖外部接口。接口慢、不稳定、还常常超时,测试一下午跑不通。Playwright 给了你一把剪刀,能在请求出门前就拦住它。这把剪刀叫 page.route()

为什么需要拦截请求

浏览器打开一个页面,背后会发起几十上百个请求:HTML、CSS、图片、接口数据。这些请求里有的是咱们的测试重点,有的纯粹是干扰。

举个例子,你想测一个列表页能不能正确渲染。可页面每次都去拉一张大图,图加载慢就把测试拖垮了。这时候把图片请求拦了,测试又快又稳。

Note

route(路由拦截)能监听、修改、甚至替浏览器伪造任意一个请求,HTTP 和 HTTPS 都行,包括 XHR 和 fetch。

三种处理方式

page.route() 接收一个 URL 匹配规则,和一个处理函数。处理函数里拿到的 route(路由)对象,有三个最常用的方法:

  1. route.continue():按原样放行,也可以改头改尾再放行。
  2. route.fulfill():自己造一个响应返回,请求根本不发出去。
  3. route.abort():直接掐断,告诉浏览器请求失败。

这三个方法互斥。一条请求只能挑一个调,调完就定案了。

拦截 URL 的规则

匹配规则有两种写法。一种是 glob 通配符,上手简单:

// 拦掉所有 .css 文件
await page.route('**/*.css', route => route.abort());

// 一次拦多种图片格式,用花括号列选项
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

另一种是正则,逻辑更灵活:

// 拦截 URL 以 png 或 jpeg 结尾的请求
await page.route(/(png|jpeg)$/, route => route.abort());

glob 的规则就四条,记住少踩坑:

  • 单个 * 匹配任意字符,但不跨斜杠
  • ** 匹配任意字符,包括斜杠
  • 花括号 {a,b} 表示「其中之一」。
  • 反斜杠 \ 用来转义特殊字符。
Tip

glob 必须匹配整个 URL,不是匹配其中一段。所以 *.js 命中不了 https://example.com/path/file.js,得写 **/*.js。规则再复杂一点,就别硬凑 glob 了,直接换正则。

拦下静态资源,让测试更稳

下面这个例子在每一条测试前拦掉 CSS,页面照样能测,渲染细节先不管。

import { test, expect } from '@playwright/test';

test.beforeEach(async ({ context }) => {
  // 每个测试前都拦掉 css 请求
  await context.route('**/*.css', route => route.abort());
});

test('没有样式也能加载页面', async ({ page }) => {
  await page.goto('https://playwright.dev');
  // 这里测试页面结构,不关心样式
});

只拦当前页面,用 page.route();想连弹窗、新标签页一起拦,用 context.route()。两个都命中同一条请求时,page 上的先跑

放行时顺手改一改

有时候请求得发出去,但你想动动手脚。比如删掉一个请求头:

await page.route('**/*', async route => {
  const headers = route.request().headers();
  delete headers['X-Secret'];   // 删掉敏感请求头
  await route.continue({ headers });
});

也能把请求改成 POST 再放行:

await page.route('**/*', route => route.continue({ method: 'POST' }));

直接掐断请求

图片太多拖慢测试,直接 abort:

// 按扩展名拦
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

// 或者按资源类型判断,比列扩展名靠谱
await page.route('**/*', route => {
  return route.request().resourceType() === 'image'
    ? route.abort()
    : route.continue();
});

resourceType() 返回的是浏览器自己的分类:documentstylesheetimagescriptxhrfetch 等。按类型拦比拼扩展名准,因为很多接口 URL 上根本没有后缀。

造一个假响应

fulfill() 是三兄弟里最有用的一个:请求根本不出门,你直接把响应递给浏览器。

await page.route('**/api/user', route => route.fulfill({
  status: 200,
  contentType: 'application/json',
  body: JSON.stringify({ name: '码上学', vip: true }),
}));

这一招的完整玩法是下一章的主题,这里先混个脸熟。

多个 route 撞车了怎么办

这是新手最容易翻车的地方。

Warning

多个 route 命中同一条请求时,执行顺序是注册顺序的反过来——最后注册的那个先跑

这么设计是有道理的:后写的规则能覆盖前面的。夹具里先铺一层通用拦截,测试里再写一条更具体的,具体的自然排在前面。

await page.route('**/*', route => route.abort());     // 注册第一,最后跑
await page.route('**/api/**', route => route.continue()); // 注册第二,先跑

上面这段里,接口请求会被第二条放行,压根轮不到第一条。

想让前面注册的处理函数也有机会跑,别用 continue(),改用 fallback()——「这单我接不了,往下传」。

// 注册第一:兜底,负责 GET
await page.route('**/api/**', async route => {
  if (route.request().method() !== 'GET')
    return route.fallback();
  await route.fulfill({ json: { list: [] } });
});

// 注册第二:先跑,只管 POST
await page.route('**/api/**', async route => {
  if (route.request().method() !== 'POST')
    return route.fallback();   // 不是 POST,交给上面那个
  await route.fulfill({ status: 201 });
});

continue() 是「我拍板了,直接发出去」,fallback() 是「我处理不了,往下传」。分清这两个,链式拦截就不会乱。

用完记得清掉

拦截规则会一直挂着。在夹具或 beforeEach 里注册的规则,容易漏到下一个测试里去,制造出莫名其妙的偶发失败。

只想拦一次,加 times

await page.route('**/api/config', route => route.fulfill({ json: { beta: true } }), { times: 1 });

手动清理有两个方法:

await page.unroute('**/api/config');   // 清掉这个规则下的所有处理函数
await page.unrouteAll({ behavior: 'wait' });  // 全清,并等正在跑的处理函数收尾
Tip

在夹具的收尾阶段清理时,behavior: 'wait' 是最稳的选择。直接清可能让还在执行的处理函数抛出没人接的异常。

什么时候该用它

请求拦截适合这几类场景:屏蔽无关资源提速、模拟接口异常、构造测试数据。它和下一章的 Mock(模拟响应)、第 32 章的响应断言是成套的组合拳。

我的原则是:能不改真实接口就别改。先想清楚到底要拦什么,别一上来就 **/* 全拦,那样测出来的东西离真实环境太远。

小结

  • page.route() 拦当前页面,context.route() 连弹窗和新标签一起拦,两者都命中时 page 的先跑。
  • 处理函数里三选一:continue() 放行、fulfill() 造假响应、abort() 掐断。
  • URL 匹配用 glob 或正则;glob 里 * 不跨斜杠、** 跨斜杠,且必须匹配整个 URL。
  • resourceType() 拦资源,比拼扩展名可靠。
  • 多个 route 撞车时,后注册的先跑;想往下传用 fallback(),不是 continue()
  • 规则会一直挂着,用 times 限次或 unroute / unrouteAll 清理,避免污染后续测试。

下一章把 fulfill() 展开讲透,学会用假数据完全替掉后端。