TUTORIALDevOps & toolsFETCHED
Playwright 网络拦截与 Mock
Playwright 网络拦截与 Mock 本章介绍如何使用 Playwright 拦截、修改和模拟网络请求,实现不依赖后端 API 的独立测试。 什么是网络拦截 网络拦截允许你在浏览器发起 HTTP 请求时进行干预。 你可以阻止请求、修改响应、返回模拟数据,让测试不依赖外部 API 或网络状态。 page.route() 拦截请求 page.route(url, handler) 用于拦截匹配指定 URL 模式的所有网络请求。..
Updated 7/16/2026
Tutorial detail pages default to noindex.
Playwright 网络拦截与 Mock
本章介绍如何使用 Playwright 拦截、修改和模拟网络请求,实现不依赖后端 API 的独立测试。
什么是网络拦截
网络拦截允许你在浏览器发起 HTTP 请求时进行干预。
你可以阻止请求、修改响应、返回模拟数据,让测试不依赖外部 API 或网络状态。
page.route() 拦截请求
page.route(url, handler) 用于拦截匹配指定 URL 模式的所有网络请求。
基本拦截
实例
test('拦截所有图片请求', async ({ page }) => {
// 拦截所有 PNG 和 JPEG 图片
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// 导航到页面(图片不会被加载)
await page.goto('https://www.runoob.com/');
// 页面可能加载更快,因为没有下载图片
});
// 拦截所有 PNG 和 JPEG 图片
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// 导航到页面(图片不会被加载)
await page.goto('https://www.runoob.com/');
// 页面可能加载更快,因为没有下载图片
});
URL 匹配模式支持以下格式:
| 模式 | 示例 | 说明 |
|---|---|---|
| 完整 URL | 'https://api.example.com/users' | 精确匹配 |
| Glob 通配符 | '**/api/**' | ** 匹配任意路径 |
| 正则表达式 | /\/api\/v\d\/users/ | 灵活匹配 |
| 函数 | url => url.includes('/api/') | 编程逻辑匹配 |
route.abort() 阻止请求
实例
test('阻止第三方分析脚本', async ({ page }) => {
// 阻止 Google Analytics 等分析脚本
await page.route('**/*analytics*', route => route.abort());
// 阻止 CSS 文件
await page.route('**/*.css', route => route.abort());
await page.goto('https://www.runoob.com/');
// 页面以无样式形式加载,但不影响功能测试
});
// 阻止 Google Analytics 等分析脚本
await page.route('**/*analytics*', route => route.abort());
// 阻止 CSS 文件
await page.route('**/*.css', route => route.abort());
await page.goto('https://www.runoob.com/');
// 页面以无样式形式加载,但不影响功能测试
});
route.fulfill() 模拟响应
route.fulfill() 是最强大的 Mock 方法,可以返回任意自定义响应。
Mock JSON 数据
实例
test('Mock 用户列表 API', async ({ page }) => {
// 拦截用户列表 API,返回模拟数据
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
json: [
{ id: 1, name: 'runoob_user', email: '[email protected]' },
{ id: 2, name: 'test_user', email: '[email protected]' },
],
});
});
await page.goto('https://www.runoob.com/users');
// 页面会显示 Mock 数据而不是真实的 API 数据
});
// 拦截用户列表 API,返回模拟数据
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
json: [
{ id: 1, name: 'runoob_user', email: '[email protected]' },
{ id: 2, name: 'test_user', email: '[email protected]' },
],
});
});
await page.goto('https://www.runoob.com/users');
// 页面会显示 Mock 数据而不是真实的 API 数据
});
Mock 错误响应
实例
test('测试 API 错误处理', async ({ page }) => {
// 模拟 500 服务器错误
await page.route('**/api/data', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
json: { error: '服务器内部错误' },
});
});
await page.goto('https://www.runoob.com/dashboard');
// 验证错误提示是否正常显示
await expect(page.getByText('服务器内部错误')).toBeVisible();
});
// 模拟 500 服务器错误
await page.route('**/api/data', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
json: { error: '服务器内部错误' },
});
});
await page.goto('https://www.runoob.com/dashboard');
// 验证错误提示是否正常显示
await expect(page.getByText('服务器内部错误')).toBeVisible();
});
route.continue() 修改后继续
不是直接返回模拟数据,而是修改真实请求或响应后继续发送到服务器。
实例
test('修改请求头', async ({ page }) => {
await page.route('**/api/**', async (route, request) => {
// 修改请求头,添加自定义 Header
const headers = {
...request.headers(),
'X-Custom-Header': 'RUNOOB-TEST',
};
await route.continue({ headers });
});
await page.goto('https://www.runoob.com/');
});
test('修改 POST 数据', async ({ page }) => {
await page.route('**/api/login', async (route, request) => {
// 覆盖 POST 请求的 body
const postData = JSON.parse(request.postData() || '{}');
postData.mode = 'test'; // 添加测试标记
await route.continue({
postData: JSON.stringify(postData),
});
});
});
await page.route('**/api/**', async (route, request) => {
// 修改请求头,添加自定义 Header
const headers = {
...request.headers(),
'X-Custom-Header': 'RUNOOB-TEST',
};
await route.continue({ headers });
});
await page.goto('https://www.runoob.com/');
});
test('修改 POST 数据', async ({ page }) => {
await page.route('**/api/login', async (route, request) => {
// 覆盖 POST 请求的 body
const postData = JSON.parse(request.postData() || '{}');
postData.mode = 'test'; // 添加测试标记
await route.continue({
postData: JSON.stringify(postData),
});
});
});
route.fetch() 真实请求
在 Mock 处理函数中,可以用 route.fetch() 实际发送请求并获取真实响应,然后修改。
实例
test('修改真实响应', async ({ page }) => {
await page.route('**/api/products', async route => {
// 先获取真实响应
const response = await route.fetch();
const body = await response.json();
// 修改响应数据
body.items[0].name = 'RUNOOB 修改后的商品名';
// 返回修改后的数据
await route.fulfill({
status: response.status(),
contentType: 'application/json',
json: body,
});
});
});
await page.route('**/api/products', async route => {
// 先获取真实响应
const response = await route.fetch();
const body = await response.json();
// 修改响应数据
body.items[0].name = 'RUNOOB 修改后的商品名';
// 返回修改后的数据
await route.fulfill({
status: response.status(),
contentType: 'application/json',
json: body,
});
});
});
Context 级拦截 vs Page 级拦截
| 方式 | 作用范围 | 适用场景 |
|---|---|---|
page.route() | 仅当前页面 | 单个页面的网络 Mock |
context.route() | 该 Context 下所有页面 | 多个页面共享的 Mock 规则 |
实例
test.beforeEach(async ({ context }) => {
// 在整个 Context 中拦截,所有页面生效
await context.route('**/*.css', route => route.abort());
});
test('测试 1', async ({ page }) => { /* 无 CSS */ });
test('测试 2', async ({ page }) => { /* 无 CSS */ });
// 在整个 Context 中拦截,所有页面生效
await context.route('**/*.css', route => route.abort());
});
test('测试 1', async ({ page }) => { /* 无 CSS */ });
test('测试 2', async ({ page }) => { /* 无 CSS */ });
HAR 文件录制与回放
HAR(HTTP Archive)文件可以记录页面的所有网络请求,然后回放模拟。
实例
test('使用 HAR 文件 Mock', async ({ page }) => {
// 从 HAR 文件加载网络记录并回放
await page.routeFromHAR('tests/hars/api-responses.har');
await page.goto('https://www.runoob.com/');
// 所有匹配的请求将从 HAR 文件中获取响应
});
// 从 HAR 文件加载网络记录并回放
await page.routeFromHAR('tests/hars/api-responses.har');
await page.goto('https://www.runoob.com/');
// 所有匹配的请求将从 HAR 文件中获取响应
});
# 录制 HAR 文件 npx playwright open --save-har=tests/hars/api-responses.har https://www.runoob.com/
HAR 文件适合 Mock 大量或复杂的 API 响应。先录制一次,之后测试都从 HAR 回放,不依赖真实服务器。