# 基于角色的用户模拟测试套件实现计划 > **面向 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。 ```java // 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。 ```sql -- 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:验证密码配置一致性** 编写测试验证主应用和测试环境的密码配置一致。 ```java // 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密码配置修复** ```bash 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:创建目录结构** ```bash 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目录结构** ```bash 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:编写角色定义基类测试** ```typescript // 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:实现角色定义基类** ```typescript // 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:编写管理员角色定义测试** ```typescript // 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:实现管理员角色定义** ```typescript // 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:实现普通用户角色定义** ```typescript // 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:实现测试用户角色定义** ```typescript // 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:编写角色工厂测试** ```typescript // 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:实现角色工厂** ```typescript // 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 = 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角色定义系统** ```bash 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管理器测试** ```typescript // 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管理器** ```typescript // 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 = 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 { 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 { 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:编写认证辅助类测试** ```typescript // 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:实现认证辅助类** ```typescript // 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 { 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 { 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 { 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 { await page.evaluate(() => { localStorage.removeItem('token'); localStorage.removeItem('access_token'); }); await page.goto('/login'); } static async isLoggedIn(page: Page): Promise { 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认证辅助工具** ```bash 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:编写测试数据管理器测试** ```typescript // 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:实现测试数据管理器** ```typescript // 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 = new Set(); static generateTestUser(overrides?: Partial): 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测试数据管理器** ```bash 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:编写权限验证工具测试** ```typescript // 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:实现权限验证工具** ```typescript // 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 { 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 { 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 { const menuElement = page.locator(`.menu-item:has-text("${menuText}")`); await expect(menuElement).toBeVisible(); } static async verifyCannotSeeMenu(page: Page, menuText: string): Promise { 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权限验证工具** ```bash 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:创建测试环境变量文件** ```bash # 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配置** ```typescript // 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添加测试脚本** ```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配置文件** ```bash 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:编写登录流程测试** ```typescript // 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:编写登出流程测试** ```typescript // 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认证场景测试** ```bash 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:编写管理员创建用户测试** ```typescript // 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:编写权限边界验证测试** ```typescript // 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用户管理场景测试** ```bash 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中添加测试框架说明: ```markdown ## 基于角色的测试框架 ### 概述 本系统采用基于角色的用户模拟测试套件,实现真实场景的验收标准。 ### 测试结构 ``` 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 执行任务,批量执行并设有检查点 **请选择执行方式。**