用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 应用。
