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();
});
Cookie 认证 #
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