Files
2026-07-13 21:36:47 +08:00

374 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 请求拦截(Request Mocking
> **适用场景**:在浏览器自动化过程中拦截、模拟、修改或阻断网络请求——API 桩代码、模拟错误、测试离线行为、移除追踪,或通过拦截大资源来加速页面加载。
> **前置条件**:请先阅读 [core-commands.md](core-commands.md) 了解基本 CLI 用法
## 快速参考
```bash
# 拦截所有图片
playwright-cli route "**/*.jpg" --status=404
# 以 JSON 响应体模拟 API
playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"}]' --content-type=application/json
# 添加自定义响应头
playwright-cli route "**/api/data" --body='{"ok":true}' --header="X-Custom: value"
# 移除请求头(例如测试时去掉认证信息)
playwright-cli route "**/*" --remove-header=cookie,authorization
# 列出当前活跃的路由规则
playwright-cli route-list
# 移除某条路由规则
playwright-cli unroute "**/*.jpg"
# 移除所有路由规则
playwright-cli unroute
```
## URL 匹配模式
playwright-cli 使用 glob 模式进行 URL 匹配:
| 模式 | 匹配规则 | 示例 URL |
| ------------------------------- | -------------------------------- | --------------------------------------------- |
| `**/api/users` | 匹配任意来源的精确路径 | `https://api.example.com/api/users` |
| `**/api/*/details` | 路径段中的通配符 | `https://api.example.com/api/123/details` |
| `**/*.{png,jpg,jpeg}` | 多个文件扩展名 | `https://cdn.example.com/hero.png` |
| `**/search?q=*` | 查询参数 | `https://example.com/search?q=test` |
| `https://api.example.com/**` | 匹配特定来源的所有请求 | `https://api.example.com/v2/users` |
| `**/*` | 匹配所有请求 | 所有请求 |
## CLI 路由命令
### 模拟状态码
```bash
# 对所有图片请求返回 404
playwright-cli route "**/*.jpg" --status=404
# 返回 503 Service Unavailable
playwright-cli route "**/api/health" --status=503
# 返回 401 Unauthorized
playwright-cli route "**/api/protected" --status=401
```
### 模拟 JSON 响应
```bash
# 简单的 JSON 响应体
playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]' --content-type=application/json
# 模拟单个资源
playwright-cli route "**/api/users/1" --body='{"id":1,"name":"Alice","email":"alice@example.com"}' --content-type=application/json
# 返回空数组(无结果场景)
playwright-cli route "**/api/search*" --body='[]' --content-type=application/json
```
### 模拟自定义响应头
```bash
# 测试时设置 CORS 头
playwright-cli route "**/api/**" --body='{"ok":true}' --header="Access-Control-Allow-Origin: *" --header="X-Request-Id: mock-123"
# 设置缓存头
playwright-cli route "**/static/**" --header="Cache-Control: max-age=3600"
```
### 移除请求头
从发出的请求中移除指定请求头——适用于测试未认证访问:
```bash
# 移除认证头
playwright-cli route "**/*" --remove-header=cookie,authorization
# 移除追踪头
playwright-cli route "**/*" --remove-header=x-tracking-id
```
### 管理活跃路由
```bash
# 查看当前拦截了哪些请求
playwright-cli route-list
# 移除某条特定路由
playwright-cli unroute "**/*.jpg"
# 清除所有路由
playwright-cli unroute
```
## 使用 run-code 实现高级模拟
如需条件响应、请求体检查、响应修改或定时延迟,可使用 `run-code` 调用完整的 Playwright 路由 API。
### 基于请求体的条件响应
```bash
playwright-cli run-code "async page => {
await page.route('**/api/login', route => {
const body = route.request().postDataJSON();
if (body.username === 'admin' && body.password === 'secret') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'mock-jwt-token', user: { role: 'admin' } })
});
} else {
route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'Invalid credentials' })
});
}
});
}"
```
### 基于 HTTP 方法的条件响应
```bash
playwright-cli run-code "async page => {
await page.route('**/api/users', route => {
const method = route.request().method();
switch (method) {
case 'GET':
route.fulfill({
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Alice' }])
});
break;
case 'POST':
route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: 2, name: 'New User' })
});
break;
case 'DELETE':
route.fulfill({ status: 204 });
break;
default:
route.continue();
}
});
}"
```
### 修改真实响应
让请求实际发出,然后在页面接收到响应之前对其进行修改:
```bash
playwright-cli run-code "async page => {
await page.route('**/api/user/profile', async route => {
const response = await route.fetch();
const json = await response.json();
// 覆盖特定字段
json.isPremium = true;
json.subscription = 'enterprise';
await route.fulfill({ response, json });
});
}"
```
### 为真实响应添加响应头
```bash
playwright-cli run-code "async page => {
await page.route('**/api/**', async route => {
const response = await route.fetch();
const headers = { ...response.headers(), 'x-mock': 'true' };
await route.fulfill({ response, headers });
});
}"
```
### 模拟网络故障
```bash
# 网络断开
playwright-cli run-code "async page => {
await page.route('**/api/offline', route => route.abort('internetdisconnected'));
}"
# 连接被拒绝
playwright-cli run-code "async page => {
await page.route('**/api/down', route => route.abort('connectionrefused'));
}"
# 超时
playwright-cli run-code "async page => {
await page.route('**/api/slow', route => route.abort('timedout'));
}"
# 连接重置
playwright-cli run-code "async page => {
await page.route('**/api/reset', route => route.abort('connectionreset'));
}"
```
可用的中止原因:`connectionrefused`, `timedout`, `connectionreset`, `internetdisconnected`, `blockedbyclient`, `failed`
### 模拟慢响应(延迟)
```bash
playwright-cli run-code "async page => {
await page.route('**/api/slow-endpoint', async route => {
await new Promise(resolve => setTimeout(resolve, 3000));
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ data: 'finally loaded' })
});
});
}"
```
### 从文件读取响应进行模拟
```bash
playwright-cli run-code "async page => {
const fs = require('fs');
await page.route('**/api/config', route => {
const body = fs.readFileSync('./fixtures/mock-config.json', 'utf8');
route.fulfill({
contentType: 'application/json',
body
});
});
}"
```
### 请求计数与验证
```bash
playwright-cli run-code "async page => {
let apiCallCount = 0;
await page.route('**/api/analytics', route => {
apiCallCount++;
route.continue();
});
// ... 执行页面操作 ...
// 之后检查计数:
return \`API was called \${apiCallCount} times\`;
}"
```
## 常见模式
### 拦截大资源以提升速度
```bash
# 拦截图片、字体和样式表
playwright-cli route "**/*.{png,jpg,jpeg,gif,svg,webp}" --status=404
playwright-cli route "**/*.{woff,woff2,ttf,eot}" --status=404
playwright-cli route "**/*.css" --status=404
```
### 拦截第三方脚本
```bash
# 拦截分析及追踪脚本
playwright-cli run-code "async page => {
await page.route('**/*', route => {
const url = route.request().url();
const blocked = [
'google-analytics.com',
'googletagmanager.com',
'facebook.net',
'hotjar.com',
'segment.io'
];
if (blocked.some(domain => url.includes(domain))) {
route.abort('blockedbyclient');
} else {
route.continue();
}
});
}"
```
### 模拟 GraphQL 请求
```bash
playwright-cli run-code "async page => {
await page.route('**/graphql', route => {
const { query } = route.request().postDataJSON();
if (query.includes('GetUser')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { user: { id: '1', name: 'Alice', email: 'alice@example.com' } }
})
});
} else if (query.includes('ListProducts')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { products: [{ id: '1', name: 'Widget', price: 9.99 }] }
})
});
} else {
route.continue();
}
});
}"
```
### 基于 HAR 文件的重放
录制真实网络流量,之后回放——非常适合确定性测试:
```bash
# 将所有网络流量录制到 HAR 文件
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
update: true,
url: '**/api/**'
});
await page.goto('https://example.com');
// 在页面上执行操作——所有匹配的请求都会被录制
}"
# 之后从 HAR 文件回放(无需真实网络)
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
url: '**/api/**'
});
await page.goto('https://example.com');
// API 响应来自 HAR 文件
}"
```
### 模拟 WebSocket 消息
```bash
playwright-cli run-code "async page => {
const ws = await page.waitForEvent('websocket');
ws.on('framereceived', event => {
console.log('WS received:', event.payload);
});
ws.on('framesent', event => {
console.log('WS sent:', event.payload);
});
}"
```
## 提示
- **顺序很重要**:路由按照添加的顺序依次评估。更具体的模式应放在全匹配模式之前添加。
- **`route.continue()`**:让请求继续发往真实服务器——在条件路由中用于"透传"场景。
- **`route.fetch()`**:发起真实请求并返回响应,以便你在兑现前检查或修改它。
- **性能**:拦截图片和第三方脚本可以大幅提升自动化过程中的页面加载速度。
- **清理**:使用完毕后务必执行 `unroute`,否则路由规则在整个会话中持续生效,可能会干扰后续操作。