如何在Playwright中拦截非导航类HTTP请求?
问题:Playwright中仅页面导航请求被拦截,如何拦截所有请求并统一添加Test ID?
你写的测试代码里,只有最后一行page.goto的请求被context.route拦截,前面的几种请求都没触发拦截逻辑:
import {test} from "@playwright/test"; test('experimental test', async ({ page, context }) => { await context.route('**/*', (route) => { console.log('Intercepted:', route.request().url()); route.continue(); }); await fetch('https://example.com'); // 未被拦截 await context.request.get('https://example.com'); // 未被拦截 await context.request.fetch('https://example.com'); // 未被拦截 await page.request.get('https://example.com'); // 未被拦截 await page.goto('https://example.com'); // 仅这个被拦截 });
你的核心需求是:给测试中所有请求(包括初始化用的fetch请求)自动添加唯一的Test ID header,让每个测试能使用独立的数据库实例和种子数据,示例的拦截逻辑如下:
base.beforeEach(async ({context}, testInfo) => { await context.route('**/*', async route => { const headers = route.request().headers(); headers['Playwright-Test-Id'] = testInfo.project.name + '-' + testInfo.titlePath.join('-'); await route.continue({ headers }); }); })
解决方案
1. 先搞懂不同请求的运行环境
Playwright的context.route只能拦截浏览器页面上下文中的请求(比如page.goto、页面内的fetch/XHR),而你代码里的其他请求分属不同环境:
await fetch(...):运行在测试脚本的Node.js环境,和浏览器上下文完全无关context.request/page.request:属于Playwright的API请求,默认不被context.route拦截
2. 拦截Playwright API请求(context.request/page.request)
用context.routeFromAPI()方法专门处理这类API层面的请求,和context.route配合就能覆盖页面+API请求:
base.beforeEach(async ({context}, testInfo) => { const testId = `${testInfo.project.name}-${testInfo.titlePath.join('-')}`; // 拦截浏览器页面上下文的请求 await context.route('**/*', async route => { const headers = {...route.request().headers(), 'Playwright-Test-Id': testId}; await route.continue({ headers }); }); // 拦截context.request/page.request发起的API请求 await context.routeFromAPI('**/*', async route => { const headers = {...route.request().headers(), 'Playwright-Test-Id': testId}; await route.continue({ headers }); }); })
3. 处理Node.js环境的fetch请求
如果必须用Node的fetch发初始化请求,需要在Node层重写fetch方法,统一添加header:
base.beforeEach(async ({context}, testInfo) => { const testId = `${testInfo.project.name}-${testInfo.titlePath.join('-')}`; // 保存原生fetch方法 const originalFetch = global.fetch; // 重写全局fetch,自动注入Test ID global.fetch = async (url, options = {}) => { options.headers = {...options.headers, 'Playwright-Test-Id': testId}; return originalFetch(url, options); }; // 测试结束后恢复原生fetch,避免影响其他测试 testInfo.attachments.push({ name: 'restore-fetch', body: '', contentType: 'text/plain', onDispose: () => { global.fetch = originalFetch; } }); // 配合前面的页面请求和API请求拦截逻辑 await context.route('**/*', async route => { const headers = {...route.request().headers(), 'Playwright-Test-Id': testId}; await route.continue({ headers }); }); await context.routeFromAPI('**/*', async route => { const headers = {...route.request().headers(), 'Playwright-Test-Id': testId}; await route.continue({ headers }); }); })
4. 更简洁的替代方案(推荐)
如果不需要用Node的fetch,可以把所有初始化请求都改用context.request发起,这样只需要routeFromAPI+route就能覆盖所有请求,不用处理Node层的fetch重写,代码更清爽。
内容的提问来源于stack exchange,提问作者kmc
相关产品推荐
相关产品推荐

