- 添加菜单数据修复设计文档 - 添加用户管理和角色管理测试修复设计文档 - 添加本地开发测试设计文档 - 添加相关实现计划
48 KiB
基于角色的用户模拟测试套件实现计划
面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(
- [ ])语法来跟踪进度。
目标: 实现一个基于角色的用户模拟测试套件,替换现有E2E测试,达到真实场景验收标准。
架构: 采用混合测试模式(业务流程 + 权限验证),使用Token注入提升执行效率,通过角色定义系统实现权限边界验证,配合测试数据管理器确保测试隔离性。
技术栈: TypeScript + Playwright + H2 Database (测试环境) + Spring Boot BCrypt
文件结构
后端文件(密码配置修复)
| 文件路径 | 职责 | 操作 |
|---|---|---|
novalon-manage-api/manage-app/src/main/resources/data-h2.sql |
主应用H2测试数据 | 修改:统一密码配置 |
novalon-manage-api/manage-app/src/test/resources/data-h2.sql |
测试环境H2数据 | 保持不变(已正确) |
novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java |
密码Hash生成工具 | 修改:添加验证逻辑 |
前端文件(测试框架)
| 文件路径 | 职责 | 操作 |
|---|---|---|
novalon-manage-web/e2e/role-based-tests/roles/base.role.ts |
角色定义基类 | 创建 |
novalon-manage-web/e2e/role-based-tests/roles/admin.role.ts |
管理员角色定义 | 创建 |
novalon-manage-web/e2e/role-based-tests/roles/user.role.ts |
普通用户角色定义 | 创建 |
novalon-manage-web/e2e/role-based-tests/roles/test.role.ts |
测试用户角色定义 | 创建 |
novalon-manage-web/e2e/role-based-tests/roles/role-factory.ts |
角色工厂 | 创建 |
novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts |
认证辅助工具 | 创建 |
novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts |
Token管理器 | 创建 |
novalon-manage-web/e2e/role-based-tests/shared/test-data-manager.ts |
测试数据管理器 | 创建 |
novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts |
权限验证工具 | 创建 |
novalon-manage-web/e2e/role-based-tests/scenarios/authentication/login-flow.spec.ts |
登录流程测试 | 创建 |
novalon-manage-web/e2e/role-based-tests/scenarios/authentication/logout-flow.spec.ts |
登出流程测试 | 创建 |
novalon-manage-web/e2e/role-based-tests/scenarios/user-management/admin-creates-user.spec.ts |
管理员创建用户测试 | 创建 |
novalon-manage-web/e2e/role-based-tests/scenarios/user-management/permission-boundary.spec.ts |
权限边界验证测试 | 创建 |
novalon-manage-web/playwright.config.ts |
Playwright配置 | 修改:添加角色测试项目 |
novalon-manage-web/.env.test |
测试环境变量 | 创建 |
novalon-manage-web/package.json |
项目配置 | 修改:添加测试脚本 |
任务清单
任务 1:修复H2数据库密码不一致问题(P0)
目标: 统一主应用和测试环境的H2数据库密码配置,确保所有环境使用相同的密码和BCrypt版本。
文件:
- 修改:
novalon-manage-api/manage-app/src/main/resources/data-h2.sql - 修改:
novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java
- 步骤 1:验证BCrypt版本兼容性
编写测试验证Spring Security BCryptPasswordEncoder能否正确验证$2a$和$2b$版本的hash。
// novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java
@Test
public void verifyBCryptVersions() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
String password = "Test@123";
// $2a$ hash (测试环境当前使用)
String hash2a = "$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C";
boolean matches2a = passwordEncoder.matches(password, hash2a);
System.out.println("========================================");
System.out.println("验证 $2a$ hash:");
System.out.println("密码: " + password);
System.out.println("Hash: " + hash2a);
System.out.println("验证结果: " + matches2a);
System.out.println("========================================");
assertTrue(matches2a, "$2a$ hash验证失败");
// $2b$ hash (主应用当前使用)
String hash2b = "$2b$12$SFefXlGRFMA0fvxIufpWPuIAl0OPLgRDoCZPThCvjpiJGPYS8yNYy";
boolean matches2b = passwordEncoder.matches("admin123", hash2b);
System.out.println("验证 $2b$ hash:");
System.out.println("密码: admin123");
System.out.println("Hash: " + hash2b);
System.out.println("验证结果: " + matches2b);
System.out.println("========================================");
assertTrue(matches2b, "$2b$ hash验证失败");
}
- 步骤 2:运行测试验证BCrypt兼容性
运行:cd novalon-manage-api && ./gradlew :manage-sys:test --tests PasswordHashGenerator.verifyBCryptVersions
预期:PASS,输出显示两个版本的hash都能正确验证
- 步骤 3:更新主应用data-h2.sql密码配置
将主应用的H2测试数据密码统一为Test@123,使用$2a$版本的hash。
-- novalon-manage-api/manage-app/src/main/resources/data-h2.sql
-- 插入测试用户
-- BCrypt哈希值对应明文密码: Test@123
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
VALUES
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'),
(2, 'testadmin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'testadmin@novalon.com', '13800138001', '测试管理员', 1, 'system', 'system'),
(3, 'normaluser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'normaluser@novalon.com', '13800138002', '普通用户', 1, 'system', 'system'),
(4, 'guestuser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'guestuser@novalon.com', '13800138003', '访客用户', 1, 'system', 'system'),
(5, 'disableduser', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'disableduser@novalon.com', '13800138004', '禁用用户', 0, 'system', 'system'),
(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system');
- 步骤 4:验证密码配置一致性
编写测试验证主应用和测试环境的密码配置一致。
// novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java
@Test
public void verifyPasswordConsistency() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
String password = "Test@123";
String hash = "$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C";
boolean matches = passwordEncoder.matches(password, hash);
System.out.println("========================================");
System.out.println("密码一致性验证:");
System.out.println("明文密码: " + password);
System.out.println("Hash: " + hash);
System.out.println("验证结果: " + matches);
System.out.println("========================================");
assertTrue(matches, "密码配置不一致");
}
- 步骤 5:运行密码一致性测试
运行:cd novalon-manage-api && ./gradlew :manage-sys:test --tests PasswordHashGenerator.verifyPasswordConsistency
预期:PASS
- 步骤 6:Commit密码配置修复
cd novalon-manage-api
git add manage-app/src/main/resources/data-h2.sql
git add manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java
git commit -m "fix: 统一H2数据库密码配置为Test@123
- 统一主应用和测试环境的密码配置
- 使用BCrypt $2a$版本hash
- 添加密码验证测试确保一致性
影响范围:
- manage-app/src/main/resources/data-h2.sql
- manage-sys/src/test/java/cn/novalon/manage/sys/util/PasswordHashGenerator.java"
任务 2:创建测试框架目录结构
目标: 建立清晰的测试框架目录结构,为后续开发奠定基础。
文件:
- 创建目录:
novalon-manage-web/e2e/role-based-tests/ - 创建目录:
novalon-manage-web/e2e/role-based-tests/roles/ - 创建目录:
novalon-manage-web/e2e/role-based-tests/scenarios/ - 创建目录:
novalon-manage-web/e2e/role-based-tests/scenarios/authentication/ - 创建目录:
novalon-manage-web/e2e/role-based-tests/scenarios/user-management/ - 创建目录:
novalon-manage-web/e2e/role-based-tests/shared/
- 步骤 1:创建目录结构
cd novalon-manage-web
mkdir -p e2e/role-based-tests/roles
mkdir -p e2e/role-based-tests/scenarios/authentication
mkdir -p e2e/role-based-tests/scenarios/user-management
mkdir -p e2e/role-based-tests/shared
- 步骤 2:验证目录结构
运行:tree novalon-manage-web/e2e/role-based-tests -L 2
预期输出:
e2e/role-based-tests/
├── roles/
├── scenarios/
│ ├── authentication/
│ └── user-management/
└── shared/
- 步骤 3:Commit目录结构
cd novalon-manage-web
git add e2e/role-based-tests/
git commit -m "chore: 创建基于角色的测试框架目录结构
创建目录:
- roles/ - 角色定义
- scenarios/ - 业务场景测试
- authentication/ - 认证场景
- user-management/ - 用户管理场景
- shared/ - 共享工具"
任务 3:实现角色定义系统
目标: 实现角色定义基类和具体角色定义,为测试提供角色信息。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/roles/base.role.ts - 创建:
novalon-manage-web/e2e/role-based-tests/roles/admin.role.ts - 创建:
novalon-manage-web/e2e/role-based-tests/roles/user.role.ts - 创建:
novalon-manage-web/e2e/role-based-tests/roles/test.role.ts - 创建:
novalon-manage-web/e2e/role-based-tests/roles/role-factory.ts - 创建:
novalon-manage-web/e2e/role-based-tests/roles/__tests__/role-factory.test.ts
- 步骤 1:编写角色定义基类测试
// novalon-manage-web/e2e/role-based-tests/roles/__tests__/base.role.test.ts
import { describe, it, expect } from '@playwright/test';
import type { RoleDefinition } from '../base.role';
describe('RoleDefinition', () => {
it('should define required role properties', () => {
const role: RoleDefinition = {
name: 'test',
displayName: '测试角色',
credentials: {
username: 'testuser',
password: 'Test@123'
},
permissions: ['test:read', 'test:write'],
cannotAccess: ['/admin'],
expectedBehaviors: {
canCreate: ['test'],
canRead: ['test'],
canUpdate: ['test'],
canDelete: []
}
};
expect(role.name).toBe('test');
expect(role.displayName).toBe('测试角色');
expect(role.credentials.username).toBe('testuser');
expect(role.credentials.password).toBe('Test@123');
expect(role.permissions).toHaveLength(2);
expect(role.cannotAccess).toHaveLength(1);
});
});
- 步骤 2:运行测试验证失败
运行:cd novalon-manage-web && pnpm test roles/__tests__/base.role.test.ts
预期:FAIL,报错 "Cannot find module '../base.role'"
- 步骤 3:实现角色定义基类
// novalon-manage-web/e2e/role-based-tests/roles/base.role.ts
export interface RoleDefinition {
name: string;
displayName: string;
credentials: {
username: string;
password: string;
};
permissions: string[];
cannotAccess: string[];
expectedBehaviors: {
canCreate: string[];
canRead: string[];
canUpdate: string[];
canDelete: string[];
};
}
- 步骤 4:运行测试验证通过
运行:cd novalon-manage-web && pnpm test roles/__tests__/base.role.test.ts
预期:PASS
- 步骤 5:编写管理员角色定义测试
// novalon-manage-web/e2e/role-based-tests/roles/__tests__/admin.role.test.ts
import { describe, it, expect } from '@playwright/test';
import { AdminRole } from '../admin.role';
describe('AdminRole', () => {
it('should have admin credentials', () => {
expect(AdminRole.name).toBe('admin');
expect(AdminRole.displayName).toBe('超级管理员');
expect(AdminRole.credentials.username).toBe('admin');
expect(AdminRole.credentials.password).toBe('Test@123');
});
it('should have all permissions', () => {
expect(AdminRole.permissions).toContain('user:*');
expect(AdminRole.permissions).toContain('role:*');
expect(AdminRole.permissions).toContain('menu:*');
expect(AdminRole.cannotAccess).toHaveLength(0);
});
it('should be able to create all resources', () => {
expect(AdminRole.expectedBehaviors.canCreate).toContain('user');
expect(AdminRole.expectedBehaviors.canCreate).toContain('role');
expect(AdminRole.expectedBehaviors.canCreate).toContain('menu');
});
});
- 步骤 6:运行测试验证失败
运行:cd novalon-manage-web && pnpm test roles/__tests__/admin.role.test.ts
预期:FAIL,报错 "Cannot find module '../admin.role'"
- 步骤 7:实现管理员角色定义
// novalon-manage-web/e2e/role-based-tests/roles/admin.role.ts
import type { RoleDefinition } from './base.role';
export const AdminRole: RoleDefinition = {
name: 'admin',
displayName: '超级管理员',
credentials: {
username: 'admin',
password: 'Test@123'
},
permissions: [
'user:*',
'role:*',
'menu:*',
'config:*',
'log:read',
'dict:*'
],
cannotAccess: [],
expectedBehaviors: {
canCreate: ['user', 'role', 'menu', 'config', 'dict'],
canRead: ['user', 'role', 'menu', 'config', 'dict', 'log'],
canUpdate: ['user', 'role', 'menu', 'config', 'dict'],
canDelete: ['user', 'role', 'menu', 'config', 'dict']
}
};
- 步骤 8:运行测试验证通过
运行:cd novalon-manage-web && pnpm test roles/__tests__/admin.role.test.ts
预期:PASS
- 步骤 9:实现普通用户角色定义
// novalon-manage-web/e2e/role-based-tests/roles/user.role.ts
import type { RoleDefinition } from './base.role';
export const UserRole: RoleDefinition = {
name: 'user',
displayName: '普通用户',
credentials: {
username: 'normaluser',
password: 'Test@123'
},
permissions: [
'user:read:self',
'user:update:self'
],
cannotAccess: [
'/user-management',
'/role-management',
'/menu-management',
'/system-config'
],
expectedBehaviors: {
canCreate: [],
canRead: ['self'],
canUpdate: ['self'],
canDelete: []
}
};
- 步骤 10:实现测试用户角色定义
// novalon-manage-web/e2e/role-based-tests/roles/test.role.ts
import type { RoleDefinition } from './base.role';
export const TestRole: RoleDefinition = {
name: 'test',
displayName: '测试用户',
credentials: {
username: 'e2e_test_user',
password: 'Test@123'
},
permissions: [
'test:read',
'test:write'
],
cannotAccess: [
'/user-management',
'/role-management'
],
expectedBehaviors: {
canCreate: ['test'],
canRead: ['test'],
canUpdate: ['test'],
canDelete: []
}
};
- 步骤 11:编写角色工厂测试
// novalon-manage-web/e2e/role-based-tests/roles/__tests__/role-factory.test.ts
import { describe, it, expect } from '@playwright/test';
import { RoleFactory } from '../role-factory';
describe('RoleFactory', () => {
it('should get admin role', () => {
const role = RoleFactory.getRole('admin');
expect(role.name).toBe('admin');
expect(role.credentials.username).toBe('admin');
});
it('should get user role', () => {
const role = RoleFactory.getRole('user');
expect(role.name).toBe('user');
expect(role.credentials.username).toBe('normaluser');
});
it('should throw error for unknown role', () => {
expect(() => RoleFactory.getRole('unknown')).toThrow("Role 'unknown' not found");
});
it('should get all roles', () => {
const roles = RoleFactory.getAllRoles();
expect(roles).toHaveLength(3);
expect(roles.map(r => r.name)).toContain('admin');
expect(roles.map(r => r.name)).toContain('user');
expect(roles.map(r => r.name)).toContain('test');
});
});
- 步骤 12:运行测试验证失败
运行:cd novalon-manage-web && pnpm test roles/__tests__/role-factory.test.ts
预期:FAIL,报错 "Cannot find module '../role-factory'"
- 步骤 13:实现角色工厂
// novalon-manage-web/e2e/role-based-tests/roles/role-factory.ts
import type { RoleDefinition } from './base.role';
import { AdminRole } from './admin.role';
import { UserRole } from './user.role';
import { TestRole } from './test.role';
export class RoleFactory {
private static roles: Map<string, RoleDefinition> = new Map([
['admin', AdminRole],
['user', UserRole],
['test', TestRole]
]);
static getRole(roleName: string): RoleDefinition {
const role = this.roles.get(roleName);
if (!role) {
throw new Error(`Role '${roleName}' not found`);
}
return role;
}
static getAllRoles(): RoleDefinition[] {
return Array.from(this.roles.values());
}
}
- 步骤 14:运行测试验证通过
运行:cd novalon-manage-web && pnpm test roles/__tests__/role-factory.test.ts
预期:PASS
- 步骤 15:Commit角色定义系统
cd novalon-manage-web
git add e2e/role-based-tests/roles/
git commit -m "feat: 实现角色定义系统
- 创建角色定义基类 RoleDefinition
- 实现管理员角色 AdminRole
- 实现普通用户角色 UserRole
- 实现测试用户角色 TestRole
- 实现角色工厂 RoleFactory
- 添加完整的单元测试
所有角色统一使用密码: Test@123"
任务 4:实现认证辅助工具
目标: 实现Token管理器和认证辅助类,支持Token注入和真实登录两种模式。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts - 创建:
novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts - 创建:
novalon-manage-web/e2e/role-based-tests/shared/__tests__/role-auth-manager.test.ts - 创建:
novalon-manage-web/e2e/role-based-tests/shared/__tests__/auth-helper.test.ts
- 步骤 1:编写Token管理器测试
// novalon-manage-web/e2e/role-based-tests/shared/__tests__/role-auth-manager.test.ts
import { describe, it, expect, beforeEach } from '@playwright/test';
import { RoleAuthManager } from '../role-auth-manager';
describe('RoleAuthManager', () => {
beforeEach(() => {
RoleAuthManager.clearCache();
});
it('should get role token', async () => {
const token = await RoleAuthManager.getRoleToken('admin');
expect(token).toBeDefined();
expect(typeof token).toBe('string');
expect(token.length).toBeGreaterThan(0);
});
it('should cache token', async () => {
const token1 = await RoleAuthManager.getRoleToken('admin');
const token2 = await RoleAuthManager.getRoleToken('admin');
expect(token1).toBe(token2);
});
it('should throw error for unknown role', async () => {
await expect(RoleAuthManager.getRoleToken('unknown')).rejects.toThrow();
});
});
- 步骤 2:运行测试验证失败
运行:cd novalon-manage-web && pnpm test shared/__tests__/role-auth-manager.test.ts
预期:FAIL,报错 "Cannot find module '../role-auth-manager'"
- 步骤 3:实现Token管理器
// novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts
import { RoleFactory } from '../roles/role-factory';
interface TokenCache {
token: string;
expiresAt: number;
}
export class RoleAuthManager {
private static tokenCache: Map<string, TokenCache> = new Map();
private static readonly API_BASE_URL = process.env.VITE_API_BASE_URL || 'http://localhost:8084';
private static readonly TOKEN_EXPIRY_MS = 86400000; // 24小时
static async getRoleToken(roleName: string): Promise<string> {
const cached = this.tokenCache.get(roleName);
if (cached && cached.expiresAt > Date.now() + 300000) {
return cached.token;
}
const role = RoleFactory.getRole(roleName);
const token = await this.fetchTokenFromAPI(role.credentials);
return token;
}
private static async fetchTokenFromAPI(credentials: {
username: string;
password: string
}): Promise<string> {
const response = await fetch(`${this.API_BASE_URL}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (!response.ok) {
throw new Error(`Failed to fetch token: ${response.statusText}`);
}
const data = await response.json();
this.tokenCache.set(credentials.username, {
token: data.token || data.access_token,
expiresAt: Date.now() + this.TOKEN_EXPIRY_MS
});
return data.token || data.access_token;
}
static clearCache(): void {
this.tokenCache.clear();
}
}
- 步骤 4:运行测试验证通过
运行:cd novalon-manage-web && pnpm test shared/__tests__/role-auth-manager.test.ts
预期:PASS(需要后端服务运行)
- 步骤 5:编写认证辅助类测试
// novalon-manage-web/e2e/role-based-tests/shared/__tests__/auth-helper.test.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../auth-helper';
test.describe('AuthHelper', () => {
test('should login as admin with token injection', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
const token = await page.evaluate(() => localStorage.getItem('token'));
expect(token).toBeDefined();
expect(token!.length).toBeGreaterThan(0);
});
test('should login as admin with full login flow', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', true);
await expect(page).toHaveURL(/\/(dashboard|\/)/);
});
test('should logout successfully', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await AuthHelper.logout(page);
const token = await page.evaluate(() => localStorage.getItem('token'));
expect(token).toBeNull();
});
});
- 步骤 6:运行测试验证失败
运行:cd novalon-manage-web && pnpm test shared/__tests__/auth-helper.test.ts
预期:FAIL,报错 "Cannot find module '../auth-helper'"
- 步骤 7:实现认证辅助类
// novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts
import type { Page } from '@playwright/test';
import { RoleFactory } from '../roles/role-factory';
import { RoleAuthManager } from './role-auth-manager';
export class AuthHelper {
static async loginAsRole(
page: Page,
roleName: string,
useFullLogin: boolean = false
): Promise<void> {
if (useFullLogin) {
await this.performFullLogin(page, roleName);
} else {
try {
await this.injectToken(page, roleName);
} catch (error) {
console.warn('Token注入失败,降级使用真实登录');
await this.performFullLogin(page, roleName);
}
}
}
private static async injectToken(page: Page, roleName: string): Promise<void> {
const token = await RoleAuthManager.getRoleToken(roleName);
await page.goto('/');
await page.evaluate((token) => {
localStorage.setItem('token', token);
localStorage.setItem('access_token', token);
}, token);
await page.reload();
}
private static async performFullLogin(page: Page, roleName: string): Promise<void> {
const role = RoleFactory.getRole(roleName);
await page.goto('/login');
await page.fill('[name="username"]', role.credentials.username);
await page.fill('[name="password"]', role.credentials.password);
await page.click('[type="submit"]');
await page.waitForURL(/\/(dashboard|\/)/);
}
static async logout(page: Page): Promise<void> {
await page.evaluate(() => {
localStorage.removeItem('token');
localStorage.removeItem('access_token');
});
await page.goto('/login');
}
static async isLoggedIn(page: Page): Promise<boolean> {
const token = await page.evaluate(() => localStorage.getItem('token'));
return token !== null && token.length > 0;
}
}
- 步骤 8:运行测试验证通过
运行:cd novalon-manage-web && pnpm test shared/__tests__/auth-helper.test.ts
预期:PASS(需要后端服务运行)
- 步骤 9:Commit认证辅助工具
cd novalon-manage-web
git add e2e/role-based-tests/shared/
git commit -m "feat: 实现认证辅助工具
- 实现 RoleAuthManager Token管理器
- Token缓存和自动刷新
- 从API获取真实Token
- 实现 AuthHelper 认证辅助类
- 支持Token注入模式
- 支持真实登录模式
- 自动降级机制
- 添加完整的单元测试"
任务 5:实现测试数据管理器
目标: 实现测试数据生成和管理工具,确保测试数据隔离和清理。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/shared/test-data-manager.ts - 创建:
novalon-manage-web/e2e/role-based-tests/shared/__tests__/test-data-manager.test.ts
- 步骤 1:编写测试数据管理器测试
// novalon-manage-web/e2e/role-based-tests/shared/__tests__/test-data-manager.test.ts
import { describe, it, expect, beforeEach } from '@playwright/test';
import { TestDataManager } from '../test-data-manager';
describe('TestDataManager', () => {
beforeEach(() => {
TestDataManager.reset();
});
it('should generate test user data', () => {
const user = TestDataManager.generateTestUser();
expect(user.username).toMatch(/^test_[a-f0-9]{8}$/);
expect(user.password).toBe('Test@123');
expect(user.email).toMatch(/^test_[a-f0-9]{8}@example\.com$/);
expect(user.phone).toMatch(/^138\d{8}$/);
});
it('should generate test user with overrides', () => {
const user = TestDataManager.generateTestUser({
username: 'custom_user',
nickname: '自定义用户'
});
expect(user.username).toBe('custom_user');
expect(user.nickname).toBe('自定义用户');
expect(user.password).toBe('Test@123');
});
it('should track created user', () => {
TestDataManager.trackUser('test_user_1');
TestDataManager.trackUser('test_user_2');
const trackedUsers = TestDataManager.getTrackedUsers();
expect(trackedUsers).toContain('test_user_1');
expect(trackedUsers).toContain('test_user_2');
});
});
- 步骤 2:运行测试验证失败
运行:cd novalon-manage-web && pnpm test shared/__tests__/test-data-manager.test.ts
预期:FAIL,报错 "Cannot find module '../test-data-manager'"
- 步骤 3:实现测试数据管理器
// novalon-manage-web/e2e/role-based-tests/shared/test-data-manager.ts
import { v4 as uuidv4 } from 'uuid';
export interface TestUserData {
username: string;
password: string;
email: string;
phone: string;
nickname: string;
}
export class TestDataManager {
private static createdUsers: Set<string> = new Set();
static generateTestUser(overrides?: Partial<TestUserData>): TestUserData {
const uuid = uuidv4().substring(0, 8);
return {
username: `test_${uuid}`,
password: 'Test@123',
email: `test_${uuid}@example.com`,
phone: `138${uuid.substring(0, 8)}`,
nickname: `测试用户_${Date.now()}`,
...overrides
};
}
static trackUser(username: string): void {
this.createdUsers.add(username);
}
static getTrackedUsers(): string[] {
return Array.from(this.createdUsers);
}
static reset(): void {
this.createdUsers.clear();
}
}
- 步骤 4:运行测试验证通过
运行:cd novalon-manage-web && pnpm test shared/__tests__/test-data-manager.test.ts
预期:PASS
- 步骤 5:Commit测试数据管理器
cd novalon-manage-web
git add e2e/role-based-tests/shared/test-data-manager.ts
git add e2e/role-based-tests/shared/__tests__/test-data-manager.test.ts
git commit -m "feat: 实现测试数据管理器
- 实现 generateTestUser 生成测试用户数据
- 实现 trackUser 跟踪创建的用户
- 实现 reset 清理测试数据
- 添加完整的单元测试"
任务 6:实现权限验证工具
目标: 实现权限边界验证工具,验证用户能否访问特定资源。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts - 创建:
novalon-manage-web/e2e/role-based-tests/shared/__tests__/permission-helper.test.ts
- 步骤 1:编写权限验证工具测试
// novalon-manage-web/e2e/role-based-tests/shared/__tests__/permission-helper.test.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../auth-helper';
import { PermissionHelper } from '../permission-helper';
test.describe('PermissionHelper', () => {
test('should verify admin can access user management', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await PermissionHelper.verifyCanAccess(page, '/user-management');
await expect(page).toHaveURL(/user-management/);
});
test('should verify user cannot access user management', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
await PermissionHelper.verifyCannotAccess(page, '/user-management');
});
test('should verify admin can see user management menu', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await PermissionHelper.verifyCanSeeMenu(page, '用户管理');
});
test('should verify user cannot see user management menu', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
await PermissionHelper.verifyCannotSeeMenu(page, '用户管理');
});
});
- 步骤 2:运行测试验证失败
运行:cd novalon-manage-web && pnpm test shared/__tests__/permission-helper.test.ts
预期:FAIL,报错 "Cannot find module '../permission-helper'"
- 步骤 3:实现权限验证工具
// novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';
export class PermissionHelper {
static async verifyCanAccess(page: Page, path: string): Promise<void> {
await page.goto(path);
await expect(page).not.toHaveURL(/.*login/);
const noPermissionElement = page.locator('.no-permission, .forbidden');
await expect(noPermissionElement).not.toBeVisible();
}
static async verifyCannotAccess(page: Page, path: string): Promise<void> {
await page.goto(path);
const isLoginPage = page.url().includes('login');
const hasNoPermission = await page.locator('.no-permission').isVisible();
const hasForbidden = await page.locator('text=/403|Forbidden/').isVisible();
expect(isLoginPage || hasNoPermission || hasForbidden).toBeTruthy();
}
static async verifyCanSeeMenu(page: Page, menuText: string): Promise<void> {
const menuElement = page.locator(`.menu-item:has-text("${menuText}")`);
await expect(menuElement).toBeVisible();
}
static async verifyCannotSeeMenu(page: Page, menuText: string): Promise<void> {
const menuElement = page.locator(`.menu-item:has-text("${menuText}")`);
await expect(menuElement).not.toBeVisible();
}
}
- 步骤 4:运行测试验证通过
运行:cd novalon-manage-web && pnpm test shared/__tests__/permission-helper.test.ts
预期:PASS(需要后端服务运行)
- 步骤 5:Commit权限验证工具
cd novalon-manage-web
git add e2e/role-based-tests/shared/permission-helper.ts
git add e2e/role-based-tests/shared/__tests__/permission-helper.test.ts
git commit -m "feat: 实现权限验证工具
- 实现 verifyCanAccess 验证用户可以访问
- 实现 verifyCannotAccess 验证用户不能访问
- 实现 verifyCanSeeMenu 验证用户可以看到菜单
- 实现 verifyCannotSeeMenu 验证用户看不到菜单
- 添加完整的集成测试"
任务 7:配置环境变量和Playwright配置
目标: 配置测试环境变量和Playwright项目配置,支持角色测试。
文件:
- 创建:
novalon-manage-web/.env.test - 修改:
novalon-manage-web/playwright.config.ts - 修改:
novalon-manage-web/package.json
- 步骤 1:创建测试环境变量文件
# novalon-manage-web/.env.test
VITE_API_BASE_URL=http://localhost:8084
BASE_URL=http://localhost:5173
TEST_TIMEOUT=30000
TEST_RETRIES=2
# 测试用户密码(统一使用Test@123)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=Test@123
USER_USERNAME=normaluser
USER_PASSWORD=Test@123
TEST_USERNAME=e2e_test_user
TEST_PASSWORD=Test@123
- 步骤 2:更新Playwright配置
// novalon-manage-web/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.test' });
export default defineConfig({
testDir: './e2e',
testMatch: [
'**/role-based-tests/**/*.spec.ts',
'**/legacy-tests/**/*.spec.ts'
],
timeout: parseInt(process.env.TEST_TIMEOUT || '30000'),
retries: parseInt(process.env.TEST_RETRIES || '2'),
workers: process.env.CI ? 1 : undefined,
reporter: [
['list'],
['html', { outputFolder: 'test-results/html' }],
['junit', { outputFile: 'test-results/junit.xml' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:5173',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'admin-tests',
testMatch: /admin.*\.spec\.ts/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1920, height: 1080 }
},
},
{
name: 'user-tests',
testMatch: /user.*\.spec\.ts/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1366, height: 768 }
},
},
{
name: 'auth-tests',
testMatch: /authentication.*\.spec\.ts/,
use: {
...devices['Desktop Chrome'],
},
},
{
name: 'permission-tests',
testMatch: /permission.*\.spec\.ts/,
use: {
...devices['Desktop Chrome'],
},
}
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});
- 步骤 3:更新package.json添加测试脚本
// novalon-manage-web/package.json
{
"scripts": {
"test": "playwright test",
"test:admin": "playwright test --project=admin-tests",
"test:user": "playwright test --project=user-tests",
"test:auth": "playwright test --project=auth-tests",
"test:permission": "playwright test --project=permission-tests",
"test:ui": "playwright test --ui",
"test:debug": "playwright test --debug",
"test:report": "playwright show-report"
}
}
- 步骤 4:验证配置
运行:cd novalon-manage-web && pnpm test --list
预期:列出所有测试文件
- 步骤 5:Commit配置文件
cd novalon-manage-web
git add .env.test
git add playwright.config.ts
git add package.json
git commit -m "chore: 配置测试环境和Playwright项目
- 创建 .env.test 测试环境变量
- 更新 playwright.config.ts 添加角色测试项目
- admin-tests: 管理员测试
- user-tests: 普通用户测试
- auth-tests: 认证测试
- permission-tests: 权限测试
- 更新 package.json 添加测试脚本"
任务 8:实现认证场景测试
目标: 实现登录和登出流程测试,验证认证功能。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/scenarios/authentication/login-flow.spec.ts - 创建:
novalon-manage-web/e2e/role-based-tests/scenarios/authentication/logout-flow.spec.ts
- 步骤 1:编写登录流程测试
// novalon-manage-web/e2e/role-based-tests/scenarios/authentication/login-flow.spec.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../../shared/auth-helper';
import { RoleFactory } from '../../roles/role-factory';
test.describe('登录流程测试', () => {
test('管理员使用正确凭证登录成功', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', true);
await expect(page).toHaveURL(/\/(dashboard|\/)/);
const isLoggedIn = await AuthHelper.isLoggedIn(page);
expect(isLoggedIn).toBeTruthy();
});
test('管理员使用错误密码登录失败', async ({ page }) => {
const role = RoleFactory.getRole('admin');
await page.goto('/login');
await page.fill('[name="username"]', role.credentials.username);
await page.fill('[name="password"]', 'wrongpassword');
await page.click('[type="submit"]');
await expect(page).toHaveURL(/.*login/);
await expect(page.locator('.error-message')).toBeVisible();
});
test('普通用户登录成功', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', true);
await expect(page).toHaveURL(/\/(dashboard|\/)/);
const isLoggedIn = await AuthHelper.isLoggedIn(page);
expect(isLoggedIn).toBeTruthy();
});
test('禁用用户登录失败', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="username"]', 'disableduser');
await page.fill('[name="password"]', 'Test@123');
await page.click('[type="submit"]');
await expect(page).toHaveURL(/.*login/);
await expect(page.locator('.error-message')).toBeVisible();
});
});
- 步骤 2:运行登录流程测试
运行:cd novalon-manage-web && pnpm test:auth
预期:PASS(需要后端服务运行)
- 步骤 3:编写登出流程测试
// novalon-manage-web/e2e/role-based-tests/scenarios/authentication/logout-flow.spec.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../../shared/auth-helper';
test.describe('登出流程测试', () => {
test('管理员登出成功', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await AuthHelper.logout(page);
await expect(page).toHaveURL(/.*login/);
const token = await page.evaluate(() => localStorage.getItem('token'));
expect(token).toBeNull();
});
test('普通用户登出成功', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
await AuthHelper.logout(page);
await expect(page).toHaveURL(/.*login/);
const token = await page.evaluate(() => localStorage.getItem('token'));
expect(token).toBeNull();
});
});
- 步骤 4:运行登出流程测试
运行:cd novalon-manage-web && pnpm test:auth
预期:PASS(需要后端服务运行)
- 步骤 5:Commit认证场景测试
cd novalon-manage-web
git add e2e/role-based-tests/scenarios/authentication/
git commit -m "feat: 实现认证场景测试
- 实现 login-flow.spec.ts 登录流程测试
- 管理员正确凭证登录
- 管理员错误密码登录
- 普通用户登录
- 禁用用户登录
- 实现 logout-flow.spec.ts 登出流程测试
- 管理员登出
- 普通用户登出"
任务 9:实现用户管理场景测试
目标: 实现用户管理场景测试,包括创建用户和权限边界验证。
文件:
- 创建:
novalon-manage-web/e2e/role-based-tests/scenarios/user-management/admin-creates-user.spec.ts - 创建:
novalon-manage-web/e2e/role-based-tests/scenarios/user-management/permission-boundary.spec.ts
- 步骤 1:编写管理员创建用户测试
// novalon-manage-web/e2e/role-based-tests/scenarios/user-management/admin-creates-user.spec.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../../shared/auth-helper';
import { TestDataManager } from '../../shared/test-data-manager';
test.describe('管理员创建用户场景', () => {
test.beforeEach(async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
});
test.afterEach(async ({ page }) => {
TestDataManager.reset();
});
test('管理员创建新用户成功', async ({ page }) => {
const testUser = TestDataManager.generateTestUser();
await page.goto('/user-management');
await page.click('button:has-text("新建")');
await page.fill('[name="username"]', testUser.username);
await page.fill('[name="password"]', testUser.password);
await page.fill('[name="email"]', testUser.email);
await page.fill('[name="phone"]', testUser.phone);
await page.fill('[name="nickname"]', testUser.nickname);
await page.click('button[type="submit"]');
await expect(page.locator('.success-message')).toBeVisible();
TestDataManager.trackUser(testUser.username);
});
test('管理员创建用户失败-用户名已存在', async ({ page }) => {
const testUser = TestDataManager.generateTestUser({
username: 'admin'
});
await page.goto('/user-management');
await page.click('button:has-text("新建")');
await page.fill('[name="username"]', testUser.username);
await page.fill('[name="password"]', testUser.password);
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
});
});
- 步骤 2:运行管理员创建用户测试
运行:cd novalon-manage-web && pnpm test:admin
预期:PASS(需要后端服务运行)
- 步骤 3:编写权限边界验证测试
// novalon-manage-web/e2e/role-based-tests/scenarios/user-management/permission-boundary.spec.ts
import { test, expect } from '@playwright/test';
import { AuthHelper } from '../../shared/auth-helper';
import { PermissionHelper } from '../../shared/permission-helper';
test.describe('用户管理权限边界验证', () => {
test('普通用户无法访问用户管理页面', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
await PermissionHelper.verifyCannotAccess(page, '/user-management');
});
test('普通用户无法看到用户管理菜单', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
await PermissionHelper.verifyCannotSeeMenu(page, '用户管理');
});
test('普通用户无法创建用户', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'user', false);
const token = await page.evaluate(() => localStorage.getItem('token'));
const response = await fetch(`${process.env.VITE_API_BASE_URL}/api/users`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'hacker',
password: 'hack123'
})
});
expect(response.status).toBe(403);
});
test('管理员可以访问用户管理页面', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await PermissionHelper.verifyCanAccess(page, '/user-management');
});
test('管理员可以看到用户管理菜单', async ({ page }) => {
await AuthHelper.loginAsRole(page, 'admin', false);
await PermissionHelper.verifyCanSeeMenu(page, '用户管理');
});
});
- 步骤 4:运行权限边界验证测试
运行:cd novalon-manage-web && pnpm test:permission
预期:PASS(需要后端服务运行)
- 步骤 5:Commit用户管理场景测试
cd novalon-manage-web
git add e2e/role-based-tests/scenarios/user-management/
git commit -m "feat: 实现用户管理场景测试
- 实现 admin-creates-user.spec.ts 管理员创建用户测试
- 创建新用户成功
- 创建用户失败-用户名已存在
- 实现 permission-boundary.spec.ts 权限边界验证测试
- 普通用户无法访问用户管理页面
- 普通用户无法看到用户管理菜单
- 普通用户无法创建用户
- 管理员可以访问用户管理页面
- 管理员可以看到用户管理菜单"
任务 10:验证和文档完善
目标: 运行全量测试验证,更新README文档。
文件:
- 修改:
novalon-manage-web/README.md
- 步骤 1:运行全量测试
运行:cd novalon-manage-web && pnpm test
预期:所有测试通过
- 步骤 2:生成测试覆盖率报告
运行:cd novalon-manage-web && pnpm test:report
预期:生成HTML报告
- 步骤 3:更新README文档
在README.md中添加测试框架说明:
## 基于角色的测试框架
### 概述
本系统采用基于角色的用户模拟测试套件,实现真实场景的验收标准。
### 测试结构
e2e/role-based-tests/ ├── roles/ # 角色定义 │ ├── base.role.ts │ ├── admin.role.ts │ ├── user.role.ts │ ├── test.role.ts │ └── role-factory.ts ├── scenarios/ # 业务场景测试 │ ├── authentication/ │ └── user-management/ └── shared/ # 共享工具 ├── auth-helper.ts ├── role-auth-manager.ts ├── test-data-manager.ts └── permission-helper.ts
### 运行测试
```bash
# 运行所有测试
pnpm test
# 运行管理员测试
pnpm test:admin
# 运行普通用户测试
pnpm test:user
# 运行认证测试
pnpm test:auth
# 运行权限测试
pnpm test:permission
# 查看测试报告
pnpm test:report
测试数据
所有测试用户统一使用密码:Test@123
| 用户名 | 角色 | 说明 |
|---|---|---|
| admin | 超级管理员 | 拥有所有权限 |
| normaluser | 普通用户 | 只能访问自己的信息 |
| e2e_test_user | 测试用户 | 用于特定测试场景 |
---
- [ ] **步骤 4:Commit文档更新**
```bash
cd novalon-manage-web
git add README.md
git commit -m "docs: 更新README添加测试框架说明
- 添加测试框架概述
- 添加测试结构说明
- 添加运行测试命令
- 添加测试数据说明"
自检清单
1. 规格覆盖度检查
| 规格需求 | 对应任务 | 状态 |
|---|---|---|
| 修复H2密码不一致问题 | 任务1 | ✅ |
| 创建目录结构 | 任务2 | ✅ |
| 实现角色定义系统 | 任务3 | ✅ |
| 实现认证辅助工具 | 任务4 | ✅ |
| 实现测试数据管理器 | 任务5 | ✅ |
| 实现权限验证工具 | 任务6 | ✅ |
| 配置环境变量和Playwright | 任务7 | ✅ |
| 实现认证场景测试 | 任务8 | ✅ |
| 实现用户管理场景测试 | 任务9 | ✅ |
| 验证和文档完善 | 任务10 | ✅ |
结论:✅ 所有规格需求都有对应任务
2. 占位符扫描
检查结果:
- ✅ 无"待定"、"TODO"等占位符
- ✅ 所有代码步骤都有完整代码
- ✅ 所有测试步骤都有完整测试代码
- ✅ 所有验证步骤都有明确的命令和预期输出
3. 类型一致性检查
检查结果:
- ✅ RoleDefinition接口在所有角色定义中一致使用
- ✅ TestUserData接口在测试数据管理器中一致使用
- ✅ 所有函数签名和参数类型一致
- ✅ 所有导入路径正确
执行交接
计划已完成并准备保存。两种执行方式:
1. 子代理驱动(推荐) - 每个任务调度一个新的子代理,任务间进行审查,快速迭代
2. 内联执行 - 在当前会话中使用 executing-plans 执行任务,批量执行并设有检查点
请选择执行方式。