test: 删除role-based-tests目录(任务 3/8)

This commit is contained in:
张翔
2026-04-07 21:46:50 +08:00
parent 0c8c993995
commit 105ad30cc6
5 changed files with 0 additions and 714 deletions
@@ -1,317 +0,0 @@
# 基于角色的用户模拟测试套件
## 概述
本测试套件实现了基于角色的用户模拟测试,用于验证后端管理系统的权限边界和业务流程。
## 架构设计
### 核心组件
1. **角色定义系统** (`roles/`)
- `base.role.ts` - 角色定义基类
- `admin.role.ts` - 管理员角色
- `user.role.ts` - 普通用户角色
- `test.role.ts` - 测试用户角色
- `role-factory.ts` - 角色工厂
2. **共享工具** (`shared/`)
- `role-auth-manager.ts` - Token管理器
- `auth-helper.ts` - 认证辅助工具
- `test-data-manager.ts` - 测试数据管理器
- `permission-helper.ts` - 权限验证工具
3. **测试场景** (`scenarios/`)
- `authentication/` - 认证场景测试
- `user-management/` - 用户管理场景测试
## 快速开始
### 环境准备
1. 确保后端服务运行在 `http://localhost:8084`
2. 确保前端服务运行在 `http://localhost:3002`
3. 确保H2数据库已初始化测试数据
### 运行测试
```bash
# 运行所有单元测试
pnpm test
# 运行角色测试项目
pnpm exec playwright test --project=role-based-tests
# 运行特定测试文件
pnpm exec playwright test e2e/role-based-tests/scenarios/authentication/login-flow.spec.ts
# 运行特定角色的测试
pnpm exec playwright test --project=role-based-tests --grep "管理员"
```
## 角色配置
### 测试用户
所有测试用户统一使用密码:`Test@123`
| 用户名 | 角色 | 说明 |
|--------|------|------|
| admin | 超级管理员 | 拥有所有权限 |
| normaluser | 普通用户 | 只能访问个人信息 |
| e2e_test_user | 测试用户 | 用于E2E测试 |
### 权限定义
每个角色定义包含:
- `permissions` - 拥有的权限列表
- `cannotAccess` - 无法访问的路径
- `expectedBehaviors` - 预期行为(CRUD权限)
## 测试场景
### 认证场景
- 登录流程测试(6个测试用例)
- 管理员用户登录成功
- 普通用户登录成功
- 错误密码登录失败
- 空用户名登录失败
- 空密码登录失败
- Token注入登录
- 登出流程测试(4个测试用例)
- 用户登出成功
- 登出后无法访问受保护页面
- 登出后Token被清除
- 多角色登出测试
### 用户管理场景
- 管理员创建用户测试(5个测试用例)
- 管理员可以创建新用户
- 管理员可以编辑用户信息
- 管理员可以删除用户
- 创建用户时用户名重复验证
- 创建用户时邮箱格式验证
- 权限边界验证测试(11个测试用例)
- 管理员权限验证(5个)
- 普通用户权限验证(4个)
- 测试用户权限验证(2个)
- 跨角色权限对比测试
## 测试数据管理
### 自动清理
测试数据管理器会自动跟踪创建的测试数据,并在测试结束后清理:
```typescript
import { getTestDataManager } from '../shared/test-data-manager';
test.afterEach(async () => {
await getTestDataManager().cleanup('user');
});
```
### 手动创建测试数据
```typescript
const testDataManager = getTestDataManager();
const user = await testDataManager.createUser({
username: 'testuser',
password: 'Test@123',
email: 'test@example.com',
});
```
## 认证方式
### Token注入(推荐)
```typescript
import { createAuthenticatedPage } from '../shared/auth-helper';
test.beforeEach(async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'admin');
});
```
### 真实登录
```typescript
import { AuthHelper } from '../shared/auth-helper';
const authHelper = new AuthHelper(page, context);
await authHelper.loginAsRole('admin', false); // false表示使用真实登录
```
## 权限验证
```typescript
import { createPermissionHelper } from '../shared/permission-helper';
const permissionHelper = createPermissionHelper(page);
// 验证可以访问
await permissionHelper.verifyCanAccess('/user-management');
// 验证无法访问
await permissionHelper.verifyCannotAccess('/role-management');
// 验证角色权限边界
const role = RoleFactory.getRole('admin');
await permissionHelper.verifyRolePermissions(role);
```
## 最佳实践
1. **使用Token注入**:提升测试执行效率
2. **遵循TDD原则**:先写测试,再实现功能
3. **测试数据隔离**:每个测试独立创建和清理数据
4. **权限边界验证**:确保每个角色的权限边界清晰
5. **跨浏览器测试**:在Chrome、Firefox、Safari上运行测试
## 故障排查
### 登录失败
1. 检查后端服务是否运行
2. 检查数据库是否初始化
3. 检查密码是否正确(应为 `Test@123`
### 权限验证失败
1. 检查角色定义是否正确
2. 检查后端权限配置
3. 检查前端路由守卫
### 测试数据清理失败
1. 检查数据库连接
2. 检查API权限
3. 手动清理测试数据
## CI/CD集成
### Jenkins Pipeline示例
```groovy
stage('Role-Based Tests') {
steps {
sh 'pnpm install'
sh 'pnpm exec playwright test --project=role-based-tests'
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'playwright-report',
reportFiles: 'index.html',
reportName: 'Playwright Report'
])
}
}
}
```
## 维护指南
### 添加新角色
1.`roles/` 目录创建新的角色定义文件
2.`role-factory.ts` 中注册新角色
3.`data-h2.sql` 中添加测试用户数据
4. 编写对应的测试用例
### 添加新测试场景
1.`scenarios/` 目录创建新的测试文件
2. 使用现有的工具类(认证、数据管理、权限验证)
3. 确保测试数据隔离和清理
4. 更新文档
## 统计信息
- **单元测试**172个测试用例
- **E2E测试**:26个测试场景(角色基础) + 18个测试用例(用户旅程)
- **角色定义**3个角色
- **用户旅程测试**5个工作流
- **测试覆盖率**:核心功能100%
## 用户旅程测试
### 概述
用户旅程测试(User Journey Tests)位于 `e2e/journeys/` 目录,模拟真实用户的完整操作流程,提供更贴近实际使用的测试覆盖。
### 测试文件
| 文件 | 测试用例数 | 描述 |
|------|-----------|------|
| `admin-complete-workflow.spec.ts` | 5 | 管理员完整工作流(登录、创建角色、创建用户、验证、清理) |
| `user-permission-boundary.spec.ts` | 3 | 用户权限边界验证 |
| `audit-workflow.spec.ts` | 3 | 审计工作流(操作日志、登录日志、搜索筛选) |
| `file-management-workflow.spec.ts` | 3 | 文件管理工作流(上传、搜索、删除) |
| `system-config-workflow.spec.ts` | 4 | 系统配置工作流(配置查看、修改、字典管理、参数管理) |
### 运行用户旅程测试
```bash
# 运行所有用户旅程测试
pnpm run test:e2e:journeys
# 运行特定测试文件
pnpm exec playwright test journeys/admin-complete-workflow.spec.ts
# 有头模式运行
pnpm run test:e2e:headed --project=journeys
# 调试模式
pnpm run test:e2e:debug journeys/admin-complete-workflow.spec.ts
```
### 测试优化成果
通过用户旅程测试重构,实现了:
- **测试文件减少 70%**:从 50 个文件减少到 15 个文件
- **测试用例减少 64%**:从 418 个用例减少到 150 个用例
- **执行时间减少 67%**:从 ~30 分钟减少到 ~10 分钟
- **维护成本降低 60%**:更清晰的测试结构,更少的重复代码
### 测试架构对比
| 维度 | 优化前 | 优化后 |
|------|--------|--------|
| 测试文件数 | 50 | 15 |
| 测试用例数 | 418 | 150 |
| 执行时间 | ~30分钟 | ~10分钟 |
| 重复测试 | 多个登录测试 | 统一登录流程 |
| 测试类型 | 功能点测试 | 用户旅程测试 |
## 更新日志
### v2.0.0 (2026-04-07)
- ✅ 实现用户旅程测试架构
- ✅ 创建 5 个核心用户旅程测试
- ✅ 删除 18 个冗余测试文件
- ✅ 启用测试并行执行
- ✅ 添加测试脚本命令
- ✅ 优化测试执行效率 3 倍
### v1.0.0 (2026-04-04)
- ✅ 实现角色定义系统
- ✅ 实现认证辅助工具
- ✅ 实现测试数据管理器
- ✅ 实现权限验证工具
- ✅ 实现认证场景测试
- ✅ 实现用户管理场景测试
- ✅ 统一H2数据库密码配置
- ✅ 配置Playwright测试项目
@@ -1,83 +0,0 @@
import { test, expect } from '@playwright/test';
import { RoleFactory } from '@/role-based-tests/roles/role-factory';
import { createAuthenticatedPage } from '@/role-based-tests/shared/auth-helper';
test.describe('登录流程测试', () => {
test('管理员用户登录成功', async ({ page, context }) => {
const role = RoleFactory.getRole('admin');
await page.goto('/login');
await page.fill('input[placeholder*="用户名"]', role.credentials.username);
await page.fill('input[placeholder*="密码"]', role.credentials.password);
await page.click('button:has-text("登录")');
await expect(page).toHaveURL(/\/(dashboard|\/)?/, { timeout: 10000 });
await page.waitForLoadState('networkidle');
});
test('普通用户登录成功', async ({ page, context }) => {
const role = RoleFactory.getRole('user');
await page.goto('/login');
await page.fill('input[placeholder*="用户名"]', role.credentials.username);
await page.fill('input[placeholder*="密码"]', role.credentials.password);
await page.click('button:has-text("登录")');
await expect(page).toHaveURL(/\/(dashboard|\/)?/, { timeout: 10000 });
});
test('错误密码登录失败', async ({ page }) => {
await page.goto('/login');
await page.fill('input[placeholder*="用户名"]', 'admin');
await page.fill('input[placeholder*="密码"]', 'wrongpassword');
await Promise.all([
page.waitForResponse(resp => resp.url().includes('/auth/login') && resp.status() === 401),
page.click('button:has-text("登录")')
]);
const errorMessage = page.locator('.el-message');
await expect(errorMessage).toBeVisible({ timeout: 10000 });
await expect(errorMessage).toContainText(/用户名或密码错误|登录失败/i);
await expect(page).toHaveURL(/\/login/);
});
test('空用户名登录失败', async ({ page }) => {
await page.goto('/login');
await page.fill('input[placeholder*="密码"]', 'Test@123');
await page.click('input[placeholder*="用户名"]');
await page.click('input[placeholder*="密码"]');
await page.click('button:has-text("登录")');
const validationMessage = page.locator('.el-form-item__error');
await expect(validationMessage).toBeVisible({ timeout: 5000 });
});
test('空密码登录失败', async ({ page }) => {
await page.goto('/login');
await page.fill('input[placeholder*="用户名"]', 'admin');
await page.click('input[placeholder*="密码"]');
await page.click('input[placeholder*="用户名"]');
await page.click('button:has-text("登录")');
const validationMessage = page.locator('.el-form-item__error');
await expect(validationMessage).toBeVisible({ timeout: 5000 });
});
test('Token注入登录', async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'admin');
await page.goto('/dashboard');
await expect(page).toHaveURL(/\/dashboard/);
await page.waitForLoadState('networkidle');
});
});
@@ -1,80 +0,0 @@
import { test, expect } from '@playwright/test';
import { RoleFactory } from '@/role-based-tests/roles/role-factory';
import { AuthHelper } from '@/role-based-tests/shared/auth-helper';
test.describe('登出流程测试', () => {
let authHelper: AuthHelper;
test.beforeEach(async ({ page, context }) => {
authHelper = new AuthHelper(page, context);
await authHelper.loginAsRole('admin');
});
test('用户登出成功', async ({ page }) => {
await page.goto('/dashboard');
await page.waitForSelector('.el-dropdown', { state: 'visible' });
await page.click('.el-dropdown .el-avatar');
await page.waitForSelector('.el-dropdown-menu', { state: 'visible', timeout: 3000 });
await page.click('.el-dropdown-menu-item:has-text("退出登录")');
await expect(page).toHaveURL(/\/login/, { timeout: 10000 });
const loginButton = page.locator('button:has-text("登录")');
await expect(loginButton).toBeVisible();
});
test('登出后无法访问受保护页面', async ({ page }) => {
await page.goto('/dashboard');
await page.waitForSelector('.el-dropdown', { state: 'visible' });
await page.click('.el-dropdown .el-avatar');
await page.waitForSelector('.el-dropdown-menu', { state: 'visible', timeout: 3000 });
await page.click('.el-dropdown-menu-item:has-text("退出登录")');
await expect(page).toHaveURL(/\/login/);
await page.goto('/users');
await expect(page).toHaveURL(/\/login/);
});
test('登出后Token被清除', async ({ page, context }) => {
await page.goto('/dashboard');
await page.waitForSelector('.el-dropdown', { state: 'visible' });
await page.click('.el-dropdown .el-avatar');
await page.waitForSelector('.el-dropdown-menu', { state: 'visible', timeout: 3000 });
await page.click('.el-dropdown-menu-item:has-text("退出登录")');
await expect(page).toHaveURL(/\/login/);
const cookies = await context.cookies();
const tokenCookie = cookies.find(c => c.name === 'token');
expect(tokenCookie).toBeUndefined();
const localStorageToken = await page.evaluate(() => {
return localStorage.getItem('token');
});
expect(localStorageToken).toBeNull();
});
test('多角色登出测试', async ({ page, context }) => {
const roles = ['admin', 'user', 'test'];
for (const roleName of roles) {
const helper = new AuthHelper(page, context);
await helper.clearAuth();
await helper.loginAsRole(roleName);
await page.goto('/dashboard');
await page.waitForSelector('.el-dropdown', { state: 'visible' });
await page.click('.el-dropdown .el-avatar');
await page.waitForSelector('.el-dropdown-menu', { state: 'visible', timeout: 3000 });
await page.click('.el-dropdown-menu-item:has-text("退出登录")');
await expect(page).toHaveURL(/\/login/, { timeout: 10000 });
}
});
});
@@ -1,102 +0,0 @@
import { test, expect } from '@playwright/test';
import { RoleFactory } from '@/role-based-tests/roles/role-factory';
import { createAuthenticatedPage } from '@/role-based-tests/shared/auth-helper';
import { getTestDataManager } from '@/role-based-tests/shared/test-data-manager';
test.describe('管理员创建用户测试', () => {
test.beforeEach(async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'admin');
getTestDataManager().setPage(page);
});
test.afterEach(async () => {
await getTestDataManager().cleanup('user');
});
test('管理员可以创建新用户', async ({ page }) => {
await page.goto('/users');
await page.click('button:has-text("新增")');
const timestamp = Date.now();
const userData = {
username: `testuser_${timestamp}`,
password: 'Test@123',
email: `testuser_${timestamp}@test.com`,
phone: '13800138000',
nickname: '测试用户',
};
await page.fill('input[placeholder*="用户名"]', userData.username);
await page.fill('input[placeholder*="密码"]', userData.password);
await page.fill('input[placeholder*="邮箱"]', userData.email);
await page.fill('input[placeholder*="手机号"]', userData.phone);
await page.fill('input[placeholder*="昵称"]', userData.nickname);
await page.click('button:has-text("确定")');
const successMessage = page.locator('text=/创建成功|操作成功/i');
await expect(successMessage).toBeVisible({ timeout: 10000 });
const createdUser = page.locator(`text=${userData.username}`);
await expect(createdUser).toBeVisible();
});
test('管理员可以编辑用户信息', async ({ page }) => {
await page.goto('/users');
const firstEditButton = page.locator('button:has-text("编辑")').first();
await firstEditButton.click();
const nicknameInput = page.locator('input[placeholder*="昵称"]');
await nicknameInput.fill('更新后的昵称');
await page.click('button:has-text("确定")');
const successMessage = page.locator('text=/更新成功|操作成功/i');
await expect(successMessage).toBeVisible({ timeout: 10000 });
});
test('管理员可以删除用户', async ({ page }) => {
await page.goto('/users');
const firstDeleteButton = page.locator('button:has-text("删除")').first();
await firstDeleteButton.click();
const confirmButton = page.locator('button:has-text("确定")');
await confirmButton.click();
const successMessage = page.locator('text=/删除成功|操作成功/i');
await expect(successMessage).toBeVisible({ timeout: 10000 });
});
test('创建用户时用户名重复验证', async ({ page }) => {
await page.goto('/users');
await page.click('button:has-text("新增")');
await page.fill('input[placeholder*="用户名"]', 'admin');
await page.fill('input[placeholder*="密码"]', 'Test@123');
await page.fill('input[placeholder*="邮箱"]', 'admin@test.com');
await page.click('button:has-text("确定")');
const errorMessage = page.locator('text=/用户名已存在|用户名重复/i');
await expect(errorMessage).toBeVisible({ timeout: 5000 });
});
test('创建用户时邮箱格式验证', async ({ page }) => {
await page.goto('/users');
await page.click('button:has-text("新增")');
await page.fill('input[placeholder*="用户名"]', 'testuser');
await page.fill('input[placeholder*="密码"]', 'Test@123');
await page.fill('input[placeholder*="邮箱"]', 'invalid-email');
await page.click('button:has-text("确定")');
const errorMessage = page.locator('text=/邮箱格式不正确|请输入正确的邮箱/i');
await expect(errorMessage).toBeVisible({ timeout: 5000 });
});
});
@@ -1,132 +0,0 @@
import { test, expect } from '@playwright/test';
import { RoleFactory } from '@/role-based-tests/roles/role-factory';
import { createAuthenticatedPage } from '@/role-based-tests/shared/auth-helper';
import { createPermissionHelper } from '@/role-based-tests/shared/permission-helper';
test.describe('权限边界验证测试', () => {
test.describe('管理员权限', () => {
test.beforeEach(async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'admin');
});
test('管理员可以访问用户管理页面', async ({ page }) => {
const permissionHelper = createPermissionHelper(page);
const adminRole = RoleFactory.getRole('admin');
await permissionHelper.verifyCanAccess('/users');
});
test('管理员可以访问角色管理页面', async ({ page }) => {
const permissionHelper = createPermissionHelper(page);
await permissionHelper.verifyCanAccess('/roles');
});
test('管理员可以创建用户', async ({ page }) => {
await page.goto('/users');
const createButton = page.locator('button:has-text("新增用户")');
await expect(createButton).toBeVisible();
await expect(createButton).toBeEnabled();
});
test('管理员可以编辑用户', async ({ page }) => {
await page.goto('/users');
await page.waitForLoadState('networkidle');
const editButton = page.locator('button:has-text("编辑")').first();
await expect(editButton).toBeVisible({ timeout: 5000 });
});
test('管理员可以删除用户', async ({ page }) => {
await page.goto('/users');
await page.waitForLoadState('networkidle');
const deleteButton = page.locator('button:has-text("删除")').first();
await expect(deleteButton).toBeVisible({ timeout: 5000 });
});
});
test.describe('普通用户权限', () => {
test.beforeEach(async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'user');
});
test('普通用户无法访问用户管理页面', async ({ page }) => {
const permissionHelper = createPermissionHelper(page);
const userRole = RoleFactory.getRole('user');
await permissionHelper.verifyCannotAccess('/users');
});
test('普通用户无法访问角色管理页面', async ({ page }) => {
const permissionHelper = createPermissionHelper(page);
await permissionHelper.verifyCannotAccess('/roles');
});
test('普通用户可以访问个人中心', async ({ page }) => {
await page.goto('/profile');
await expect(page).not.toHaveURL(/\/login/);
await expect(page).not.toHaveURL(/\/403/);
});
test('普通用户可以修改个人信息', async ({ page }) => {
await page.goto('/profile');
const editButton = page.locator('button:has-text("编辑")');
const count = await editButton.count();
if (count > 0) {
await expect(editButton.first()).toBeVisible();
}
});
});
test.describe('测试用户权限', () => {
test.beforeEach(async ({ page, context }) => {
await createAuthenticatedPage(page, context, 'test');
});
test('测试用户无法访问用户管理页面', async ({ page }) => {
const permissionHelper = createPermissionHelper(page);
await permissionHelper.verifyCannotAccess('/users');
});
test('测试用户可以访问测试页面', async ({ page }) => {
await page.goto('/test');
await expect(page).not.toHaveURL(/\/login/);
await expect(page).not.toHaveURL(/\/403/);
});
});
test.describe('跨角色权限对比', () => {
test('不同角色访问权限对比', async ({ page, context }) => {
const roles = ['admin', 'user', 'test'];
const protectedPaths = ['/users', '/roles', '/menus'];
for (const roleName of roles) {
const role = RoleFactory.getRole(roleName);
const helper = new (await import('../../shared/auth-helper')).AuthHelper(page, context);
await helper.clearAuth();
await helper.loginAsRole(roleName);
for (const path of protectedPaths) {
await page.goto(path);
const isForbidden = role.cannotAccess.includes(path);
const url = page.url();
if (isForbidden) {
expect(url.includes('/403') || url.includes('/login')).toBeTruthy();
} else {
expect(url.includes('/403')).toBeFalsy();
}
}
}
});
});
});