1705 lines
48 KiB
Markdown
1705 lines
48 KiB
Markdown
# 基于角色的用户模拟测试套件实现计划
|
||
|
||
> **面向 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<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角色定义系统**
|
||
|
||
```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<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:编写认证辅助类测试**
|
||
|
||
```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<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认证辅助工具**
|
||
|
||
```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<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测试数据管理器**
|
||
|
||
```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<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权限验证工具**
|
||
|
||
```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 执行任务,批量执行并设有检查点
|
||
|
||
**请选择执行方式。**
|