chore: import zh skill playwright-skill

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:47 +08:00
commit 5d144ced31
77 changed files with 54629 additions and 0 deletions
+373
View File
@@ -0,0 +1,373 @@
# 请求拦截(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`,否则路由规则在整个会话中持续生效,可能会干扰后续操作。