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

48 KiB
Raw Permalink 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


  • 步骤 6Commit密码配置修复
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/

  • 步骤 3Commit目录结构
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


  • 步骤 15Commit角色定义系统
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(需要后端服务运行)


  • 步骤 9Commit认证辅助工具
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


  • 步骤 5Commit测试数据管理器
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(需要后端服务运行)


  • 步骤 5Commit权限验证工具
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

预期:列出所有测试文件


  • 步骤 5Commit配置文件
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(需要后端服务运行)


  • 步骤 5Commit认证场景测试
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(需要后端服务运行)


  • 步骤 5Commit用户管理场景测试
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 测试用户 用于特定测试场景

---

- [ ] **步骤 4Commit文档更新**

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

请选择执行方式。