发布时间:2026/6/14 10:57:47
在 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, scriptalert(页面已被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: aliceexample.com }, { id: 2, name: Bob, email: bobexample.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 modifiedexample.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;q0.9,en;q0.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-testidproduct-1] .price)).toHaveText(¥100.00); // 验证打折商品显示原价和现价 await expect(page.locator([data-testidproduct-2] .original-price)).toHaveText(¥100.00); await expect(page.locator([data-testidproduct-2] .current-price)).toHaveText(¥80.00); // 验证免费商品显示免费 await expect(page.locator([data-testidproduct-3] .price)).toHaveText(免费); // 验证高价商品正确格式化 await expect(page.locator([data-testidproduct-4] .price)).toHaveText(¥99,999.00); // 验证异常价格显示错误提示 await expect(page.locator([data-testidproduct-5] .price)).toHaveText(价格异常); }); test(应该正确计算购物车总价, async ({ page }) { await page.goto(/products); // 添加普通商品到购物车 await page.click([data-testidproduct-1] .add-to-cart); // 验证购物车总价 await expect(page.locator([data-testidcart-total])).toHaveText(¥100.00); // 添加打折商品到购物车 await page.click([data-testidproduct-2] .add-to-cart); // 验证购物车总价更新 await expect(page.locator([data-testidcart-total])).toHaveText(¥180.00); }); });六、最佳实践与常见陷阱6.1 最佳实践尽早注册路由在调用page.goto()或触发任何可能产生网络请求的操作之前注册路由使用精确的 URL 模式避免使用**/*拦截所有请求这会影响性能保持路由处理函数简洁复杂的逻辑应该提取到单独的函数中正确处理异步操作确保所有异步操作都使用await使用route.fetch()获取原始响应这样可以保留原始的状态码、头信息和 cookie在测试结束后清理路由使用page.unroute()移除不再需要的路由分层测试策略大多数测试使用 Mock 数据提高速度保留少量集成测试使用真实 API6.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 应用。