Playwright API 测试 #

API 测试概述 #

Playwright 提供内置的 API 测试能力,可以独立于浏览器测试 API,也可以与 UI 测试结合使用。

API 测试优势 #

text
┌─────────────────────────────────────────────────────────────┐
│                    API 测试优势                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ✅ 快速执行 - 无需启动浏览器                                 │
│  ✅ 独立测试 - 可以单独测试后端 API                           │
│  ✅ 数据准备 - 为 UI 测试准备数据                             │
│  ✅ 完整覆盖 - 测试 UI 无法覆盖的 API                         │
│  ✅ 集成测试 - 测试 API 与数据库集成                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

基本请求 #

GET 请求 #

typescript
import { test, expect } from '@playwright/test';

test('GET 请求示例', async ({ request }) => {
  // 基本 GET 请求
  const response = await request.get('/api/users');
  
  // 验证状态码
  expect(response.status()).toBe(200);
  
  // 获取响应数据
  const users = await response.json();
  expect(users.length).toBeGreaterThan(0);
});

test('带参数的 GET 请求', async ({ request }) => {
  // 查询参数
  const response = await request.get('/api/users', {
    params: {
      page: 1,
      limit: 10,
      sort: 'name',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

test('带请求头的 GET 请求', async ({ request }) => {
  const response = await request.get('/api/users', {
    headers: {
      'Authorization': 'Bearer token123',
      'Accept': 'application/json',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

POST 请求 #

typescript
test('POST 请求示例', async ({ request }) => {
  // 创建用户
  const response = await request.post('/api/users', {
    data: {
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user',
    },
  });
  
  expect(response.status()).toBe(201);
  
  const user = await response.json();
  expect(user.name).toBe('John Doe');
  expect(user.id).toBeDefined();
});

test('带请求头的 POST 请求', async ({ request }) => {
  const response = await request.post('/api/users', {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token123',
    },
    data: {
      name: 'John Doe',
      email: 'john@example.com',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

test('表单数据 POST', async ({ request }) => {
  const response = await request.post('/api/login', {
    form: {
      username: 'john',
      password: 'password123',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

test('文件上传', async ({ request }) => {
  const response = await request.post('/api/upload', {
    multipart: {
      file: {
        name: 'test.txt',
        mimeType: 'text/plain',
        buffer: Buffer.from('Hello World'),
      },
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

PUT 请求 #

typescript
test('PUT 请求示例', async ({ request }) => {
  const response = await request.put('/api/users/1', {
    data: {
      name: 'John Updated',
      email: 'john.updated@example.com',
    },
  });
  
  expect(response.ok()).toBeTruthy();
  
  const user = await response.json();
  expect(user.name).toBe('John Updated');
});

PATCH 请求 #

typescript
test('PATCH 请求示例', async ({ request }) => {
  const response = await request.patch('/api/users/1', {
    data: {
      name: 'John Patched',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

DELETE 请求 #

typescript
test('DELETE 请求示例', async ({ request }) => {
  // 先创建用户
  const createResponse = await request.post('/api/users', {
    data: { name: 'To Delete', email: 'delete@example.com' },
  });
  const user = await createResponse.json();
  
  // 删除用户
  const deleteResponse = await request.delete(`/api/users/${user.id}`);
  expect(deleteResponse.status()).toBe(204);
  
  // 验证已删除
  const getResponse = await request.get(`/api/users/${user.id}`);
  expect(getResponse.status()).toBe(404);
});

响应验证 #

状态码验证 #

typescript
test('状态码验证', async ({ request }) => {
  // 成功响应
  const okResponse = await request.get('/api/users');
  expect(okResponse.ok()).toBeTruthy();
  expect(okResponse.status()).toBe(200);
  
  // 创建成功
  const createdResponse = await request.post('/api/users', {
    data: { name: 'Test' },
  });
  expect(createdResponse.status()).toBe(201);
  
  // 未找到
  const notFoundResponse = await request.get('/api/users/999999');
  expect(notFoundResponse.status()).toBe(404);
  
  // 错误请求
  const badResponse = await request.post('/api/users', {
    data: {}, // 缺少必需字段
  });
  expect(badResponse.status()).toBe(400);
});

响应头验证 #

typescript
test('响应头验证', async ({ request }) => {
  const response = await request.get('/api/users');
  
  // 获取单个响应头
  const contentType = response.headers()['content-type'];
  expect(contentType).toContain('application/json');
  
  // 获取所有响应头
  const headers = response.headers();
  console.log(headers);
});

响应体验证 #

typescript
test('响应体验证', async ({ request }) => {
  const response = await request.get('/api/users/1');
  
  // JSON 响应
  const user = await response.json();
  expect(user).toHaveProperty('id');
  expect(user).toHaveProperty('name');
  expect(user).toHaveProperty('email');
  
  // 文本响应
  const text = await response.text();
  expect(text).toContain('expected content');
  
  // Buffer 响应
  const buffer = await response.body();
  expect(buffer.length).toBeGreaterThan(0);
});

复杂响应验证 #

typescript
test('复杂响应验证', async ({ request }) => {
  const response = await request.get('/api/users');
  const users = await response.json();
  
  // 验证数组
  expect(Array.isArray(users)).toBeTruthy();
  expect(users.length).toBeGreaterThan(0);
  
  // 验证每个元素
  for (const user of users) {
    expect(user).toHaveProperty('id');
    expect(user).toHaveProperty('name');
    expect(user.email).toMatch(/@/);
  }
  
  // 验证特定元素
  const firstUser = users[0];
  expect(firstUser).toEqual({
    id: expect.any(Number),
    name: expect.any(String),
    email: expect.stringContaining('@'),
  });
});

认证处理 #

Bearer Token #

typescript
test('Bearer Token 认证', async ({ request }) => {
  // 获取 token
  const loginResponse = await request.post('/api/login', {
    data: {
      username: 'admin',
      password: 'password',
    },
  });
  const { token } = await loginResponse.json();
  
  // 使用 token
  const response = await request.get('/api/protected', {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

Basic 认证 #

typescript
test('Basic 认证', async ({ request }) => {
  const response = await request.get('/api/protected', {
    headers: {
      'Authorization': `Basic ${Buffer.from('user:password').toString('base64')}`,
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

API Key 认证 #

typescript
test('API Key 认证', async ({ request }) => {
  const response = await request.get('/api/data', {
    headers: {
      'X-API-Key': 'your-api-key',
    },
  });
  
  expect(response.ok()).toBeTruthy();
});
typescript
test('Cookie 认证', async ({ request }) => {
  // 登录获取 cookie
  const loginResponse = await request.post('/api/login', {
    data: { username: 'user', password: 'password' },
  });
  
  // 从响应头获取 cookie
  const cookies = loginResponse.headers()['set-cookie'];
  
  // 使用 cookie
  const response = await request.get('/api/protected', {
    headers: {
      'Cookie': cookies,
    },
  });
  
  expect(response.ok()).toBeTruthy();
});

数据驱动测试 #

参数化测试 #

typescript
const testCases = [
  { name: 'John', email: 'john@example.com', expectedStatus: 201 },
  { name: 'Jane', email: 'jane@example.com', expectedStatus: 201 },
  { name: '', email: 'invalid@example.com', expectedStatus: 400 },
  { name: 'No Email', email: '', expectedStatus: 400 },
];

for (const testCase of testCases) {
  test(`创建用户: ${testCase.name || '空名称'}, ${testCase.email || '空邮箱'}`, async ({ request }) => {
    const response = await request.post('/api/users', {
      data: {
        name: testCase.name,
        email: testCase.email,
      },
    });
    
    expect(response.status()).toBe(testCase.expectedStatus);
  });
}

从文件加载数据 #

typescript
import { test, expect } from '@playwright/test';
import fs from 'fs/promises';

test.describe('数据驱动测试', () => {
  let testData: any[];
  
  test.beforeAll(async () => {
    const content = await fs.readFile('test-data/users.json', 'utf-8');
    testData = JSON.parse(content);
  });
  
  for (const data of testData) {
    test(`测试用户: ${data.name}`, async ({ request }) => {
      const response = await request.post('/api/users', {
        data: data,
      });
      
      expect(response.status()).toBe(data.expectedStatus);
    });
  }
});

API 测试与 UI 测试结合 #

API 准备数据 #

typescript
test('API 准备数据后 UI 测试', async ({ page, request }) => {
  // 通过 API 创建用户
  const response = await request.post('/api/users', {
    data: {
      name: 'Test User',
      email: 'test@example.com',
    },
  });
  const user = await response.json();
  
  // UI 测试
  await page.goto(`/users/${user.id}`);
  await expect(page.locator('.user-name')).toHaveText('Test User');
});

API 验证 UI 操作 #

typescript
test('UI 操作后 API 验证', async ({ page, request }) => {
  // UI 操作
  await page.goto('/users/new');
  await page.fill('#name', 'New User');
  await page.fill('#email', 'new@example.com');
  await page.click('button[type="submit"]');
  
  // API 验证
  const response = await request.get('/api/users');
  const users = await response.json();
  
  const newUser = users.find(u => u.email === 'new@example.com');
  expect(newUser).toBeDefined();
  expect(newUser.name).toBe('New User');
});

测试夹具 #

API 测试夹具 #

typescript
// fixtures/api.ts
import { test as base, APIRequestContext } from '@playwright/test';

type ApiFixtures = {
  apiClient: ApiClient;
};

class ApiClient {
  constructor(private request: APIRequestContext) {}
  
  async createUser(data: any) {
    const response = await this.request.post('/api/users', { data });
    return response.json();
  }
  
  async getUser(id: number) {
    const response = await this.request.get(`/api/users/${id}`);
    return response.json();
  }
  
  async deleteUser(id: number) {
    await this.request.delete(`/api/users/${id}`);
  }
}

export const test = base.extend<ApiFixtures>({
  apiClient: async ({ request }, use) => {
    const client = new ApiClient(request);
    await use(client);
  },
});

使用 API 夹具 #

typescript
import { test, expect } from '../fixtures/api';

test('使用 API 夹具', async ({ apiClient }) => {
  // 创建用户
  const user = await apiClient.createUser({
    name: 'Test User',
    email: 'test@example.com',
  });
  
  expect(user.id).toBeDefined();
  
  // 获取用户
  const fetchedUser = await apiClient.getUser(user.id);
  expect(fetchedUser.name).toBe('Test User');
});

API 测试最佳实践 #

1. 清理测试数据 #

typescript
test.afterEach(async ({ request }) => {
  // 清理创建的测试数据
  const response = await request.get('/api/users?email=test@example.com');
  const users = await response.json();
  
  for (const user of users) {
    await request.delete(`/api/users/${user.id}`);
  }
});

2. 使用环境变量 #

typescript
test('使用环境变量', async ({ request }) => {
  const baseUrl = process.env.API_BASE_URL || 'http://localhost:3000';
  
  const response = await request.get(`${baseUrl}/api/users`);
  expect(response.ok()).toBeTruthy();
});

3. 错误处理 #

typescript
test('错误处理', async ({ request }) => {
  try {
    const response = await request.get('/api/users/999999');
    
    if (!response.ok()) {
      const error = await response.json();
      console.error(`API 错误: ${error.message}`);
    }
    
    expect(response.status()).toBe(404);
  } catch (error) {
    console.error('请求失败:', error);
    throw error;
  }
});

4. 请求日志 #

typescript
test('请求日志', async ({ request }) => {
  const response = await request.get('/api/users', {
    headers: { 'X-Request-ID': 'test-123' },
  });
  
  console.log('请求 URL:', response.url());
  console.log('状态码:', response.status());
  console.log('响应时间:', response.headers()['x-response-time']);
});

下一步 #

现在你已经掌握了 API 测试,接下来学习 CI/CD 集成 了解如何将测试集成到持续集成流程!

最后更新:2026-03-28