用Playwright拦截和修改网络请求:不只是抓包那么简单
在 Web 开发和测试领域,抓包工具如 Fiddler、Charles 和 Chrome DevTools 早已成为开发者的标配。它们能让我们查看网络请求和响应内容,帮助定位问题。但当我们需要更深度的网络控制 —— 比如自动化修改请求、模拟各种异常场景、与 UI 操作无缝集成时,传统抓包工具就显得力不从心了。
Playwright 作为现代浏览器自动化框架,提供了一套强大而灵活的网络拦截 API。它不仅仅是一个 "抓包工具",更是一个能让你完全掌控浏览器网络层的 "网络编程接口"。通过 Playwright,你可以在请求发出前修改它,在响应到达应用前篡改它,甚至完全替换整个网络通信流程。
一、为什么 Playwright 的网络拦截与众不同
传统抓包工具工作在系统代理或浏览器扩展层面,而 Playwright 直接与浏览器的 DevTools 协议深度集成。这种架构带来了几个关键优势:
- 无侵入性:不需要修改系统代理或安装浏览器扩展,启动浏览器时自动配置
- 时序精确:拦截发生在浏览器内部,比外部代理更早捕获请求
- 自动化友好:所有操作都可以通过代码控制,完美融入自动化测试和爬虫流程
- 上下文隔离:每个浏览器上下文可以有独立的网络拦截规则,互不干扰
- 协议全面:支持 HTTP/HTTPS、WebSocket、Fetch 和 XHR 等所有现代 Web 协议
二、核心原理:page.route () 是如何工作的
Playwright 网络拦截的核心是page.route()方法。它允许你注册一个路由处理函数,当浏览器发出匹配特定 URL 模式的请求时,这个函数会被调用。
typescript
运行
// 基本语法
await page.route(urlPattern, async (route) => {
// 在这里处理拦截到的请求
// route对象包含了请求的所有信息和操作方法
});
URL 模式支持三种匹配方式:
- 精确匹配:
https://api.example.com/users - 通配符匹配:
**/api/users/*(最常用) - 正则表达式:
new RegExp('.*\\.json$')
当一个请求被拦截时,你有四种选择:
route.continue():允许请求继续发送,可选修改请求头、URL 或请求体route.fulfill():直接返回一个自定义响应,不发送真实请求route.abort():完全中止请求route.fetch():先获取真实响应,修改后再返回给应用
三、基础操作:从拦截到修改
3.1 拦截并记录所有请求
最简单的用法是记录所有网络请求,这比 Chrome DevTools 更适合自动化分析:
typescript
运行
import { chromium } from 'playwright';
async function logAllRequests() {
const browser = await chromium.launch();
const page = await browser.newPage();
// 拦截所有请求
await page.route('**/*', (route) => {
const request = route.request();
console.log(`[${request.method()}] ${request.url()}`);
console.log(` 资源类型: ${request.resourceType()}`);
console.log(` 请求头: ${JSON.stringify(request.headers())}`);
// 继续请求
route.continue();
});
await page.goto('https://example.com');
await browser.close();
}
logAllRequests();
3.2 修改请求:在发送前篡改数据
你可以在请求发送到服务器之前修改它的任何部分:
typescript
运行
// 修改请求头
await page.route('**/api/**', async (route) => {
const headers = { ...route.request().headers() };
// 添加认证令牌
headers['Authorization'] = 'Bearer your-token-here';
// 移除追踪头
delete headers['X-Tracking-Id'];
// 修改User-Agent
headers['User-Agent'] = 'Custom-Browser/1.0';
await route.continue({ headers });
});
// 修改POST请求体
await page.route('**/api/login', async (route) => {
if (route.request().method() === 'POST') {
const postData = JSON.parse(route.request().postData() || '{}');
// 修改用户名
postData.username = 'modified-user';
// 添加额外参数
postData.extraField = 'injected-by-playwright';
await route.continue({
postData: JSON.stringify(postData)
});
} else {
await route.continue();
}
});
3.3 修改响应:在应用收到前篡改数据
这是 Playwright 最强大的功能之一。你可以先获取真实的 API 响应,修改其中的部分内容,然后再返回给应用:
typescript
运行
// 修改API响应数据
await page.route('**/api/products', async (route) => {
// 先获取真实响应
const response = await route.fetch();
// 解析JSON数据
const data = await response.json();
// 修改数据:将所有商品价格打五折
data.products = data.products.map((product: any) => ({
...product,
price: product.price * 0.5,
discounted: true
}));
// 添加一个新商品
data.products.push({
id: 999,
name: 'Playwright专属商品',
price: 0.99,
discounted: true
});
// 返回修改后的响应
await route.fulfill({
response, // 保留原始响应的状态码和头信息
json: data // 替换响应体
});
});
// 修改HTML页面内容
await page.route('**/index.html', async (route) => {
const response = await route.fetch();
let body = await response.text();
// 注入自定义JavaScript
body = body.replace(
'</body>',
'<script>alert("页面已被Playwright修改!");</script></body>'
);
await route.fulfill({
response,
body
});
});
3.4 模拟响应:完全替代真实 API
当后端 API 尚未开发完成或不稳定时,你可以直接返回模拟数据:
typescript
运行
// 模拟成功响应
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
])
});
});
// 模拟错误响应
await page.route('**/api/payment', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({
error: '服务器内部错误',
code: 'INTERNAL_SERVER_ERROR'
})
});
});
3.5 中止请求:阻止不必要的资源加载
这对于加速测试和爬虫非常有用,可以阻止图片、广告和统计代码的加载:
typescript
运行
// 阻止所有图片加载
await page.route('**/*.{png,jpg,jpeg,gif,svg}', (route) => {
route.abort();
});
// 阻止特定域名的请求
await page.route('**/*.google-analytics.com/**', (route) => {
route.abort();
});
// 根据资源类型阻止
await page.route('**/*', (route) => {
const resourceType = route.request().resourceType();
if (['image', 'stylesheet', 'font'].includes(resourceType)) {
route.abort();
} else {
route.continue();
}
});
四、高级应用场景:超越基础抓包
4.1 GraphQL 请求的精准拦截与修改
GraphQL 请求通常都发送到同一个端点,传统的 URL 匹配方式无法区分不同的操作。Playwright 可以根据请求体中的operationName进行精准拦截:
typescript
运行
// 拦截特定的GraphQL操作
await page.route('**/graphql', async (route) => {
const request = route.request();
const postData = JSON.parse(request.postData() || '{}');
// 根据操作名称区分处理
switch (postData.operationName) {
case 'GetUserProfile':
// 修改用户查询响应
const response = await route.fetch();
const data = await response.json();
data.data.user.name = 'Modified Name';
data.data.user.email = 'modified@example.com';
await route.fulfill({ response, json: data });
break;
case 'CreateOrder':
// 模拟订单创建失败
await route.fulfill({
status: 400,
contentType: 'application/json',
body: JSON.stringify({
errors: [{ message: '库存不足' }]
})
});
break;
default:
// 其他请求正常发送
await route.continue();
}
});
4.2 WebSocket 通信的完全控制
Playwright 不仅支持 HTTP 请求,还能拦截和修改 WebSocket 通信:
typescript
运行
// 监听WebSocket连接
page.on('websocket', (ws) => {
console.log(`WebSocket已连接: ${ws.url()}`);
// 监听发送的消息
ws.on('framesent', (event) => {
console.log(`发送: ${event.payload}`);
});
// 监听接收的消息
ws.on('framereceived', (event) => {
console.log(`接收: ${event.payload}`);
});
// 监听关闭事件
ws.on('close', () => {
console.log('WebSocket已关闭');
});
});
// 你甚至可以发送自定义消息到WebSocket服务器
// 或者模拟服务器发送消息到客户端
4.3 模拟网络条件:测试弱网和离线场景
Playwright 可以模拟各种网络条件,包括 3G、4G、离线等:
typescript
运行
// 模拟3G网络
await page.context().setOffline(false);
await page.context().setNetworkConditions({
offline: false,
downloadThroughput: 750 * 1024 / 8, // 750 Kbps
uploadThroughput: 250 * 1024 / 8, // 250 Kbps
latency: 150 // 150ms延迟
});
// 模拟离线状态
await page.context().setOffline(true);
// 恢复正常网络
await page.context().setNetworkConditions({
offline: false,
downloadThroughput: -1,
uploadThroughput: -1,
latency: 0
});
4.4 动态路由:根据条件决定如何处理请求
你可以在路由处理函数中实现复杂的条件逻辑:
typescript
运行
// 动态决定是否使用Mock数据
let useMockData = true;
await page.route('**/api/data', async (route) => {
if (useMockData) {
// 返回Mock数据
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: 'mock-data' })
});
} else {
// 使用真实API
await route.continue();
}
});
// 稍后可以切换模式
useMockData = false;
4.5 反爬虫绕过:修改请求指纹
许多网站使用请求指纹来检测爬虫。通过 Playwright 的网络拦截,你可以修改这些指纹:
typescript
运行
// 模拟真实浏览器的请求头
await page.route('**/*', async (route) => {
const headers = { ...route.request().headers() };
// 移除Playwright特有的头信息
delete headers['X-Playwright'];
// 添加真实浏览器的头信息
headers['Accept-Language'] = 'zh-CN,zh;q=0.9,en;q=0.8';
headers['Accept-Encoding'] = 'gzip, deflate, br';
headers['Sec-Fetch-Dest'] = 'document';
headers['Sec-Fetch-Mode'] = 'navigate';
headers['Sec-Fetch-Site'] = 'none';
headers['Sec-Fetch-User'] = '?1';
headers['Upgrade-Insecure-Requests'] = '1';
await route.continue({ headers });
});
五、完整实战案例:电商网站价格测试
让我们通过一个完整的案例来展示 Playwright 网络拦截的强大功能。假设我们需要测试一个电商网站的价格显示和折扣计算功能:
typescript
运行
import { test, expect } from '@playwright/test';
test.describe('电商价格测试', () => {
test.beforeEach(async ({ page }) => {
// 拦截商品列表API
await page.route('**/api/products', async (route) => {
const response = await route.fetch();
const data = await response.json();
// 修改商品价格:创建各种测试场景
data.products = [
// 正常价格商品
{ id: 1, name: '普通商品', price: 100, originalPrice: 100 },
// 打折商品
{ id: 2, name: '打折商品', price: 80, originalPrice: 100 },
// 免费商品
{ id: 3, name: '免费商品', price: 0, originalPrice: 50 },
// 高价商品
{ id: 4, name: '高价商品', price: 99999, originalPrice: 99999 },
// 负数价格(测试错误处理)
{ id: 5, name: '异常商品', price: -10, originalPrice: 100 }
];
await route.fulfill({ response, json: data });
});
// 拦截购物车API
await page.route('**/api/cart', async (route) => {
if (route.request().method() === 'POST') {
const postData = JSON.parse(route.request().postData() || '{}');
// 模拟添加商品到购物车成功
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
cart: {
items: [postData],
total: postData.price,
discount: 0
}
})
});
} else {
await route.continue();
}
});
});
test('应该正确显示各种价格', async ({ page }) => {
await page.goto('/products');
// 验证普通商品价格
await expect(page.locator('[data-testid="product-1"] .price')).toHaveText('¥100.00');
// 验证打折商品显示原价和现价
await expect(page.locator('[data-testid="product-2"] .original-price')).toHaveText('¥100.00');
await expect(page.locator('[data-testid="product-2"] .current-price')).toHaveText('¥80.00');
// 验证免费商品显示"免费"
await expect(page.locator('[data-testid="product-3"] .price')).toHaveText('免费');
// 验证高价商品正确格式化
await expect(page.locator('[data-testid="product-4"] .price')).toHaveText('¥99,999.00');
// 验证异常价格显示错误提示
await expect(page.locator('[data-testid="product-5"] .price')).toHaveText('价格异常');
});
test('应该正确计算购物车总价', async ({ page }) => {
await page.goto('/products');
// 添加普通商品到购物车
await page.click('[data-testid="product-1"] .add-to-cart');
// 验证购物车总价
await expect(page.locator('[data-testid="cart-total"]')).toHaveText('¥100.00');
// 添加打折商品到购物车
await page.click('[data-testid="product-2"] .add-to-cart');
// 验证购物车总价更新
await expect(page.locator('[data-testid="cart-total"]')).toHaveText('¥180.00');
});
});
六、最佳实践与常见陷阱
6.1 最佳实践
- 尽早注册路由:在调用
page.goto()或触发任何可能产生网络请求的操作之前注册路由 - 使用精确的 URL 模式:避免使用
**/*拦截所有请求,这会影响性能 - 保持路由处理函数简洁:复杂的逻辑应该提取到单独的函数中
- 正确处理异步操作:确保所有异步操作都使用
await - 使用
route.fetch()获取原始响应:这样可以保留原始的状态码、头信息和 cookie - 在测试结束后清理路由:使用
page.unroute()移除不再需要的路由 - 分层测试策略:大多数测试使用 Mock 数据提高速度,保留少量集成测试使用真实 API
6.2 常见陷阱
- 忘记调用
route.continue()或route.fulfill():这会导致请求挂起,页面永远加载不完 - 修改响应时丢失 Content-Type 头:确保在
route.fulfill()中设置正确的contentType - 路由处理函数中的错误未被捕获:使用 try-catch 块处理可能的异常
- 多个路由匹配同一个请求:Playwright 会按照注册顺序调用第一个匹配的路由
- 在路由处理函数中进行耗时操作:这会影响页面加载速度
- 模拟响应时忽略 CORS 头:如果跨域请求需要特定的 CORS 头,确保在模拟响应中包含它们
七、总结
Playwright 的网络拦截功能远不止是一个简单的抓包工具。它提供了对浏览器网络层的完全控制,让你能够:
- 在请求发出前修改任何部分
- 在响应到达应用前篡改数据
- 完全模拟 API 响应,无需依赖后端
- 模拟各种网络条件和异常场景
- 拦截和修改 WebSocket 通信
- 与 UI 自动化无缝集成
这些能力使得 Playwright 成为现代 Web 应用开发和测试的必备工具。无论是前端开发、自动化测试还是网络爬虫,Playwright 的网络拦截功能都能帮助你解决传统抓包工具无法解决的问题。
随着 Web 应用变得越来越复杂,对网络控制的需求也越来越高。掌握 Playwright 的网络拦截技术,将让你在 Web 开发和测试中如虎添翼,能够更高效地构建和维护高质量的 Web 应用。
更多推荐




所有评论(0)