name: qa-regression description: 使用可重用的测试技能自动化QA回归测试。创建登录流程、仪表板检查、用户创建和其他常见测试场景,确保一致运行。 license: MIT
QA回归测试
使用Playwright构建和运行自动化回归测试。每个测试都是可重用的技能,可以组合成完整的测试套件。
设置
npm init -y
npm install playwright @playwright/test
npx playwright install
测试结构
在tests/文件夹中创建测试:
tests/
├── auth/
│ ├── login.spec.ts
│ └── logout.spec.ts
├── dashboard/
│ └── load.spec.ts
├── users/
│ ├── create.spec.ts
│ └── delete.spec.ts
└── regression.spec.ts # 完整套件
常见测试技能
登录测试
// tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('登录流程', () => {
test('应使用有效凭据登录', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', process.env.TEST_EMAIL!);
await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD!);
await page.click('[data-testid="submit"]');
// 验证重定向到仪表板
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
});
test('应显示无效凭据错误', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'wrong@example.com');
await page.fill('[data-testid="password"]', 'wrongpassword');
await page.click('[data-testid="submit"]');
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
});
});
仪表板加载测试
// tests/dashboard/load.spec.ts
import { test, expect } from '@playwright/test';
import { login } from '../helpers/auth';
test.describe('仪表板', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});
test('应在3秒内加载仪表板', async ({ page }) => {
const start = Date.now();
await page.goto('/dashboard');
await page.waitForSelector('[data-testid="dashboard-content"]');
const loadTime = Date.now() - start;
expect(loadTime).toBeLessThan(3000);
});
test('应显示所有小部件', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.locator('[data-testid="stats-widget"]')).toBeVisible();
await expect(page.locator('[data-testid="chart-widget"]')).toBeVisible();
await expect(page.locator('[data-testid="activity-widget"]')).toBeVisible();
});
test('应在按钮点击时刷新数据', async ({ page }) => {
await page.goto('/dashboard');
const initialValue = await page.locator('[data-testid="last-updated"]').textContent();
await page.click('[data-testid="refresh-button"]');
await page.waitForTimeout(1000);
const newValue = await page.locator('[data-testid="last-updated"]').textContent();
expect(newValue).not.toBe(initialValue);
});
});
创建用户测试
// tests/users/create.spec.ts
import { test, expect } from '@playwright/test';
import { login } from '../helpers/auth';
import { generateTestUser, deleteTestUser } from '../helpers/users';
test.describe('用户创建', () => {
let testUser: { email: string; name: string };
test.beforeEach(async ({ page }) => {
await login(page);
testUser = generateTestUser();
});
test.afterEach(async () => {
// 清理
await deleteTestUser(testUser.email);
});
test('应成功创建新用户', async ({ page }) => {
await page.goto('/users/new');
await page.fill('[data-testid="user-name"]', testUser.name);
await page.fill('[data-testid="user-email"]', testUser.email);
await page.selectOption('[data-testid="user-role"]', 'member');
await page.click('[data-testid="create-user-btn"]');
// 验证成功
await expect(page.locator('[data-testid="success-toast"]')).toBeVisible();
await expect(page).toHaveURL(/users/);
// 验证用户出现在列表中
await expect(page.locator(`text=${testUser.email}`)).toBeVisible();
});
test('应验证必填字段', async ({ page }) => {
await page.goto('/users/new');
await page.click('[data-testid="create-user-btn"]');
await expect(page.locator('[data-testid="name-error"]')).toBeVisible();
await expect(page.locator('[data-testid="email-error"]')).toBeVisible();
});
});
共享助手
// tests/helpers/auth.ts
import { Page } from '@playwright/test';
export async function login(page: Page) {
await page.goto('/login');
await page.fill('[data-testid="email"]', process.env.TEST_EMAIL!);
await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD!);
await page.click('[data-testid="submit"]');
await page.waitForURL(/dashboard/);
}
export async function logout(page: Page) {
await page.click('[data-testid="user-menu"]');
await page.click('[data-testid="logout"]');
await page.waitForURL(/login/);
}
// tests/helpers/users.ts
export function generateTestUser() {
const id = Date.now();
return {
name: `测试用户 ${id}`,
email: `test-${id}@example.com`,
};
}
export async function deleteTestUser(email: string) {
// API调用来清理测试用户
await fetch(`${process.env.API_URL}/admin/users`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.ADMIN_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
}
完整回归套件
// tests/regression.spec.ts
import { test } from '@playwright/test';
// 导入所有测试套件
import './auth/login.spec';
import './auth/logout.spec';
import './dashboard/load.spec';
import './users/create.spec';
import './users/delete.spec';
test.describe('完整回归套件', () => {
// 测试按上述顺序运行
});
Playwright配置
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
运行测试
# 运行所有测试
npx playwright test
# 运行特定测试文件
npx playwright test tests/auth/login.spec.ts
# 在UI模式下运行测试
npx playwright test --ui
# 在headed模式下运行(显示浏览器)
npx playwright test --headed
# 生成报告
npx playwright show-report
CI集成
# .github/workflows/regression.yml
name: 回归测试
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * *' # 每天上午6点
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: 安装依赖
run: npm ci
- name: 安装Playwright
run: npx playwright install --with-deps
- name: 运行测试
run: npx playwright test
env:
BASE_URL: ${{ secrets.STAGING_URL }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
最佳实践
- 使用data-testid属性 - 比CSS选择器更稳定
- 清理测试数据 - 总是删除创建的内容
- 避免硬编码等待 - 使用
waitForSelector代替waitForTimeout - 并行运行 - 在CI上更快反馈
- 失败时截图 - 更容易调试
- 环境变量 - 永不提交凭据
快速命令
| 任务 | 命令 |
|---|---|
| 运行所有 | npx playwright test |
| 运行一个文件 | npx playwright test login.spec.ts |
| 调试模式 | npx playwright test --debug |
| UI模式 | npx playwright test --ui |
| 更新快照 | npx playwright test --update-snapshots |