Files
gym-manage/docs/superpowers/plans/2026-04-04-role-based-test-suite.md
T

48 KiB
Raw Blame History

基于角色的用户模拟测试套件实现计划

面向 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 执行任务,批量执行并设有检查点

请选择执行方式。