现在位置: 首页 > Playwright 教程 > 正文

Playwright 网络拦截与 Mock

本章介绍如何使用 Playwright 拦截、修改和模拟网络请求,实现不依赖后端 API 的独立测试。


什么是网络拦截

网络拦截允许你在浏览器发起 HTTP 请求时进行干预。

你可以阻止请求、修改响应、返回模拟数据,让测试不依赖外部 API 或网络状态。


page.route() 拦截请求

page.route(url, handler) 用于拦截匹配指定 URL 模式的所有网络请求。

基本拦截

实例

test('拦截所有图片请求', async ({ page }) => {
  // 拦截所有 PNG 和 JPEG 图片
  await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

  // 导航到页面(图片不会被加载)
  await page.goto('https://www.runoob.com/');
  // 页面可能加载更快,因为没有下载图片
});

URL 匹配模式支持以下格式:

模式示例说明
完整 URL'https://api.example.com/users'精确匹配
Glob 通配符'**/api/**'** 匹配任意路径
正则表达式/\/api\/v\d\/users/灵活匹配
函数url => url.includes('/api/')编程逻辑匹配

route.abort() 阻止请求

实例

test('阻止第三方分析脚本', async ({ page }) => {
  // 阻止 Google Analytics 等分析脚本
  await page.route('**/*analytics*', route => route.abort());
  // 阻止 CSS 文件
  await page.route('**/*.css', route => route.abort());

  await page.goto('https://www.runoob.com/');
  // 页面以无样式形式加载,但不影响功能测试
});

route.fulfill() 模拟响应

route.fulfill() 是最强大的 Mock 方法,可以返回任意自定义响应。

Mock JSON 数据

实例

test('Mock 用户列表 API', async ({ page }) => {
  // 拦截用户列表 API,返回模拟数据
  await page.route('**/api/users', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      json: [
        { id: 1, name: 'runoob_user', email: 'user@runoob.com' },
        { id: 2, name: 'test_user', email: 'test@runoob.com' },
      ],
    });
  });

  await page.goto('https://www.runoob.com/users');
  // 页面会显示 Mock 数据而不是真实的 API 数据
});

Mock 错误响应

实例

test('测试 API 错误处理', async ({ page }) => {
  // 模拟 500 服务器错误
  await page.route('**/api/data', async route => {
    await route.fulfill({
      status: 500,
      contentType: 'application/json',
      json: { error: '服务器内部错误' },
    });
  });

  await page.goto('https://www.runoob.com/dashboard');
  // 验证错误提示是否正常显示
  await expect(page.getByText('服务器内部错误')).toBeVisible();
});

route.continue() 修改后继续

不是直接返回模拟数据,而是修改真实请求或响应后继续发送到服务器。

实例

test('修改请求头', async ({ page }) => {
  await page.route('**/api/**', async (route, request) => {
    // 修改请求头,添加自定义 Header
    const headers = {
      ...request.headers(),
      'X-Custom-Header': 'RUNOOB-TEST',
    };
    await route.continue({ headers });
  });

  await page.goto('https://www.runoob.com/');
});

test('修改 POST 数据', async ({ page }) => {
  await page.route('**/api/login', async (route, request) => {
    // 覆盖 POST 请求的 body
    const postData = JSON.parse(request.postData() || '{}');
    postData.mode = 'test';  // 添加测试标记
    await route.continue({
      postData: JSON.stringify(postData),
    });
  });
});

route.fetch() 真实请求

在 Mock 处理函数中,可以用 route.fetch() 实际发送请求并获取真实响应,然后修改。

实例

test('修改真实响应', async ({ page }) => {
  await page.route('**/api/products', async route => {
    // 先获取真实响应
    const response = await route.fetch();
    const body = await response.json();

    // 修改响应数据
    body.items[0].name = 'RUNOOB 修改后的商品名';

    // 返回修改后的数据
    await route.fulfill({
      status: response.status(),
      contentType: 'application/json',
      json: body,
    });
  });
});

Context 级拦截 vs Page 级拦截

方式作用范围适用场景
page.route()仅当前页面单个页面的网络 Mock
context.route()该 Context 下所有页面多个页面共享的 Mock 规则

实例

test.beforeEach(async ({ context }) => {
  // 在整个 Context 中拦截,所有页面生效
  await context.route('**/*.css', route => route.abort());
});

test('测试 1', async ({ page }) => { /* 无 CSS */ });
test('测试 2', async ({ page }) => { /* 无 CSS */ });

HAR 文件录制与回放

HAR(HTTP Archive)文件可以记录页面的所有网络请求,然后回放模拟。

实例

test('使用 HAR 文件 Mock', async ({ page }) => {
  // 从 HAR 文件加载网络记录并回放
  await page.routeFromHAR('tests/hars/api-responses.har');

  await page.goto('https://www.runoob.com/');
  // 所有匹配的请求将从 HAR 文件中获取响应
});
# 录制 HAR 文件
npx playwright open --save-har=tests/hars/api-responses.har https://www.runoob.com/

HAR 文件适合 Mock 大量或复杂的 API 响应。先录制一次,之后测试都从 HAR 回放,不依赖真实服务器。