突破测试瓶颈:Playwright网络拦截与Mock实战指南
你是否还在为API依赖不稳定导致测试失败而烦恼?是否因第三方服务限制无法模拟异常场景?本文将带你掌握Playwright的网络请求拦截技术,从基础拦截到高级Mock策略,让前端测试彻底摆脱外部依赖,实现毫秒级响应和100%可重复的测试环境。
网络拦截核心原理与基础操作
Playwright的网络拦截功能基于DevTools协议实现,允许在浏览器级别拦截、修改和模拟所有HTTP(S)请求。与传统测试工具相比,其优势在于:支持所有现代浏览器、可拦截WebSockets、API设计直观且支持多语言绑定。
基础拦截三要素
- 路由匹配:通过URL模式精确定位需要拦截的请求
- 拦截处理:决定如何响应拦截到的请求(模拟/修改/放行)
- 作用域控制:可针对页面、浏览器上下文或全局生效
快速上手:拦截并中止图片请求
// 阻止所有图片加载以提高测试速度
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
await page.goto('https://example.com');
官方API文档:docs/src/network.md
五种实战拦截策略
1. 完全模拟响应(无后端测试)
适用于:前端开发早期、API尚未就绪、需要隔离前端测试场景。
test("模拟用户登录成功响应", async ({ page }) => {
// 拦截登录API请求
await page.route('**/api/login', async route => {
// 返回模拟的成功响应
await route.fulfill({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: 'mock-token-123',
user: { id: 1, name: '测试用户' }
})
});
});
await page.goto('/login');
await page.fill('input[name="username"]', 'test');
await page.fill('input[name="password"]', 'test');
await page.click('button[type="submit"]');
// 验证登录成功后的UI状态
await expect(page.locator('.user-name')).toContainText('测试用户');
});
模拟响应示例:docs/src/mock.md
2. 修改真实响应(部分Mock)
当需要真实API的大部分数据,但需调整部分内容时使用,例如模拟特定用户数据或边界情况。
test("修改商品列表价格", async ({ page }) => {
await page.route('**/api/products', async route => {
// 先获取真实API响应
const response = await route.fetch();
const products = await response.json();
// 修改价格数据
const discountedProducts = products.map(product => ({
...product,
price: product.price * 0.8 // 打八折
}));
// 返回修改后的响应
await route.fulfill({
response, // 保留原始响应的状态码和 headers
body: JSON.stringify(discountedProducts)
});
});
await page.goto('/products');
// 验证价格已更新
await expect(page.locator('.product-price').first()).toContainText('¥80');
});
3. HAR文件录制与回放
HAR(HTTP Archive)文件可录制一次网络请求,之后重复使用,特别适合:
- 测试环境不稳定的API
- 需要频繁重复相同请求序列
- 离线测试场景
录制HAR文件
test('录制产品列表API请求', async ({ page }) => {
// 录制API请求到HAR文件
await page.routeFromHAR('./hars/products.har', {
url: '**/api/products',
update: true // 首次录制设为true,后续回放设为false
});
await page.goto('/products');
// 执行操作以触发API请求...
});
修改HAR文件
录制后可直接编辑HAR文件修改响应内容:
// hars/products.har 片段
[
{
"name": "无线耳机",
"price": 999,
"inStock": true
},
{
"name": "测试专用商品", // 添加自定义测试数据
"price": 0,
"inStock": true
}
]
使用HAR文件回放
test('使用HAR文件测试商品展示', async ({ page }) => {
// 使用录制的HAR文件响应请求
await page.routeFromHAR('./hars/products.har', {
url: '**/api/products',
update: false // 禁用更新,使用HAR文件数据
});
await page.goto('/products');
// 验证HAR文件中的测试商品是否显示
await expect(page.getByText('测试专用商品')).toBeVisible();
});
HAR录制完整指南:docs/src/mock.md
4. 请求监控与断言
无需修改请求,仅记录网络交互并验证请求是否符合预期:
test("验证搜索请求参数", async ({ page }) => {
// 监听所有API请求
const searchRequests = [];
page.on('request', request => {
if (request.url().includes('/api/search')) {
searchRequests.push(request);
}
});
await page.goto('/search');
await page.fill('input[name="query"]', 'playwright');
await page.click('button[type="submit"]');
// 断言请求数量
expect(searchRequests.length).toBe(1);
// 断言请求参数
const searchRequest = searchRequests[0];
const queryParams = new URLSearchParams(searchRequest.url().split('?')[1]);
expect(queryParams.get('q')).toBe('playwright');
expect(queryParams.get('page')).toBe('1');
expect(queryParams.get('limit')).toBe('20');
});
5. WebSocket模拟
现代应用广泛使用WebSocket进行实时通信,Playwright提供完整的WebSocket拦截能力:
test("模拟实时通知", async ({ page }) => {
// 拦截WebSocket连接
await page.routeWebSocket('wss://example.com/notifications', ws => {
console.log('WebSocket连接已拦截');
// 监听客户端发送的消息
ws.onMessage(message => {
console.log('客户端发送:', message);
// 回应客户端
if (message.includes('subscribe')) {
ws.send(JSON.stringify({
type: 'notification',
content: '这是一条模拟的实时通知'
}));
}
});
});
await page.goto('/dashboard');
// 验证是否收到模拟通知
await expect(page.locator('.notification')).toContainText('模拟的实时通知');
});
WebSocket模拟详情:docs/src/mock.md
高级技巧与最佳实践
按环境切换Mock策略
test.beforeEach(async ({ page, context }, testInfo) => {
// 根据测试环境决定是否使用Mock
if (testInfo.project.name === 'mock') {
// 注册所有必要的Mock路由
await registerAllMockRoutes(context);
} else if (testInfo.project.name === 'staging') {
// 仅修改部分请求
await registerPartialMocks(context);
}
// 生产环境不使用任何Mock
});
处理动态URL与鉴权
使用正则表达式匹配动态URL,并添加认证头:
// 匹配版本号动态变化的API
await page.route(/\/api\/v\d+\/user/, async route => {
// 添加认证头
const headers = {
...route.request().headers(),
'Authorization': 'Bearer ' + process.env.TEST_TOKEN
};
await route.continue({ headers });
});
常见问题解决方案
| 问题场景 | 解决方案 | 参考文档 |
|---|---|---|
| 跨域请求拦截失败 | 在浏览器上下文设置ignoreHTTPSErrors: true | docs/src/network.md |
| ServiceWorker干扰 | 禁用ServiceWorker: { serviceWorkers: 'block' } | docs/src/network.md |
| 复杂请求匹配 | 使用自定义谓词函数进行匹配 | docs/src/network.md |
性能优化建议
- 作用域最小化:优先使用page.route而非browserContext.route
- 精确URL匹配:避免使用过于宽泛的匹配模式
- 并行处理:复杂响应处理使用Promise.all提高效率
- 复用Mock数据:将常用Mock数据抽象为可复用模块
总结与扩展应用
通过Playwright的网络拦截功能,我们不仅解决了测试环境依赖问题,还获得了:
- 故障注入:模拟API超时、500错误等异常场景
- 性能测试:模拟慢速网络和大延迟环境
- 安全测试:修改请求参数测试接口安全性
- 用户行为分析:记录用户会话中的所有网络交互
官方高级网络指南:docs/src/network.md
掌握这些技术后,你可以构建更健壮、更快速且不受外部环境影响的测试套件。尝试将这些方法应用到你的CI/CD流程中,体验真正的端到端测试自动化!
下一步行动:
- 尝试用HAR文件录制并回放你的核心用户流程
- 为一个API实现完整的异常场景测试(404、500、超时)
- 使用WebSocket模拟构建实时功能测试
祝你测试愉快!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



