develop #2
Vendored
+310
@@ -0,0 +1,310 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
// 项目配置
|
||||
PROJECT_NAME = 'novalon-manage-system'
|
||||
FRONTEND_DIR = 'novalon-manage-web'
|
||||
BACKEND_DIR = 'novalon-manage-api'
|
||||
|
||||
// Node.js 配置
|
||||
NODE_VERSION = '20'
|
||||
PNPM_VERSION = '8.15.0'
|
||||
|
||||
// Java 配置
|
||||
JAVA_VERSION = '17'
|
||||
MAVEN_VERSION = '3.9.0'
|
||||
|
||||
// Docker 配置
|
||||
DOCKER_REGISTRY = credentials('docker-registry')
|
||||
DOCKER_IMAGE_FRONTEND = "${PROJECT_NAME}-frontend"
|
||||
DOCKER_IMAGE_BACKEND = "${PROJECT_NAME}-backend"
|
||||
|
||||
// 数据库配置(用于E2E测试)
|
||||
DB_HOST = 'localhost'
|
||||
DB_PORT = '5432'
|
||||
DB_NAME = 'novalon_test'
|
||||
DB_USER = credentials('db-user')
|
||||
DB_PASSWORD = credentials('db-password')
|
||||
|
||||
// 测试配置
|
||||
TEST_TIMEOUT = '30'
|
||||
RETRY_COUNT = '2'
|
||||
}
|
||||
|
||||
tools {
|
||||
nodejs "NodeJS-${NODE_VERSION}"
|
||||
maven "Maven-${MAVEN_VERSION}"
|
||||
jdk "JDK-${JAVA_VERSION}"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('环境准备') {
|
||||
steps {
|
||||
echo '🔧 准备构建环境...'
|
||||
sh '''
|
||||
# 安装 pnpm
|
||||
npm install -g pnpm@${PNPM_VERSION}
|
||||
|
||||
# 验证工具版本
|
||||
node --version
|
||||
pnpm --version
|
||||
java -version
|
||||
mvn --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('代码检查') {
|
||||
parallel {
|
||||
stage('前端代码检查') {
|
||||
steps {
|
||||
dir(FRONTEND_DIR) {
|
||||
echo '🔍 执行前端代码检查...'
|
||||
sh '''
|
||||
pnpm install
|
||||
pnpm run lint
|
||||
pnpm run type-check
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('后端代码检查') {
|
||||
steps {
|
||||
dir(BACKEND_DIR) {
|
||||
echo '🔍 执行后端代码检查...'
|
||||
sh 'mvn clean compile -DskipTests'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('单元测试') {
|
||||
parallel {
|
||||
stage('前端单元测试') {
|
||||
steps {
|
||||
dir(FRONTEND_DIR) {
|
||||
echo '🧪 执行前端单元测试...'
|
||||
sh 'pnpm run test:unit'
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
dir(FRONTEND_DIR) {
|
||||
// 发布测试报告
|
||||
publishHTML(target: [
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
reportDir: 'coverage',
|
||||
reportFiles: 'index.html',
|
||||
reportName: '前端单元测试覆盖率报告'
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('后端单元测试') {
|
||||
steps {
|
||||
dir(BACKEND_DIR) {
|
||||
echo '🧪 执行后端单元测试...'
|
||||
sh 'mvn test'
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
dir(BACKEND_DIR) {
|
||||
// 发布测试报告
|
||||
junit '**/target/surefire-reports/*.xml'
|
||||
|
||||
// 发布代码覆盖率报告
|
||||
publishHTML(target: [
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
reportDir: 'target/site/jacoco',
|
||||
reportFiles: 'index.html',
|
||||
reportName: '后端单元测试覆盖率报告'
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建') {
|
||||
parallel {
|
||||
stage('前端构建') {
|
||||
steps {
|
||||
dir(FRONTEND_DIR) {
|
||||
echo '📦 构建前端项目...'
|
||||
sh '''
|
||||
pnpm run build:prod
|
||||
|
||||
# 创建构建产物归档
|
||||
tar -czf frontend-dist.tar.gz dist/
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: "${FRONTEND_DIR}/frontend-dist.tar.gz", fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('后端构建') {
|
||||
steps {
|
||||
dir(BACKEND_DIR) {
|
||||
echo '📦 构建后端项目...'
|
||||
sh '''
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 创建构建产物归档
|
||||
tar -czf backend-jars.tar.gz */target/*.jar
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: "${BACKEND_DIR}/backend-jars.tar.gz", fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('E2E测试') {
|
||||
steps {
|
||||
echo '🎭 执行E2E测试...'
|
||||
dir(FRONTEND_DIR) {
|
||||
sh '''
|
||||
# 安装Playwright浏览器
|
||||
pnpm exec playwright install --with-deps chromium
|
||||
|
||||
# 执行E2E测试
|
||||
pnpm run test:e2e:journeys
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
dir(FRONTEND_DIR) {
|
||||
// 发布E2E测试报告
|
||||
publishHTML(target: [
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
reportDir: 'test-results',
|
||||
reportFiles: 'custom-report.html',
|
||||
reportName: 'E2E测试报告'
|
||||
])
|
||||
|
||||
// 归档测试失败截图和视频
|
||||
archiveArtifacts artifacts: 'test-results/**/*.png, test-results/**/*.webm', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建Docker镜像') {
|
||||
when {
|
||||
branch 'develop'
|
||||
}
|
||||
steps {
|
||||
echo '🐳 构建Docker镜像...'
|
||||
|
||||
// 构建前端镜像
|
||||
dir(FRONTEND_DIR) {
|
||||
sh """
|
||||
docker build -t ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:${BUILD_NUMBER} .
|
||||
docker tag ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:${BUILD_NUMBER} ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:latest
|
||||
"""
|
||||
}
|
||||
|
||||
// 构建后端镜像
|
||||
dir(BACKEND_DIR) {
|
||||
sh """
|
||||
docker build -t ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:${BUILD_NUMBER} .
|
||||
docker tag ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:${BUILD_NUMBER} ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:latest
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('推送Docker镜像') {
|
||||
when {
|
||||
branch 'develop'
|
||||
}
|
||||
steps {
|
||||
echo '📤 推送Docker镜像到仓库...'
|
||||
sh """
|
||||
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:${BUILD_NUMBER}
|
||||
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:latest
|
||||
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:${BUILD_NUMBER}
|
||||
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:latest
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
stage('部署到测试环境') {
|
||||
when {
|
||||
branch 'develop'
|
||||
}
|
||||
steps {
|
||||
echo '🚀 部署到测试环境...'
|
||||
sh """
|
||||
# 这里可以添加部署脚本
|
||||
# 例如:使用docker-compose或kubernetes部署
|
||||
|
||||
echo "部署前端镜像: ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:${BUILD_NUMBER}"
|
||||
echo "部署后端镜像: ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:${BUILD_NUMBER}"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
stage('部署到生产环境') {
|
||||
when {
|
||||
branch 'main'
|
||||
}
|
||||
steps {
|
||||
echo '🚀 部署到生产环境...'
|
||||
input message: '确认部署到生产环境?', ok: '确认部署'
|
||||
|
||||
sh """
|
||||
# 这里可以添加生产环境部署脚本
|
||||
# 例如:使用kubernetes进行滚动更新
|
||||
|
||||
echo "部署前端镜像: ${DOCKER_REGISTRY}/${DOCKER_IMAGE_FRONTEND}:${BUILD_NUMBER}"
|
||||
echo "部署后端镜像: ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:${BUILD_NUMBER}"
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
echo '🧹 清理工作空间...'
|
||||
cleanWs()
|
||||
}
|
||||
|
||||
success {
|
||||
echo '✅ 流水线执行成功!'
|
||||
// 可以添加通知,例如发送邮件或Slack消息
|
||||
}
|
||||
|
||||
failure {
|
||||
echo '❌ 流水线执行失败!'
|
||||
// 可以添加失败通知
|
||||
}
|
||||
|
||||
unstable {
|
||||
echo '⚠️ 流水线执行不稳定!'
|
||||
// 可以添加不稳定状态通知
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# PostgreSQL 数据库(用于测试)
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: novalon-test-db
|
||||
environment:
|
||||
POSTGRES_DB: novalon_test
|
||||
POSTGRES_USER: novalon
|
||||
POSTGRES_PASSWORD: novalon123
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_test_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U novalon -d novalon_test"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# Redis 缓存(可选)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: novalon-test-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 后端服务
|
||||
backend:
|
||||
build:
|
||||
context: ./novalon-manage-api
|
||||
dockerfile: Dockerfile
|
||||
container_name: novalon-test-backend
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: test
|
||||
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/novalon_test
|
||||
SPRING_R2DBC_USERNAME: novalon
|
||||
SPRING_R2DBC_PASSWORD: novalon123
|
||||
SPRING_FLYWAY_URL: jdbc:postgresql://postgres:5432/novalon_test
|
||||
SPRING_FLYWAY_USER: novalon
|
||||
SPRING_FLYWAY_PASSWORD: novalon123
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
ports:
|
||||
- "8084:8084"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8084/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 网关服务
|
||||
gateway:
|
||||
build:
|
||||
context: ./novalon-manage-api/manage-gateway
|
||||
dockerfile: Dockerfile
|
||||
container_name: novalon-test-gateway
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: test
|
||||
BACKEND_SERVICE_URL: http://backend:8084
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 前端服务(开发模式)
|
||||
frontend:
|
||||
build:
|
||||
context: ./novalon-manage-web
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: novalon-test-frontend
|
||||
environment:
|
||||
VITE_API_BASE_URL: http://gateway:8080
|
||||
ports:
|
||||
- "3002:3002"
|
||||
volumes:
|
||||
- ./novalon-manage-web:/app
|
||||
- /app/node_modules
|
||||
depends_on:
|
||||
gateway:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
volumes:
|
||||
postgres_test_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
novalon-test-network:
|
||||
driver: bridge
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package cn.novalon.manage.app.config;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||
|
||||
/**
|
||||
* R2DBC数据库初始化配置
|
||||
*
|
||||
* 用于测试环境的H2数据库初始化
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-03
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "test")
|
||||
public class R2dbcInitConfig {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(R2dbcInitConfig.class);
|
||||
|
||||
@Bean
|
||||
public ConnectionFactoryInitializer connectionFactoryInitializer(ConnectionFactory connectionFactory) {
|
||||
logger.info("Initializing R2DBC database with H2 schema and data");
|
||||
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||
populator.addScript(new ClassPathResource("schema-h2.sql"));
|
||||
populator.addScript(new ClassPathResource("data-h2.sql"));
|
||||
|
||||
initializer.setDatabasePopulator(populator);
|
||||
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# H2数据库配置(用于测试环境)
|
||||
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
username: sa
|
||||
password:
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
settings:
|
||||
web-allow-others: true
|
||||
|
||||
flyway:
|
||||
enabled: false
|
||||
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
continue-on-error: false
|
||||
schema-locations: classpath:schema-h2.sql
|
||||
data-locations: classpath:data-h2.sql
|
||||
|
||||
# 测试专用配置
|
||||
test:
|
||||
database:
|
||||
type: h2
|
||||
in-memory: true
|
||||
cleanup:
|
||||
enabled: true
|
||||
strategy: truncate
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
cn.novalon.manage: DEBUG
|
||||
org.springframework.r2dbc: DEBUG
|
||||
org.springframework.jdbc: DEBUG
|
||||
org.flywaydb: INFO
|
||||
com.h2database: WARN
|
||||
@@ -5,28 +5,23 @@ spring:
|
||||
application:
|
||||
name: manage-app
|
||||
r2dbc:
|
||||
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
username: sa
|
||||
password:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
initial-size: 5
|
||||
max-size: 20
|
||||
max-idle-time: 30m
|
||||
max-life-time: 1h
|
||||
acquire-timeout: 5s
|
||||
datasource:
|
||||
url: jdbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
validate-on-migrate: true
|
||||
sql:
|
||||
init:
|
||||
mode: never
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
|
||||
+4
-2
@@ -2,7 +2,8 @@
|
||||
-- 用于测试环境
|
||||
|
||||
-- 插入测试角色
|
||||
INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
MERGE INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by)
|
||||
KEY(id)
|
||||
VALUES
|
||||
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
|
||||
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
@@ -11,7 +12,8 @@ VALUES
|
||||
|
||||
-- 插入测试用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
MERGE INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
KEY(id)
|
||||
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'),
|
||||
@@ -1,253 +0,0 @@
|
||||
-- H2 Database Schema for Integration Testing
|
||||
-- Create user table
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
nickname VARCHAR(100),
|
||||
role_id BIGINT,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create role table
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create user role relation table
|
||||
CREATE TABLE IF NOT EXISTS user_role (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
role_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
|
||||
);
|
||||
|
||||
-- Create menu table
|
||||
CREATE TABLE IF NOT EXISTS sys_menu (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
menu_name VARCHAR(50) NOT NULL,
|
||||
parent_id BIGINT DEFAULT 0,
|
||||
order_num INTEGER DEFAULT 0,
|
||||
path VARCHAR(200),
|
||||
component VARCHAR(200),
|
||||
menu_type VARCHAR(1) DEFAULT 'C',
|
||||
visible VARCHAR(1) DEFAULT '1',
|
||||
status VARCHAR(1) DEFAULT '1',
|
||||
perms VARCHAR(100),
|
||||
icon VARCHAR(100),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create permission table
|
||||
CREATE TABLE IF NOT EXISTS sys_permission (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
permission_name VARCHAR(100) NOT NULL,
|
||||
permission_code VARCHAR(100) NOT NULL UNIQUE,
|
||||
resource VARCHAR(200),
|
||||
action VARCHAR(20),
|
||||
description VARCHAR(500),
|
||||
status INTEGER DEFAULT 1,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create role permission relation table
|
||||
CREATE TABLE IF NOT EXISTS sys_role_permission (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_id BIGINT NOT NULL,
|
||||
permission_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_by VARCHAR(50),
|
||||
CONSTRAINT fk_role_permission_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_role_permission_permission FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_role_permission UNIQUE (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- Create dict type table
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
dict_name VARCHAR(100) NOT NULL,
|
||||
dict_type VARCHAR(100) NOT NULL UNIQUE,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
remark VARCHAR(500),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create dict data table
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
dict_sort INTEGER DEFAULT 0,
|
||||
dict_label VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(100) NOT NULL,
|
||||
dict_type VARCHAR(100) NOT NULL,
|
||||
css_class VARCHAR(100),
|
||||
list_class VARCHAR(100),
|
||||
is_default VARCHAR(1) DEFAULT 'N',
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create dictionary table (general)
|
||||
CREATE TABLE IF NOT EXISTS sys_dictionary (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
type VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(500),
|
||||
remark VARCHAR(500),
|
||||
sort INTEGER DEFAULT 0,
|
||||
create_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create system config table
|
||||
CREATE TABLE IF NOT EXISTS sys_config (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_name VARCHAR(100) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
config_value VARCHAR(500) NOT NULL,
|
||||
config_type VARCHAR(1) DEFAULT 'N',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create login log table
|
||||
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
ip VARCHAR(50),
|
||||
location VARCHAR(255),
|
||||
browser VARCHAR(50),
|
||||
os VARCHAR(50),
|
||||
status VARCHAR(1),
|
||||
message VARCHAR(255),
|
||||
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create exception log table
|
||||
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
title VARCHAR(100),
|
||||
exception_name VARCHAR(100),
|
||||
method_name VARCHAR(255),
|
||||
method_params TEXT,
|
||||
exception_msg TEXT,
|
||||
exception_stack TEXT,
|
||||
ip VARCHAR(50),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create operation log table
|
||||
CREATE TABLE IF NOT EXISTS operation_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
operation VARCHAR(100),
|
||||
method VARCHAR(200),
|
||||
params TEXT,
|
||||
result TEXT,
|
||||
ip VARCHAR(50),
|
||||
duration BIGINT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
error_msg TEXT,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create system notice table
|
||||
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
notice_title VARCHAR(50) NOT NULL,
|
||||
notice_type VARCHAR(1) NOT NULL,
|
||||
notice_content TEXT,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create user message table
|
||||
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
notice_id BIGINT,
|
||||
message_title VARCHAR(255),
|
||||
message_content TEXT,
|
||||
is_read VARCHAR(1) DEFAULT '0',
|
||||
read_time TIMESTAMP,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create file management table
|
||||
CREATE TABLE IF NOT EXISTS sys_file (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
file_size BIGINT,
|
||||
file_type VARCHAR(100),
|
||||
file_extension VARCHAR(10),
|
||||
storage_type VARCHAR(50),
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_menu_parent_id ON sys_menu(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_dict_type ON sys_dict_data(dict_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_login_log_username ON sys_login_log(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_log_username ON operation_log(username);
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package cn.novalon.manage.app.config;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||
|
||||
/**
|
||||
* 测试数据库配置类
|
||||
*
|
||||
* 初始化H2内存数据库schema
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-04-02
|
||||
*/
|
||||
@TestConfiguration
|
||||
public class TestDatabaseConfig {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
initializer.setDatabasePopulator(new ResourceDatabasePopulator(
|
||||
new ClassPathResource("schema-h2.sql"),
|
||||
new ClassPathResource("data-h2.sql")));
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -1,6 +1,5 @@
|
||||
package cn.novalon.manage.app.integration;
|
||||
|
||||
import cn.novalon.manage.app.config.TestDatabaseConfig;
|
||||
import cn.novalon.manage.common.util.StatusConstants;
|
||||
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.manage.sys.core.domain.SysRole;
|
||||
@@ -14,7 +13,6 @@ import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@@ -27,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
/**
|
||||
* 用户服务集成测试
|
||||
*
|
||||
* 使用H2内存数据库进行集成测试
|
||||
* 使用PostgreSQL数据库进行集成测试
|
||||
*
|
||||
* 注意:此测试需要完整的Spring上下文,暂时禁用。
|
||||
* TODO: 优化集成测试配置
|
||||
@@ -38,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@Disabled("暂时禁用:集成测试配置需要优化")
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@Import(TestDatabaseConfig.class)
|
||||
class SysUserServiceIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
username: sa
|
||||
password:
|
||||
url: r2dbc:postgresql://localhost:55432/manage_system
|
||||
username: novalon
|
||||
password: novalon123
|
||||
pool:
|
||||
enabled: true
|
||||
initial-size: 2
|
||||
max-size: 10
|
||||
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
validate-on-migrate: true
|
||||
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
continue-on-error: false
|
||||
schema-locations: classpath:schema-h2.sql
|
||||
data-locations: classpath:data-h2.sql
|
||||
|
||||
flyway:
|
||||
enabled: false
|
||||
mode: never
|
||||
|
||||
security:
|
||||
enabled: false
|
||||
|
||||
+12
-1
@@ -1,6 +1,7 @@
|
||||
package cn.novalon.manage.db.entity;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -11,7 +12,7 @@ import java.time.LocalDateTime;
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
*/
|
||||
public abstract class BaseEntity {
|
||||
public abstract class BaseEntity implements Persistable<Long> {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
@@ -31,6 +32,7 @@ public abstract class BaseEntity {
|
||||
@Column("deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -78,4 +80,13 @@ public abstract class BaseEntity {
|
||||
public void setDeletedAt(LocalDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断实体是否为新的
|
||||
* 如果createdAt为null,则认为是新实体
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return createdAt == null;
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
-- Novalon管理系统普通用户角色和数据
|
||||
-- 版本: V10
|
||||
-- 描述: 创建普通用户角色并分配权限
|
||||
|
||||
-- 插入普通用户角色
|
||||
INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by)
|
||||
VALUES ('普通用户', 'user', 2, 1, 'system', 'system')
|
||||
ON CONFLICT (role_key) DO UPDATE SET
|
||||
role_name = EXCLUDED.role_name,
|
||||
role_sort = EXCLUDED.role_sort,
|
||||
status = EXCLUDED.status;
|
||||
|
||||
-- 为普通用户分配基本权限(查看个人信息、修改密码等)
|
||||
-- 注意:这里只分配基本权限,不包含管理功能权限
|
||||
INSERT INTO sys_permission (permission_name, permission_key, permission_type, parent_id, path, component, icon, sort, status, create_by, update_by)
|
||||
VALUES
|
||||
('个人中心', 'profile', 'MENU', 0, '/profile', 'views/profile/index', 'user', 1, 1, 'system', 'system'),
|
||||
('个人信息', 'profile:info', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 1, 1, 'system', 'system'),
|
||||
('修改密码', 'profile:password', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 2, 1, 'system', 'system')
|
||||
ON CONFLICT (permission_key) DO NOTHING;
|
||||
|
||||
-- 为普通用户角色分配权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by)
|
||||
SELECT
|
||||
r.id as role_id,
|
||||
p.id as permission_id,
|
||||
'system' as create_by,
|
||||
'system' as update_by
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_permission p
|
||||
WHERE r.role_key = 'user'
|
||||
AND p.permission_key IN ('profile', 'profile:info', 'profile:password')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 将测试用户分配给普通用户角色
|
||||
INSERT INTO user_role (user_id, role_id, create_by, update_by)
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
r.id as role_id,
|
||||
'system' as create_by,
|
||||
'system' as update_by
|
||||
FROM sys_user u
|
||||
CROSS JOIN sys_role r
|
||||
WHERE u.username = 'user' AND r.role_key = 'user'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 重置序列值
|
||||
SELECT setval('sys_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role));
|
||||
SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission));
|
||||
SELECT setval('sys_role_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role_permission));
|
||||
SELECT setval('user_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM user_role));
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
-- Novalon管理系统测试数据脚本
|
||||
-- 版本: V11
|
||||
-- 描述: 更新测试用户密码为Test@123,插入E2E测试所需数据
|
||||
|
||||
-- 更新admin用户密码为Test@123
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
UPDATE sys_user
|
||||
SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C'
|
||||
WHERE username = 'admin';
|
||||
|
||||
-- 更新user用户密码为Test@123
|
||||
UPDATE sys_user
|
||||
SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C'
|
||||
WHERE username = 'user';
|
||||
|
||||
-- 插入测试角色(如果不存在)
|
||||
INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by)
|
||||
VALUES
|
||||
('测试管理员', 'test_admin', 2, 1, 'system', 'system'),
|
||||
('普通用户', 'normal_user', 3, 1, 'system', 'system'),
|
||||
('访客', 'guest', 4, 1, 'system', 'system')
|
||||
ON CONFLICT (role_key) DO NOTHING;
|
||||
|
||||
-- 为admin用户分配超级管理员角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
SELECT 1, id, 'system' FROM sys_role WHERE role_key = 'admin'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 为user用户分配普通用户角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
SELECT 2, id, 'system' FROM sys_role WHERE role_key = 'normal_user'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 插入E2E测试专用用户
|
||||
-- BCrypt哈希值对应明文密码: Test@123
|
||||
INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by)
|
||||
VALUES
|
||||
(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system')
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
password = EXCLUDED.password,
|
||||
status = EXCLUDED.status;
|
||||
|
||||
-- 为E2E测试用户分配超级管理员角色
|
||||
INSERT INTO user_role (user_id, role_id, created_by)
|
||||
SELECT 10, id, 'system' FROM sys_role WHERE role_key = 'admin'
|
||||
ON CONFLICT DO NOTHING;
|
||||
+14
-14
@@ -3,7 +3,7 @@
|
||||
-- 描述: 创建所有核心表结构
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100),
|
||||
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS sys_user (
|
||||
);
|
||||
-- 角色表
|
||||
CREATE TABLE IF NOT EXISTS sys_role (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
role_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
role_sort INTEGER DEFAULT 0,
|
||||
@@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS sys_role (
|
||||
);
|
||||
-- 菜单表(统一使用sys_menu表名)
|
||||
CREATE TABLE IF NOT EXISTS sys_menu (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
menu_name VARCHAR(50) NOT NULL,
|
||||
parent_id BIGINT DEFAULT 0,
|
||||
order_num INTEGER DEFAULT 0,
|
||||
@@ -48,7 +48,7 @@ CREATE TABLE IF NOT EXISTS sys_menu (
|
||||
);
|
||||
-- 字典类型表
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
dict_name VARCHAR(100) NOT NULL,
|
||||
dict_type VARCHAR(100) NOT NULL UNIQUE,
|
||||
status VARCHAR(1) DEFAULT '0',
|
||||
@@ -61,7 +61,7 @@ CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||
);
|
||||
-- 字典数据表
|
||||
CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
dict_sort INTEGER DEFAULT 0,
|
||||
dict_label VARCHAR(100) NOT NULL,
|
||||
dict_value VARCHAR(100) NOT NULL,
|
||||
@@ -78,7 +78,7 @@ CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||
);
|
||||
-- 字典表(通用字典)
|
||||
CREATE TABLE IF NOT EXISTS sys_dictionary (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
type VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
@@ -92,7 +92,7 @@ CREATE TABLE IF NOT EXISTS sys_dictionary (
|
||||
);
|
||||
-- 系统配置表
|
||||
CREATE TABLE IF NOT EXISTS sys_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
config_name VARCHAR(100) NOT NULL,
|
||||
config_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
config_value VARCHAR(500) NOT NULL,
|
||||
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS sys_config (
|
||||
);
|
||||
-- 登录日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
ip VARCHAR(50),
|
||||
location VARCHAR(255),
|
||||
@@ -117,7 +117,7 @@ CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||
);
|
||||
-- 异常日志表
|
||||
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
title VARCHAR(100),
|
||||
exception_name VARCHAR(100),
|
||||
@@ -130,7 +130,7 @@ CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||
);
|
||||
-- 操作日志表
|
||||
CREATE TABLE IF NOT EXISTS operation_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
operation VARCHAR(100),
|
||||
method VARCHAR(200),
|
||||
@@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS operation_log (
|
||||
);
|
||||
-- 系统公告表
|
||||
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
notice_title VARCHAR(50) NOT NULL,
|
||||
notice_type VARCHAR(1) NOT NULL,
|
||||
notice_content TEXT,
|
||||
@@ -161,7 +161,7 @@ CREATE TABLE IF NOT EXISTS sys_notice (
|
||||
);
|
||||
-- 用户消息表
|
||||
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
notice_id BIGINT,
|
||||
message_title VARCHAR(255),
|
||||
@@ -176,7 +176,7 @@ CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||
);
|
||||
-- 文件管理表
|
||||
CREATE TABLE IF NOT EXISTS sys_file (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
file_size BIGINT,
|
||||
@@ -191,7 +191,7 @@ CREATE TABLE IF NOT EXISTS sys_file (
|
||||
);
|
||||
-- OAuth2客户端表
|
||||
CREATE TABLE IF NOT EXISTS oauth2_client (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
id BIGINT PRIMARY KEY,
|
||||
client_id VARCHAR(100) NOT NULL UNIQUE,
|
||||
client_secret VARCHAR(255) NOT NULL,
|
||||
client_name VARCHAR(100),
|
||||
|
||||
+11
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.manage.sys.core.domain;
|
||||
|
||||
import cn.novalon.manage.common.util.SnowflakeId;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -64,4 +65,14 @@ public abstract class BaseDomain {
|
||||
public void setDeletedAt(LocalDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
-10
@@ -78,16 +78,6 @@ public class SysPermission extends BaseDomain {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
|
||||
-10
@@ -58,16 +58,6 @@ public class SysRole extends BaseDomain {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
|
||||
-10
@@ -33,14 +33,4 @@ public class SysRolePermission extends BaseDomain {
|
||||
public void setPermissionId(Long permissionId) {
|
||||
this.permissionId = permissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
-10
@@ -101,16 +101,6 @@ public class SysUser extends BaseDomain {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成主键ID
|
||||
*
|
||||
* @return 主键ID
|
||||
*/
|
||||
public Long generateId() {
|
||||
this.id = SnowflakeId.nextId();
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ public class SysRoleService implements ISysRoleService {
|
||||
@Override
|
||||
public Mono<SysRole> createRole(CreateRoleCommand command) {
|
||||
SysRole role = new SysRole();
|
||||
role.generateId();
|
||||
role.setRoleName(command.roleName());
|
||||
role.setRoleKey(command.roleKey());
|
||||
role.setRoleSort(command.roleSort());
|
||||
|
||||
+22
-23
@@ -44,15 +44,15 @@ public class SysUserService implements ISysUserService {
|
||||
private final IUserRoleRepository userRoleRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public SysUserService(ISysUserRepository userRepository,
|
||||
ISysRoleRepository roleRepository,
|
||||
IUserRoleRepository userRoleRepository,
|
||||
@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) {
|
||||
public SysUserService(ISysUserRepository userRepository,
|
||||
ISysRoleRepository roleRepository,
|
||||
IUserRoleRepository userRoleRepository,
|
||||
@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.roleRepository = roleRepository;
|
||||
this.userRoleRepository = userRoleRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
|
||||
|
||||
logger.info("使用的密码编码器类型: {}", passwordEncoder.getClass().getName());
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public class SysUserService implements ISysUserService {
|
||||
logger.info("SysUserService.createUser - 用户名: {}, 密码前缀: {}",
|
||||
user.getUsername(),
|
||||
user.getPassword() != null ? user.getPassword().substring(0, 7) : "null");
|
||||
user.generateId();
|
||||
if (user.getPassword() != null && !user.getPassword().startsWith("$2a$")
|
||||
&& !user.getPassword().startsWith("$2b$")) {
|
||||
logger.info("密码不以$2a$或$2b$开头,重新编码");
|
||||
@@ -106,7 +107,6 @@ public class SysUserService implements ISysUserService {
|
||||
} else {
|
||||
logger.info("密码已编码,跳过重新编码");
|
||||
}
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus(StatusConstants.ENABLED);
|
||||
}
|
||||
@@ -116,6 +116,7 @@ public class SysUserService implements ISysUserService {
|
||||
@Override
|
||||
public Mono<SysUser> createUser(CreateUserCommand command) {
|
||||
SysUser user = new SysUser();
|
||||
user.generateId();
|
||||
user.setUsername(command.username().getValue());
|
||||
user.setPassword(passwordEncoder.encode(command.password().getValue()));
|
||||
user.setEmail(command.email().getValue());
|
||||
@@ -123,7 +124,6 @@ public class SysUserService implements ISysUserService {
|
||||
user.setPhone(command.phone());
|
||||
user.setRoleId(command.roleId());
|
||||
user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class SysUserService implements ISysUserService {
|
||||
@Transactional
|
||||
public Mono<Void> deleteUser(Long id) {
|
||||
logger.debug("开始删除用户,ID: {}", id);
|
||||
|
||||
|
||||
return userRepository.findById(id)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
|
||||
.flatMap(user -> {
|
||||
@@ -244,31 +244,30 @@ public class SysUserService implements ISysUserService {
|
||||
@Transactional
|
||||
public Mono<Void> assignRolesToUser(Long userId, List<Long> roleIds) {
|
||||
logger.debug("开始为用户分配角色,用户ID: {}, 角色IDs: {}", userId, roleIds);
|
||||
|
||||
|
||||
if (roleIds == null || roleIds.isEmpty()) {
|
||||
logger.debug("角色列表为空,删除用户的所有角色关联");
|
||||
return userRoleRepository.deleteByUserId(userId)
|
||||
.doOnSuccess(v -> logger.debug("成功删除用户的所有角色关联"))
|
||||
.doOnError(e -> logger.error("删除用户角色关联失败", e));
|
||||
}
|
||||
|
||||
|
||||
return userRoleRepository.deleteByUserId(userId)
|
||||
.doOnSuccess(v -> logger.debug("成功删除用户的旧角色关联"))
|
||||
.doOnError(e -> logger.error("删除用户旧角色关联失败", e))
|
||||
.then(
|
||||
Flux.fromIterable(roleIds)
|
||||
.concatMap(roleId -> {
|
||||
logger.debug("为用户分配角色ID: {}", roleId);
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(roleId);
|
||||
userRole.setCreatedAt(LocalDateTime.now());
|
||||
return userRoleRepository.save(userRole)
|
||||
.doOnSuccess(v -> logger.debug("成功保存用户角色关联"))
|
||||
.doOnError(e -> logger.error("保存用户角色关联失败", e));
|
||||
})
|
||||
.then()
|
||||
);
|
||||
Flux.fromIterable(roleIds)
|
||||
.concatMap(roleId -> {
|
||||
logger.debug("为用户分配角色ID: {}", roleId);
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(roleId);
|
||||
userRole.setCreatedAt(LocalDateTime.now());
|
||||
return userRoleRepository.save(userRole)
|
||||
.doOnSuccess(v -> logger.debug("成功保存用户角色关联"))
|
||||
.doOnError(e -> logger.error("保存用户角色关联失败", e));
|
||||
})
|
||||
.then());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package cn.novalon.manage.sys.dto.request;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AssignRolesRequest {
|
||||
private List<Long> roleIds;
|
||||
|
||||
public List<Long> getRoleIds() {
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
public void setRoleIds(List<Long> roleIds) {
|
||||
this.roleIds = roleIds;
|
||||
}
|
||||
}
|
||||
+13
@@ -6,6 +6,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户注册请求DTO
|
||||
*
|
||||
@@ -42,6 +44,9 @@ public class UserRegisterRequest {
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "角色ID列表", example = "[1, 2]")
|
||||
private List<Long> roles;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
@@ -81,4 +86,12 @@ public class UserRegisterRequest {
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public List<Long> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(List<Long> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.handler.user;
|
||||
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||
import cn.novalon.manage.common.dto.PageRequest;
|
||||
import cn.novalon.manage.sys.dto.request.AssignRolesRequest;
|
||||
import cn.novalon.manage.sys.dto.request.PasswordChangeRequest;
|
||||
import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
|
||||
import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
|
||||
@@ -135,6 +136,14 @@ public class SysUserHandler {
|
||||
null
|
||||
))
|
||||
.flatMap(userService::createUser)
|
||||
.flatMap(user -> {
|
||||
if (req.getRoles() != null && !req.getRoles().isEmpty()) {
|
||||
logger.info("为用户 {} 分配角色: {}", user.getUsername(), req.getRoles());
|
||||
return userService.assignRolesToUser(user.getId(), req.getRoles())
|
||||
.then(Mono.just(user));
|
||||
}
|
||||
return Mono.just(user);
|
||||
})
|
||||
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
|
||||
});
|
||||
}
|
||||
@@ -249,9 +258,8 @@ public class SysUserHandler {
|
||||
@OperationLog(operation = "分配角色", module = "用户管理")
|
||||
public Mono<ServerResponse> assignRoles(ServerRequest request) {
|
||||
Long id = Long.valueOf(request.pathVariable("id"));
|
||||
return request.bodyToMono(new org.springframework.core.ParameterizedTypeReference<List<Long>>() {
|
||||
})
|
||||
.flatMap(roleIds -> userService.assignRolesToUser(id, roleIds))
|
||||
return request.bodyToMono(AssignRolesRequest.class)
|
||||
.flatMap(req -> userService.assignRolesToUser(id, req.getRoleIds()))
|
||||
.then(ServerResponse.ok().build())
|
||||
.onErrorResume(error -> {
|
||||
logger.error("分配角色失败", error);
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
FROM node:18-alpine AS builder
|
||||
# 构建阶段
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
# 安装 pnpm
|
||||
RUN npm install -g pnpm@8.15.0
|
||||
|
||||
# 复制 package.json 和 lock 文件
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# 安装依赖
|
||||
RUN pnpm install
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 构建生产版本
|
||||
RUN pnpm run build:prod
|
||||
|
||||
# 生产阶段
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
# 复制自定义 nginx 配置
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 复制构建产物
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
# 启动 nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装 pnpm
|
||||
RUN npm install -g pnpm@8.15.0
|
||||
|
||||
# 复制 package.json 和 lock 文件
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# 安装依赖
|
||||
RUN pnpm install
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 3002
|
||||
|
||||
# 启动开发服务器
|
||||
CMD ["pnpm", "run", "dev"]
|
||||
@@ -20,7 +20,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-001: 管理员查看操作日志', async ({ page }) => {
|
||||
await test.step('管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-002: 按关键词搜索操作日志', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到操作日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-003: 导出操作日志', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到操作日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-004: 管理员查看登录日志', async ({ page }) => {
|
||||
await test.step('管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-005: 按IP地址搜索登录日志', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到登录日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-006: 导出登录日志', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到登录日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -164,7 +164,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-008: 验证操作日志时间排序', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到操作日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -177,7 +177,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-009: 验证登录日志状态显示', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到登录日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
@@ -189,7 +189,7 @@ test.describe('审计功能 E2E 测试', () => {
|
||||
test('AUDIT-010: 验证审计日志数据完整性', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到操作日志', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await loginPage.login('admin', 'Test@123');
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ setup('authenticate', async ({ page }) => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.locator('input[placeholder*="用户名"]').fill('admin');
|
||||
await page.locator('input[placeholder*="密码"]').fill('admin123');
|
||||
await page.locator('input[placeholder*="密码"]').fill('Test@123');
|
||||
await page.locator('button:has-text("登录")').click();
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: 30000 });
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
|
||||
test.describe('用户认证 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
await loginPage.goto();
|
||||
});
|
||||
|
||||
test('成功登录流程', async ({ page }) => {
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
|
||||
await loginPage.login('e2e_test_user', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain('e2e_test_user');
|
||||
});
|
||||
|
||||
test('登录失败 - 无效凭证', async ({ page }) => {
|
||||
await loginPage.login('invalid', 'invalid');
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page).not.toHaveURL(/.*dashboard/);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/login');
|
||||
});
|
||||
|
||||
test('登录失败 - 缺少必填字段', async ({ page }) => {
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
expect(errorMessage).toBeTruthy();
|
||||
});
|
||||
|
||||
test('登出流程', async ({ page }) => {
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await loginPage.logout();
|
||||
|
||||
await expect(page).toHaveURL(/.*login/);
|
||||
await expect(page).toHaveTitle(/登录/);
|
||||
});
|
||||
|
||||
test('登录后可以访问主要菜单', async ({ page }) => {
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await expect(page).toHaveURL(/.*roles/);
|
||||
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await expect(page).toHaveURL(/.*menus/);
|
||||
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await expect(page).toHaveURL(/.*sysconfig/);
|
||||
});
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('基础功能测试', () => {
|
||||
test('后端健康检查', async ({ request }) => {
|
||||
const response = await request.get('http://localhost:8084/actuator/health');
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
const health = await response.json();
|
||||
expect(health.status).toBe('UP');
|
||||
});
|
||||
|
||||
test('前端首页加载', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/.*login.*/);
|
||||
});
|
||||
|
||||
test('登录页面可访问', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h2')).toContainText('登录');
|
||||
await expect(page.locator('input[placeholder*="用户名"]')).toBeVisible();
|
||||
await expect(page.locator('input[placeholder*="密码"]')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,270 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
|
||||
test.describe('完整业务流程 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
});
|
||||
|
||||
test('完整用户管理流程:登录 -> 创建角色 -> 创建用户 -> 分配角色 -> 删除', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建新角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `测试角色_${timestamp}`,
|
||||
roleKey: `test_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: `测试角色备注_${timestamp}`,
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
await expect(roleManagementPage.table).toContainText(roleData.roleName);
|
||||
});
|
||||
|
||||
await test.step('3. 为角色分配权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.selectPermission('user:create');
|
||||
await roleManagementPage.selectPermission('user:edit');
|
||||
await roleManagementPage.savePermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 创建新用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `testuser_${timestamp}`,
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await expect(userManagementPage.table).toContainText(userData.username);
|
||||
});
|
||||
|
||||
await test.step('5. 为用户分配角色', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.editUser(1);
|
||||
await page.click('.role-select');
|
||||
await page.click('option:has-text("测试角色")');
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 验证用户登录', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`testuser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain(`testuser_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('7. 管理员删除测试用户', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.search(`testuser_${timestamp}`);
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('8. 管理员删除测试角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.search(`测试角色_${timestamp}`);
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整菜单管理流程:创建菜单 -> 构建菜单树 -> 删除菜单', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建父级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('text=新增菜单');
|
||||
|
||||
await page.fill('input[name="menuName"]', `父级菜单_${timestamp}`);
|
||||
await page.fill('input[name="parentId"]', '0');
|
||||
await page.fill('input[name="orderNum"]', '1');
|
||||
await page.selectOption('select[name="menuType"]', 'M');
|
||||
await page.fill('input[name="component"]', `parent_${timestamp}`);
|
||||
await page.fill('input[name="perms"]', `parent:view_${timestamp}`);
|
||||
await page.selectOption('select[name="status"]', '1');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 创建子级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('text=新增菜单');
|
||||
|
||||
await page.fill('input[name="menuName"]', `子级菜单_${timestamp}`);
|
||||
await page.fill('input[name="parentId"]', '1');
|
||||
await page.fill('input[name="orderNum"]', '1');
|
||||
await page.selectOption('select[name="menuType"]', 'C');
|
||||
await page.fill('input[name="component"]', `child_${timestamp}`);
|
||||
await page.fill('input[name="perms"]', `child:view_${timestamp}`);
|
||||
await page.selectOption('select[name="status"]', '1');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 验证菜单树结构', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await expect(page.locator('table')).toContainText(`父级菜单_${timestamp}`);
|
||||
await expect(page.locator('table')).toContainText(`子级菜单_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('5. 删除子级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('table tbody tr:has-text("子级菜单") .delete-button');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 删除父级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.click('table tbody tr:has-text("父级菜单") .delete-button');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整系统配置流程:修改配置 -> 验证配置 -> 恢复默认', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 修改系统配置', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await page.fill('input[name="configValue"]', `test_value_${timestamp}`);
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 验证配置修改', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await expect(page.locator('table')).toContainText(`test_value_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('4. 恢复默认配置', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await page.click('table tbody tr:first-child .edit-button');
|
||||
await page.fill('input[name="configValue"]', 'default_value');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success-message')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('完整权限控制流程:创建受限角色 -> 创建用户 -> 验证权限限制', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建受限角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `受限角色_${timestamp}`,
|
||||
roleKey: `limited_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: '仅查看权限',
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 为受限角色分配仅查看权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.savePermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 创建受限用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `limiteduser_${timestamp}`,
|
||||
email: `limited_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 验证受限用户权限', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`limiteduser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
|
||||
await page.goto('/users/create');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,773 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
import { MenuManagementPage } from './pages/MenuManagementPage';
|
||||
import { SystemConfigPage } from './pages/SystemConfigPage';
|
||||
import { FileManagementPage } from './pages/FileManagementPage';
|
||||
import { OperationLogPage } from './pages/OperationLogPage';
|
||||
import { NotificationPage } from './pages/NotificationPage';
|
||||
import { DictionaryManagementPage } from './pages/DictionaryManagementPage';
|
||||
|
||||
test.describe('E2E完整业务流程测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
let menuManagementPage: MenuManagementPage;
|
||||
let systemConfigPage: SystemConfigPage;
|
||||
let fileManagementPage: FileManagementPage;
|
||||
let operationLogPage: OperationLogPage;
|
||||
let notificationPage: NotificationPage;
|
||||
let dictionaryManagementPage: DictionaryManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
menuManagementPage = new MenuManagementPage(page);
|
||||
systemConfigPage = new SystemConfigPage(page);
|
||||
fileManagementPage = new FileManagementPage(page);
|
||||
operationLogPage = new OperationLogPage(page);
|
||||
notificationPage = new NotificationPage(page);
|
||||
dictionaryManagementPage = new DictionaryManagementPage(page);
|
||||
});
|
||||
|
||||
test('E2E-001: 用户完整生命周期流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建新角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `测试角色_${timestamp}`,
|
||||
roleKey: `test_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: 'ACTIVE',
|
||||
remark: `测试角色备注_${timestamp}`,
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 为角色分配权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('user:view');
|
||||
await roleManagementPage.selectPermission('user:create');
|
||||
await roleManagementPage.selectPermission('user:edit');
|
||||
await roleManagementPage.selectPermission('user:delete');
|
||||
await roleManagementPage.savePermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 创建新用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `testuser_${timestamp}`,
|
||||
nickname: `测试用户${timestamp}`,
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 为用户分配角色', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.editUser(1);
|
||||
await page.click('.role-select');
|
||||
await page.click('option:has-text("测试角色")');
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 用户登录验证', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`testuser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain(`testuser_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('7. 修改用户信息', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.editUser(1);
|
||||
const dialog = page.locator('.el-dialog');
|
||||
const nicknameInput = dialog.locator('.el-form-item').filter({ hasText: '昵称' }).locator('input');
|
||||
await nicknameInput.fill(`更新用户_${timestamp}`);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('8. 禁用用户', async () => {
|
||||
await userManagementPage.clickStatusButton(1);
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('9. 启用用户', async () => {
|
||||
await userManagementPage.clickStatusButton(1);
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('10. 删除用户', async () => {
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('11. 删除角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-002: 角色权限分配完整流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建新角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const roleData = {
|
||||
roleName: `UAT角色_${timestamp}`,
|
||||
roleKey: `uat_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: 'UAT测试角色',
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 为角色分配菜单权限', async () => {
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('system:user:view');
|
||||
await roleManagementPage.selectPermission('system:user:add');
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 为角色分配API权限', async () => {
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('api:user:list');
|
||||
await roleManagementPage.selectPermission('api:user:create');
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 创建新用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const userData = {
|
||||
username: `uatuser_${timestamp}`,
|
||||
nickname: `UAT用户${timestamp}`,
|
||||
email: `uat_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 为用户分配角色', async () => {
|
||||
await userManagementPage.editUser(1);
|
||||
await page.click('.role-select');
|
||||
await page.click('option:has-text("UAT角色")');
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 用户登录验证权限', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`uatuser_${timestamp}`, 'Test123!@#');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
|
||||
await page.goto('/users/create');
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
});
|
||||
|
||||
await test.step('8. 撤销角色权限', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.deselectPermission('system:user:add');
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('9. 删除角色', async () => {
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-003: 菜单树构建与权限控制流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建父级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
|
||||
const menuData = {
|
||||
menuName: `父级菜单_${timestamp}`,
|
||||
parentId: '0',
|
||||
orderNum: '1',
|
||||
menuType: 'M',
|
||||
component: `parent_${timestamp}`,
|
||||
perms: `parent:view_${timestamp}`,
|
||||
status: '1',
|
||||
};
|
||||
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 创建子级菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
|
||||
const menuData = {
|
||||
menuName: `子级菜单_${timestamp}`,
|
||||
parentId: '1',
|
||||
orderNum: '1',
|
||||
menuType: 'C',
|
||||
component: `child_${timestamp}`,
|
||||
perms: `child:view_${timestamp}`,
|
||||
status: '1',
|
||||
};
|
||||
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 配置菜单权限', async () => {
|
||||
await menuManagementPage.editMenu(1);
|
||||
await menuManagementPage.selectPermission('menu:view');
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 验证菜单树显示', async () => {
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).toContainText(`父级菜单_${timestamp}`);
|
||||
await expect(page.locator('table')).toContainText(`子级菜单_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('6. 为角色分配菜单权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission(`parent:view_${timestamp}`);
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 用户登录验证菜单访问', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
await expect(page.locator('.menu-item')).toContainText(`父级菜单_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('8. 删除子级菜单', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await menuManagementPage.deleteMenu(2);
|
||||
await menuManagementPage.confirmDelete();
|
||||
await expect(menuManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('9. 删除父级菜单', async () => {
|
||||
await menuManagementPage.deleteMenu(1);
|
||||
await menuManagementPage.confirmDelete();
|
||||
await expect(menuManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-004: 系统配置管理流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 查看当前配置', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
await expect(systemConfigPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 修改配置值', async () => {
|
||||
await systemConfigPage.editConfig(1);
|
||||
await page.fill('input[name="configValue"]', `test_value_${timestamp}`);
|
||||
await systemConfigPage.submitForm();
|
||||
await expect(systemConfigPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 验证配置生效', async () => {
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).toContainText(`test_value_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('5. 刷新配置缓存', async () => {
|
||||
await systemConfigPage.refreshCache();
|
||||
await expect(systemConfigPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 恢复默认配置', async () => {
|
||||
await systemConfigPage.editConfig(1);
|
||||
await page.fill('input[name="configValue"]', 'default_value');
|
||||
await systemConfigPage.submitForm();
|
||||
await expect(systemConfigPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 批量修改配置', async () => {
|
||||
await systemConfigPage.editConfig(2);
|
||||
await page.fill('input[name="configValue"]', `batch_value_${timestamp}`);
|
||||
await systemConfigPage.submitForm();
|
||||
await expect(systemConfigPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-005: 文件管理完整流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 上传文件', async () => {
|
||||
await dashboardPage.navigateToFileManagement();
|
||||
await fileManagementPage.clickUploadFile();
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles('./e2e/fixtures/test-file.txt');
|
||||
await fileManagementPage.submitUpload();
|
||||
await expect(fileManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 验证文件信息', async () => {
|
||||
await expect(page.locator('table')).toContainText('test-file.txt');
|
||||
});
|
||||
|
||||
await test.step('4. 预览文件', async () => {
|
||||
await fileManagementPage.previewFile(1);
|
||||
await expect(page.locator('.file-preview')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 下载文件', async () => {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await fileManagementPage.downloadFile(1);
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe('test-file.txt');
|
||||
});
|
||||
|
||||
await test.step('6. 设置文件权限', async () => {
|
||||
await fileManagementPage.editFile(1);
|
||||
await page.selectOption('select[name="permission"]', 'private');
|
||||
await fileManagementPage.submitForm();
|
||||
await expect(fileManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 删除文件', async () => {
|
||||
await fileManagementPage.deleteFile(1);
|
||||
await fileManagementPage.confirmDelete();
|
||||
await expect(fileManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-006: 审计日志记录与查询流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 执行各种操作', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('3. 查看操作日志', async () => {
|
||||
await dashboardPage.navigateToOperationLog();
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
await expect(page.locator('table')).toContainText('用户管理');
|
||||
});
|
||||
|
||||
await test.step('4. 查看登录日志', async () => {
|
||||
await operationLogPage.switchToLoginLog();
|
||||
await expect(page.locator('table')).toContainText('admin');
|
||||
});
|
||||
|
||||
await test.step('5. 查看异常日志', async () => {
|
||||
await operationLogPage.switchToExceptionLog();
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 搜索日志', async () => {
|
||||
await operationLogPage.search('用户管理');
|
||||
await page.waitForTimeout(2000);
|
||||
await expect(page.locator('table')).toContainText('用户管理');
|
||||
});
|
||||
|
||||
await test.step('7. 导出日志', async () => {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await operationLogPage.exportLogs();
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toMatch(/logs.*\.xlsx/);
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-007: 通知发布与推送流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 发布系统通知', async () => {
|
||||
await dashboardPage.navigateToNotification();
|
||||
await notificationPage.clickCreateNotification();
|
||||
|
||||
const notificationData = {
|
||||
title: `系统通知_${timestamp}`,
|
||||
content: `这是一条测试通知内容_${timestamp}`,
|
||||
type: 'system',
|
||||
status: '1',
|
||||
};
|
||||
|
||||
await notificationPage.fillNotificationForm(notificationData);
|
||||
await notificationPage.submitForm();
|
||||
await expect(notificationPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 发布用户消息', async () => {
|
||||
await notificationPage.clickCreateNotification();
|
||||
|
||||
const notificationData = {
|
||||
title: `用户消息_${timestamp}`,
|
||||
content: `这是一条测试用户消息_${timestamp}`,
|
||||
type: 'user',
|
||||
status: '1',
|
||||
};
|
||||
|
||||
await notificationPage.fillNotificationForm(notificationData);
|
||||
await notificationPage.submitForm();
|
||||
await expect(notificationPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 推送实时消息', async () => {
|
||||
await notificationPage.pushRealTimeMessage(1);
|
||||
await expect(notificationPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 用户查看通知', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
await expect(page.locator('.notification-badge')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 标记通知已读', async () => {
|
||||
await dashboardPage.navigateToNotification();
|
||||
await notificationPage.markAsRead(1);
|
||||
await expect(notificationPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 删除通知', async () => {
|
||||
await notificationPage.deleteNotification(1);
|
||||
await notificationPage.confirmDelete();
|
||||
await expect(notificationPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-008: 字典数据管理流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建字典类型', async () => {
|
||||
await dashboardPage.navigateToDictionary();
|
||||
await dictionaryManagementPage.clickCreateDictType();
|
||||
|
||||
const dictTypeData = {
|
||||
dictName: `测试字典_${timestamp}`,
|
||||
dictType: `test_dict_${timestamp}`,
|
||||
status: '1',
|
||||
remark: `测试字典类型_${timestamp}`,
|
||||
};
|
||||
|
||||
await dictionaryManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictionaryManagementPage.submitForm();
|
||||
await expect(dictionaryManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 添加字典数据', async () => {
|
||||
await dictionaryManagementPage.clickCreateDictData();
|
||||
|
||||
const dictData = {
|
||||
dictLabel: `测试数据1_${timestamp}`,
|
||||
dictValue: `value1_${timestamp}`,
|
||||
dictSort: '1',
|
||||
status: '1',
|
||||
};
|
||||
|
||||
await dictionaryManagementPage.fillDictDataForm(dictData);
|
||||
await dictionaryManagementPage.submitForm();
|
||||
await expect(dictionaryManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 修改字典数据', async () => {
|
||||
await dictionaryManagementPage.editDictData(1);
|
||||
await page.fill('input[name="dictLabel"]', `更新数据_${timestamp}`);
|
||||
await dictionaryManagementPage.submitForm();
|
||||
await expect(dictionaryManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 查询字典数据', async () => {
|
||||
await dictionaryManagementPage.search(`更新数据_${timestamp}`);
|
||||
await page.waitForTimeout(2000);
|
||||
await expect(page.locator('table')).toContainText(`更新数据_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('6. 删除字典数据', async () => {
|
||||
await dictionaryManagementPage.deleteDictData(1);
|
||||
await dictionaryManagementPage.confirmDelete();
|
||||
await expect(dictionaryManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 删除字典类型', async () => {
|
||||
await dictionaryManagementPage.deleteDictType(1);
|
||||
await dictionaryManagementPage.confirmDelete();
|
||||
await expect(dictionaryManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-009: 多用户并发操作流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 创建测试用户', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
await userManagementPage.clickCreateUser();
|
||||
const userData = {
|
||||
username: `concurrent_user_${i}_${timestamp}`,
|
||||
nickname: `并发用户${i}_${timestamp}`,
|
||||
email: `concurrent_${i}_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('2. 用户A创建数据', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`concurrent_user_1_${timestamp}`, 'Test123!@#');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
const userData = {
|
||||
username: `user_a_data_${timestamp}`,
|
||||
nickname: `用户A数据_${timestamp}`,
|
||||
email: `user_a_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 用户B同时创建数据', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login(`concurrent_user_2_${timestamp}`, 'Test123!@#');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
const userData = {
|
||||
username: `user_b_data_${timestamp}`,
|
||||
nickname: `用户B数据_${timestamp}`,
|
||||
email: `user_b_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 验证数据一致性', async () => {
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).toContainText(`user_a_data_${timestamp}`);
|
||||
await expect(page.locator('table')).toContainText(`user_b_data_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('5. 清理测试数据', async () => {
|
||||
await loginPage.logout();
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await userManagementPage.search(`concurrent_user_1_${timestamp}`);
|
||||
await page.waitForTimeout(1000);
|
||||
const rows = await page.locator('table tbody tr').count();
|
||||
if (rows > 0) {
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
}
|
||||
|
||||
await userManagementPage.search(`concurrent_user_2_${timestamp}`);
|
||||
await page.waitForTimeout(1000);
|
||||
const rows2 = await page.locator('table tbody tr').count();
|
||||
if (rows2 > 0) {
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('E2E-010: 系统异常恢复流程', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建测试数据', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
const userData = {
|
||||
username: `recovery_test_${timestamp}`,
|
||||
nickname: `恢复测试用户_${timestamp}`,
|
||||
email: `recovery_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 记录数据状态', async () => {
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).toContainText(`recovery_test_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('4. 模拟网络中断', async () => {
|
||||
await page.context().setOffline(true);
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
await test.step('5. 恢复网络连接', async () => {
|
||||
await page.context().setOffline(false);
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
await test.step('6. 验证数据完整性', async () => {
|
||||
await page.reload();
|
||||
await expect(page.locator('table')).toContainText(`recovery_test_${timestamp}`);
|
||||
});
|
||||
|
||||
await test.step('7. 验证会话恢复', async () => {
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const username = await dashboardPage.getUsername();
|
||||
expect(username).toContain('admin');
|
||||
});
|
||||
|
||||
await test.step('8. 验证操作继续', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await expect(page).toHaveURL(/.*users/);
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('9. 清理测试数据', async () => {
|
||||
await userManagementPage.search(`recovery_test_${timestamp}`);
|
||||
await page.waitForTimeout(1000);
|
||||
const rows = await page.locator('table tbody tr').count();
|
||||
if (rows > 0) {
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
|
||||
test.describe('关键业务流程E2E测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
loginPage = new LoginPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
});
|
||||
|
||||
test('1. 用户登录流程', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
await expect(page.locator('.dashboard')).toBeVisible();
|
||||
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'));
|
||||
expect(token).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2. 用户创建流程', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
|
||||
const uuid = Math.random().toString(36).substring(2, 15);
|
||||
const username = `user_${uuid}`;
|
||||
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: username,
|
||||
password: 'Test@123',
|
||||
email: `${username}@test.com`,
|
||||
phone: '13800138000',
|
||||
nickname: `测试用户${Date.now()}`
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3. 管理员权限验证', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
await expect(userManagementPage.table).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const userCount = await userManagementPage.getUserCount();
|
||||
expect(userCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('4. 未登录用户访问受保护页面', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await page.waitForURL(/\/login/, { timeout: 10000 });
|
||||
await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('5. 登出流程', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const avatar = page.locator('.el-avatar');
|
||||
await avatar.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const logoutButton = page.locator('.el-dropdown-menu').getByText('退出登录');
|
||||
await logoutButton.click();
|
||||
|
||||
await page.waitForURL(/\/login/, { timeout: 10000 });
|
||||
await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,185 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
|
||||
test.describe('Dashboard操作日志显示验证', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('Dashboard应显示操作日志统计卡片', async ({ page }) => {
|
||||
await test.step('验证操作日志统计卡片存在', async () => {
|
||||
const operationLogCard = page.locator('.stat-card.log-card');
|
||||
await expect(operationLogCard).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证操作日志统计标题', async () => {
|
||||
const title = page.locator('.stat-card.log-card .el-statistic__head');
|
||||
await expect(title).toContainText('操作日志');
|
||||
});
|
||||
|
||||
await test.step('验证操作日志统计数值', async () => {
|
||||
const value = page.locator('.stat-card.log-card .el-statistic__number');
|
||||
await expect(value).toBeVisible();
|
||||
const countText = await value.textContent();
|
||||
expect(countText).not.toBeNull();
|
||||
const count = parseInt(countText!);
|
||||
expect(count).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard应显示其他统计卡片', async ({ page }) => {
|
||||
await test.step('验证用户总数卡片', async () => {
|
||||
const userCard = page.locator('.stat-card.user-card');
|
||||
await expect(userCard).toBeVisible();
|
||||
const title = userCard.locator('.el-statistic__head');
|
||||
await expect(title).toContainText('用户总数');
|
||||
});
|
||||
|
||||
await test.step('验证角色总数卡片', async () => {
|
||||
const roleCard = page.locator('.stat-card.role-card');
|
||||
await expect(roleCard).toBeVisible();
|
||||
const title = roleCard.locator('.el-statistic__head');
|
||||
await expect(title).toContainText('角色总数');
|
||||
});
|
||||
|
||||
await test.step('验证今日登录卡片', async () => {
|
||||
const loginCard = page.locator('.stat-card.login-card');
|
||||
await expect(loginCard).toBeVisible();
|
||||
const title = loginCard.locator('.el-statistic__head');
|
||||
await expect(title).toContainText('今日登录');
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard统计卡片应显示图标', async ({ page }) => {
|
||||
await test.step('验证操作日志图标', async () => {
|
||||
const icon = page.locator('.stat-card.log-card .stat-icon');
|
||||
await expect(icon).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证用户图标', async () => {
|
||||
const icon = page.locator('.stat-card.user-card .stat-icon');
|
||||
await expect(icon).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证角色图标', async () => {
|
||||
const icon = page.locator('.stat-card.role-card .stat-icon');
|
||||
await expect(icon).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证登录图标', async () => {
|
||||
const icon = page.locator('.stat-card.login-card .stat-icon');
|
||||
await expect(icon).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard统计卡片应有悬停效果', async ({ page }) => {
|
||||
await test.step('验证操作日志卡片悬停效果', async () => {
|
||||
const card = page.locator('.stat-card.log-card');
|
||||
await card.hover();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(card).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard应显示最近登录记录', async ({ page }) => {
|
||||
await test.step('验证最近登录卡片存在', async () => {
|
||||
const recentLoginCard = page.locator('.recent-login-card');
|
||||
await expect(recentLoginCard).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证最近登录标题', async () => {
|
||||
const title = page.locator('.recent-login-card .card-title');
|
||||
await expect(title).toContainText('最近登录');
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard应显示系统信息', async ({ page }) => {
|
||||
await test.step('验证系统信息卡片存在', async () => {
|
||||
const systemInfoCard = page.locator('.system-info-card');
|
||||
await expect(systemInfoCard).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证系统信息标题', async () => {
|
||||
const title = page.locator('.system-info-card .card-title');
|
||||
await expect(title).toContainText('系统信息');
|
||||
});
|
||||
|
||||
await test.step('验证系统版本显示', async () => {
|
||||
const versionItem = page.locator('.system-info-card').getByText('系统版本');
|
||||
await expect(versionItem).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证Java版本显示', async () => {
|
||||
const javaItem = page.locator('.system-info-card').getByText('Java版本');
|
||||
await expect(javaItem).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证前端框架显示', async () => {
|
||||
const frontendItem = page.locator('.system-info-card').getByText('前端框架');
|
||||
await expect(frontendItem).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证数据库显示', async () => {
|
||||
const dbItem = page.locator('.system-info-card').getByText('数据库');
|
||||
await expect(dbItem).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard操作日志统计应正确反映实际数据', async ({ page }) => {
|
||||
await test.step('获取Dashboard显示的操作日志数量', async () => {
|
||||
const value = page.locator('.stat-card.log-card .el-statistic__number');
|
||||
await expect(value).toBeVisible();
|
||||
const countText = await value.textContent();
|
||||
expect(countText).not.toBeNull();
|
||||
const dashboardCount = parseInt(countText!);
|
||||
expect(dashboardCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard页面加载性能', async ({ page }) => {
|
||||
await test.step('验证页面加载时间', async () => {
|
||||
const startTime = Date.now();
|
||||
await dashboardPage.goto();
|
||||
const loadTime = Date.now() - startTime;
|
||||
expect(loadTime).toBeLessThan(10000);
|
||||
});
|
||||
|
||||
await test.step('验证统计卡片加载', async () => {
|
||||
const cards = page.locator('.stat-card');
|
||||
await expect(cards.first()).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test('Dashboard响应式布局验证', async ({ page }) => {
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
const cards = page.locator('.stat-card');
|
||||
expect(await cards.count()).toBe(4);
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
const cards = page.locator('.stat-card');
|
||||
expect(await cards.count()).toBe(4);
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
const cards = page.locator('.stat-card');
|
||||
expect(await cards.count()).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,481 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DictionaryManagementPage } from './pages/DictionaryManagementPage';
|
||||
|
||||
test.describe('字典管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dictManagementPage: DictionaryManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dictManagementPage = new DictionaryManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test('DICT-001: 访问字典管理页面', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
await expect(page).toHaveURL(/.*dict/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(dictManagementPage.table).toBeVisible();
|
||||
await expect(dictManagementPage.createDictTypeButton).toBeVisible();
|
||||
await expect(dictManagementPage.searchInput).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-002: 创建字典类型', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('点击新增字典类型按钮', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
});
|
||||
|
||||
await test.step('填写字典类型信息', async () => {
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `测试字典类型_${timestamp}`,
|
||||
dictType: `test_dict_type_${timestamp}`,
|
||||
status: '1',
|
||||
remark: '这是一个测试字典类型'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
});
|
||||
|
||||
await test.step('提交表单', async () => {
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证字典类型创建成功', async () => {
|
||||
await dictManagementPage.reload();
|
||||
const dictTypeCount = await dictManagementPage.getDictTypeCount();
|
||||
expect(dictTypeCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-003: 编辑字典类型', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建测试字典类型', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `待编辑字典_${timestamp}`,
|
||||
dictType: `edit_dict_${timestamp}`,
|
||||
status: '1'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('编辑字典类型', async () => {
|
||||
const timestamp = Date.now();
|
||||
await dictManagementPage.editDictType(`待编辑字典_${timestamp}`);
|
||||
await page.waitForTimeout(500);
|
||||
const updateData = {
|
||||
dictName: `已编辑字典_${timestamp}`,
|
||||
remark: '这是更新后的备注'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(updateData);
|
||||
});
|
||||
|
||||
await test.step('提交修改', async () => {
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-004: 删除字典类型', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建测试字典类型', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `待删除字典_${timestamp}`,
|
||||
dictType: `delete_dict_${timestamp}`,
|
||||
status: '1'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('删除字典类型', async () => {
|
||||
const timestamp = Date.now();
|
||||
await dictManagementPage.deleteDictType(`待删除字典_${timestamp}`);
|
||||
await dictManagementPage.confirmDelete();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证字典类型已删除', async () => {
|
||||
await dictManagementPage.reload();
|
||||
const timestamp = Date.now();
|
||||
const dictDeleted = await dictManagementPage.containsText(`待删除字典_${timestamp}`);
|
||||
expect(dictDeleted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-005: 创建字典数据', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('点击新增字典数据按钮', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
});
|
||||
|
||||
await test.step('填写字典数据信息', async () => {
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `测试字典标签_${timestamp}`,
|
||||
dictValue: `test_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
cssClass: 'el-tag-success',
|
||||
listClass: 'default',
|
||||
isDefault: 'Y',
|
||||
status: '1',
|
||||
sort: 1
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
});
|
||||
|
||||
await test.step('提交表单', async () => {
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证字典数据创建成功', async () => {
|
||||
await dictManagementPage.reload();
|
||||
const dictDataCount = await dictManagementPage.getDictDataCount();
|
||||
expect(dictDataCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-006: 编辑字典数据', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建测试字典数据', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `待编辑标签_${timestamp}`,
|
||||
dictValue: `edit_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
status: '1',
|
||||
sort: 1
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('编辑字典数据', async () => {
|
||||
const timestamp = Date.now();
|
||||
await dictManagementPage.editDictData(`待编辑标签_${timestamp}`);
|
||||
await page.waitForTimeout(500);
|
||||
const updateData = {
|
||||
dictLabel: `已编辑标签_${timestamp}`,
|
||||
cssClass: 'el-tag-warning'
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(updateData);
|
||||
});
|
||||
|
||||
await test.step('提交修改', async () => {
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-007: 删除字典数据', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建测试字典数据', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `待删除标签_${timestamp}`,
|
||||
dictValue: `delete_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
status: '1',
|
||||
sort: 1
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('删除字典数据', async () => {
|
||||
const timestamp = Date.now();
|
||||
await dictManagementPage.deleteDictData(`待删除标签_${timestamp}`);
|
||||
await dictManagementPage.confirmDelete();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证字典数据已删除', async () => {
|
||||
await dictManagementPage.reload();
|
||||
const timestamp = Date.now();
|
||||
const dictDataDeleted = await dictManagementPage.containsText(`待删除标签_${timestamp}`);
|
||||
expect(dictDataDeleted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-008: 搜索字典', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索字典类型', async () => {
|
||||
await dictManagementPage.search('系统');
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('验证搜索结果', async () => {
|
||||
const searchResult = await dictManagementPage.containsText('系统');
|
||||
expect(searchResult).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await dictManagementPage.search('');
|
||||
await page.waitForTimeout(1000);
|
||||
const dictTypeCount = await dictManagementPage.getDictTypeCount();
|
||||
expect(dictTypeCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-009: 字典状态管理', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建启用状态的字典类型', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `启用字典_${timestamp}`,
|
||||
dictType: `enabled_dict_${timestamp}`,
|
||||
status: '1',
|
||||
remark: '这是启用的字典'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('创建禁用状态的字典类型', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `禁用字典_${timestamp}`,
|
||||
dictType: `disabled_dict_${timestamp}`,
|
||||
status: '0',
|
||||
remark: '这是禁用的字典'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-010: 字典排序功能', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建多个字典数据测试排序', async () => {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `排序标签_${i}_${timestamp}`,
|
||||
dictValue: `sort_value_${i}_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
status: '1',
|
||||
sort: i
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('验证字典数据按排序号显示', async () => {
|
||||
await dictManagementPage.reload();
|
||||
await page.waitForTimeout(1000);
|
||||
const dictDataCount = await dictManagementPage.getDictDataCount();
|
||||
expect(dictDataCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-011: 字典默认值设置', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建默认字典数据', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `默认标签_${timestamp}`,
|
||||
dictValue: `default_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
isDefault: 'Y',
|
||||
status: '1',
|
||||
sort: 1
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('创建非默认字典数据', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `非默认标签_${timestamp}`,
|
||||
dictValue: `non_default_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
isDefault: 'N',
|
||||
status: '1',
|
||||
sort: 2
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-012: 字典CSS样式配置', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建带CSS样式的字典数据', async () => {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `样式标签_${timestamp}`,
|
||||
dictValue: `style_value_${timestamp}`,
|
||||
dictType: 'sys_normal_disable',
|
||||
cssClass: 'el-tag-success',
|
||||
listClass: 'default',
|
||||
status: '1',
|
||||
sort: 1
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-013: 字典数据验证', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证字典类型数据完整性', async () => {
|
||||
const dictTypeCount = await dictManagementPage.getDictTypeCount();
|
||||
expect(dictTypeCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('验证字典数据完整性', async () => {
|
||||
const dictDataCount = await dictManagementPage.getDictDataCount();
|
||||
expect(dictDataCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('验证表格包含必要列', async () => {
|
||||
await expect(dictManagementPage.table).toContainText('字典名称');
|
||||
await expect(dictManagementPage.table).toContainText('字典类型');
|
||||
await expect(dictManagementPage.table).toContainText('状态');
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-014: 字典响应式布局', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(dictManagementPage.table).toBeVisible();
|
||||
await expect(dictManagementPage.createDictTypeButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(dictManagementPage.table).toBeVisible();
|
||||
await expect(dictManagementPage.createDictTypeButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(dictManagementPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('DICT-015: 字典类型与数据关联', async ({ page }) => {
|
||||
await test.step('导航到字典管理页面', async () => {
|
||||
await dictManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建字典类型', async () => {
|
||||
await dictManagementPage.clickCreateDictType();
|
||||
const timestamp = Date.now();
|
||||
const dictTypeData = {
|
||||
dictName: `关联测试字典_${timestamp}`,
|
||||
dictType: `relation_dict_${timestamp}`,
|
||||
status: '1',
|
||||
remark: '用于测试类型与数据关联'
|
||||
};
|
||||
await dictManagementPage.fillDictTypeForm(dictTypeData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('为该类型创建多个字典数据', async () => {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
await dictManagementPage.clickCreateDictData();
|
||||
const timestamp = Date.now();
|
||||
const dictData = {
|
||||
dictLabel: `关联数据_${i}_${timestamp}`,
|
||||
dictValue: `relation_value_${i}_${timestamp}`,
|
||||
dictType: `relation_dict_${timestamp}`,
|
||||
status: '1',
|
||||
sort: i
|
||||
};
|
||||
await dictManagementPage.fillDictDataForm(dictData);
|
||||
await dictManagementPage.submitForm();
|
||||
await expect(dictManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('验证字典数据关联成功', async () => {
|
||||
await dictManagementPage.reload();
|
||||
const dictDataCount = await dictManagementPage.getDictDataCount();
|
||||
expect(dictDataCount).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,534 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
import { TestHelper } from './utils/testHelper';
|
||||
|
||||
test.describe('边缘场景测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await TestHelper.clearAllStorage(page);
|
||||
});
|
||||
|
||||
test.describe('边界值测试', () => {
|
||||
test('用户名边界值 - 最小长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建最小长度用户名的用户', async () => {
|
||||
await userManagementPage.clickCreateUser();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
const minUsername = 'ab';
|
||||
await userManagementPage.fillUserForm({
|
||||
username: minUsername,
|
||||
email: 'test@example.com',
|
||||
password: 'password123'
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
});
|
||||
|
||||
await test.step('验证用户创建成功', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户名边界值 - 最大长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建最大长度用户名的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
const maxUsername = 'a'.repeat(50);
|
||||
await userManagementPage.fillUsername(maxUsername);
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证用户创建成功', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户名边界值 - 超过最大长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建超过最大长度用户名的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
const exceedUsername = 'a'.repeat(51);
|
||||
await userManagementPage.fillUsername(exceedUsername);
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证用户名长度验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('用户名长度不能超过50个字符');
|
||||
});
|
||||
});
|
||||
|
||||
test('密码边界值 - 最小长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建最小长度密码的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
const minPassword = 'a'.repeat(6);
|
||||
await userManagementPage.fillPassword(minPassword);
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证用户创建成功', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('密码边界值 - 最大长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建最大长度密码的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
const maxPassword = 'a'.repeat(20);
|
||||
await userManagementPage.fillPassword(maxPassword);
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证用户创建成功', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('密码边界值 - 低于最小长度', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建低于最小长度密码的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
const shortPassword = 'a'.repeat(5);
|
||||
await userManagementPage.fillPassword(shortPassword);
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证密码长度验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('密码长度不能少于6个字符');
|
||||
});
|
||||
});
|
||||
|
||||
test('邮箱边界值 - 无效格式', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建无效邮箱格式的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('invalid-email');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证邮箱格式验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('邮箱格式不正确');
|
||||
});
|
||||
});
|
||||
|
||||
test('角色名边界值 - 特殊字符', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建包含特殊字符的角色', async () => {
|
||||
await roleManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
const specialCharRole = '角色@#$%';
|
||||
await roleManagementPage.fillRoleName(specialCharRole);
|
||||
await roleManagementPage.fillRoleKey('ROLE_SPECIAL');
|
||||
await roleManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证特殊字符处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('空值和null值测试', () => {
|
||||
test('用户创建 - 用户名为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建用户名为空的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证用户名必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('用户名不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户创建 - 密码为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建密码为空的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证密码必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('密码不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户创建 - 邮箱为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建邮箱为空的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证邮箱必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('邮箱不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户创建 - 角色为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建角色为空的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证角色必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('角色不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
test('角色创建 - 角色名为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建角色名为空的角色', async () => {
|
||||
await roleManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await roleManagementPage.fillRoleName('');
|
||||
await roleManagementPage.fillRoleKey('ROLE_EMPTY');
|
||||
await roleManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证角色名必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('角色名不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
test('角色创建 - 角色键为空', async ({ page }) => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建角色键为空的角色', async () => {
|
||||
await roleManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await roleManagementPage.fillRoleName('测试角色');
|
||||
await roleManagementPage.fillRoleKey('');
|
||||
await roleManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证角色键必填验证', async () => {
|
||||
await TestHelper.waitForErrorMessage(page);
|
||||
const errorMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(errorMessage).toContain('角色键不能为空');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('特殊字符和格式测试', () => {
|
||||
test('用户名 - 包含中文字符', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建包含中文的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('测试用户');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证中文用户名处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('用户名 - 包含emoji表情', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建包含emoji的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('test😀user');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证emoji用户名处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('密码 - 包含特殊字符', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建包含特殊字符密码的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('P@ssw0rd!#$');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证特殊字符密码处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
|
||||
test('邮箱 - 包含特殊字符', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建包含特殊字符邮箱的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('testuser');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test.user+tag@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证特殊字符邮箱处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('并发和竞态条件测试', () => {
|
||||
test('并发创建相同用户名', async ({ page, context }) => {
|
||||
const page1 = page;
|
||||
const page2 = await context.newPage();
|
||||
|
||||
await test.step('在两个页面同时创建相同用户名的用户', async () => {
|
||||
await page1.goto('/users');
|
||||
await page2.goto('/users');
|
||||
|
||||
await TestHelper.waitForPageLoad(page1);
|
||||
await TestHelper.waitForPageLoad(page2);
|
||||
|
||||
await page1.click('.create-button');
|
||||
await page2.click('.create-button');
|
||||
|
||||
await TestHelper.waitForElementVisible(page1, '.el-dialog');
|
||||
await TestHelper.waitForElementVisible(page2, '.el-dialog');
|
||||
|
||||
await page1.fill('input[name="username"]', 'duplicateuser');
|
||||
await page2.fill('input[name="username"]', 'duplicateuser');
|
||||
|
||||
await page1.fill('input[name="password"]', 'password123');
|
||||
await page2.fill('input[name="password"]', 'password123');
|
||||
|
||||
await page1.fill('input[name="email"]', 'test1@example.com');
|
||||
await page2.fill('input[name="email"]', 'test2@example.com');
|
||||
|
||||
await page1.click('.el-dialog__footer button[type="submit"]');
|
||||
await page2.click('.el-dialog__footer button[type="submit"]');
|
||||
});
|
||||
|
||||
await test.step('验证并发冲突处理', async () => {
|
||||
await TestHelper.waitForPageLoad(page1);
|
||||
await TestHelper.waitForPageLoad(page2);
|
||||
|
||||
const errorMessage1 = await TestHelper.getElementText(page1, '.el-message__content');
|
||||
const errorMessage2 = await TestHelper.getElementText(page2, '.el-message__content');
|
||||
|
||||
expect(errorMessage1 || errorMessage2).toContain('用户名已存在');
|
||||
});
|
||||
});
|
||||
|
||||
test('快速连续操作', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('快速连续点击创建按钮', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await page.click('.create-button');
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('验证重复点击处理', async () => {
|
||||
const dialogs = await page.locator('.el-dialog').count();
|
||||
expect(dialogs).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('国际化场景测试', () => {
|
||||
test('中文界面操作', async ({ page }) => {
|
||||
await test.step('验证中文界面显示', async () => {
|
||||
const dashboardTitle = await page.textContent('h1');
|
||||
expect(dashboardTitle).toContain('仪表盘');
|
||||
});
|
||||
|
||||
await test.step('验证中文按钮文本', async () => {
|
||||
const createButton = await page.textContent('.create-button');
|
||||
expect(createButton).toContain('创建');
|
||||
});
|
||||
|
||||
await test.step('验证中文表单标签', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
const usernameLabel = await page.textContent('label[for="username"]');
|
||||
expect(usernameLabel).toContain('用户名');
|
||||
});
|
||||
});
|
||||
|
||||
test('中英文混合输入', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelper.waitForPageLoad(page);
|
||||
|
||||
await test.step('创建中英文混合用户名的用户', async () => {
|
||||
await userManagementPage.clickCreateButton();
|
||||
await TestHelper.waitForElementVisible(page, '.el-dialog');
|
||||
|
||||
await userManagementPage.fillUsername('test测试user');
|
||||
await userManagementPage.fillPassword('password123');
|
||||
await userManagementPage.fillEmail('test@example.com');
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.clickSaveButton();
|
||||
});
|
||||
|
||||
await test.step('验证中英文混合处理', async () => {
|
||||
await TestHelper.waitForSuccessMessage(page);
|
||||
const successMessage = await TestHelper.getElementText(page, '.el-message__content');
|
||||
expect(successMessage).toContain('创建成功');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,238 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { ExceptionLogPage } from './pages/ExceptionLogPage';
|
||||
|
||||
test.describe('异常日志 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let exceptionLogPage: ExceptionLogPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
exceptionLogPage = new ExceptionLogPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('EXCEPTION-001: 访问异常日志页面', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
await expect(page).toHaveURL(/.*exceptionlog/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
await expect(exceptionLogPage.searchInput).toBeVisible();
|
||||
await expect(exceptionLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-002: 搜索异常日志', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索异常日志', async () => {
|
||||
const keyword = 'admin';
|
||||
await exceptionLogPage.search(keyword);
|
||||
await exceptionLogPage.verifyTableContains(keyword);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await exceptionLogPage.clearSearch();
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-003: 异常日志分页功能', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格数据加载', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-004: 异常日志响应式布局', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
await expect(exceptionLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
await expect(exceptionLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-005: 异常日志数据验证', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志数据完整性', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
await test.step('验证日志字段显示', async () => {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-006: 异常日志搜索功能', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('按用户名搜索', async () => {
|
||||
const operator = 'admin';
|
||||
await exceptionLogPage.search(operator);
|
||||
await exceptionLogPage.verifyTableContains(operator);
|
||||
});
|
||||
|
||||
await test.step('按异常信息搜索', async () => {
|
||||
const exceptionInfo = 'Exception';
|
||||
await exceptionLogPage.search(exceptionInfo);
|
||||
});
|
||||
|
||||
await test.step('清除搜索结果', async () => {
|
||||
await exceptionLogPage.clearSearch();
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-007: 异常日志导出功能', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('导出异常日志', async () => {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await exceptionLogPage.exportData();
|
||||
const download = await downloadPromise;
|
||||
expect(download).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-008: 异常日志时间范围验证', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志时间戳显示', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-009: 异常日志权限验证', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证导出按钮可见性', async () => {
|
||||
await expect(exceptionLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证搜索功能可用', async () => {
|
||||
await expect(exceptionLogPage.searchInput).toBeVisible();
|
||||
await expect(exceptionLogPage.searchButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-010: 异常日志详情查看', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志详情显示', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-011: 异常日志刷新功能', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('刷新异常日志', async () => {
|
||||
await exceptionLogPage.refresh();
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-012: 异常日志排序功能', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格排序功能', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-013: 异常日志空状态显示', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索不存在的异常', async () => {
|
||||
await exceptionLogPage.search('nonexistent_exception_123456');
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('验证空状态显示', async () => {
|
||||
const rowCount = await exceptionLogPage.getLogCount();
|
||||
expect(rowCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-014: 异常日志批量操作', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证批量操作按钮可见性', async () => {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('EXCEPTION-015: 异常日志详细信息验证', async ({ page }) => {
|
||||
await test.step('导航到异常日志页面', async () => {
|
||||
await exceptionLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证异常日志包含必要信息', async () => {
|
||||
await expect(exceptionLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,205 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { FileManagementPage } from './pages/FileManagementPage';
|
||||
|
||||
test.describe('文件管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let fileManagementPage: FileManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
fileManagementPage = new FileManagementPage(page);
|
||||
});
|
||||
|
||||
test('FILE-001: 管理员查看文件列表', async ({ page }) => {
|
||||
await test.step('管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('导航到文件管理页面', async () => {
|
||||
await page.goto('/files');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(3000);
|
||||
});
|
||||
|
||||
await test.step('验证文件列表页面加载', async () => {
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
const rowCount = await fileManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
await test.step('验证文件表格包含必要列', async () => {
|
||||
await expect(fileManagementPage.table).toContainText('文件名');
|
||||
await expect(fileManagementPage.table).toContainText('文件大小');
|
||||
await expect(fileManagementPage.table).toContainText('上传时间');
|
||||
await expect(fileManagementPage.table).toContainText('上传人');
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-002: 上传文件', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('上传测试文件', async () => {
|
||||
const testFilePath = './e2e/fixtures/test-file.txt';
|
||||
|
||||
const uploadButton = page.locator('.el-upload');
|
||||
await uploadButton.first().click();
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles(testFilePath);
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-003: 搜索文件', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索特定文件', async () => {
|
||||
await fileManagementPage.searchFile('test');
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('清除搜索条件', async () => {
|
||||
await fileManagementPage.clearSearch();
|
||||
const rowCount = await fileManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-004: 下载文件', async ({ page, context }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('下载文件', async () => {
|
||||
const rows = await fileManagementPage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
const pagePromise = context.waitForEvent('page');
|
||||
await fileManagementPage.downloadFile('test');
|
||||
const newPage = await pagePromise;
|
||||
expect(newPage).toBeDefined();
|
||||
await newPage.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-005: 删除文件', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('删除文件', async () => {
|
||||
const rows = await fileManagementPage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
const firstRow = fileManagementPage.table.locator('.el-table__row').first();
|
||||
const fileName = await firstRow.locator('td').nth(1).textContent();
|
||||
|
||||
if (fileName) {
|
||||
await fileManagementPage.deleteFile(fileName);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-006: 验证文件权限控制', async ({ page }) => {
|
||||
await test.step('普通用户登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('尝试访问文件管理页面', async () => {
|
||||
await page.goto('/files');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentURL = page.url();
|
||||
if (currentURL.includes('/files')) {
|
||||
const rows = await fileManagementPage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
}
|
||||
} else {
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-007: 验证文件列表排序', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证文件按上传时间排序', async () => {
|
||||
const rows = await fileManagementPage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
const firstRow = fileManagementPage.table.locator('.el-table__row').first();
|
||||
await expect(firstRow).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-008: 验证文件大小显示', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证文件大小列显示', async () => {
|
||||
await expect(fileManagementPage.table).toContainText('文件大小');
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-009: 验证文件上传人信息', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证上传人列显示', async () => {
|
||||
await expect(fileManagementPage.table).toContainText('上传人');
|
||||
});
|
||||
});
|
||||
|
||||
test('FILE-010: 验证文件操作按钮可见性', async ({ page }) => {
|
||||
await test.step('管理员登录并导航到文件管理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await fileManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格可见', async () => {
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证搜索功能可用', async () => {
|
||||
const searchInput = page.locator('.search-bar input');
|
||||
await expect(searchInput).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('登录表单验证测试', () => {
|
||||
test('验证fill方法是否触发Vue响应式更新', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 使用fill方法填充
|
||||
await page.locator('input[placeholder="请输入用户名"]').fill('admin');
|
||||
await page.locator('input[placeholder="请输入密码"]').fill('admin123');
|
||||
|
||||
// 检查input元素的值
|
||||
const usernameValue = await page.locator('input[placeholder="请输入用户名"]').inputValue();
|
||||
const passwordValue = await page.locator('input[placeholder="请输入密码"]').inputValue();
|
||||
|
||||
console.log('Username input value:', usernameValue);
|
||||
console.log('Password input value:', passwordValue);
|
||||
|
||||
// 检查Vue组件的状态
|
||||
const formState = await page.evaluate(() => {
|
||||
const app = document.querySelector('#app');
|
||||
return app?.__vue_app__?.config?.globalProperties?.$data;
|
||||
});
|
||||
|
||||
console.log('Vue formState:', formState);
|
||||
|
||||
// 尝试获取localStorage中的值(登录前应该为空)
|
||||
const tokenBefore = await page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token before login:', tokenBefore);
|
||||
|
||||
// 点击登录按钮
|
||||
await page.locator('button:has-text("登录")').click();
|
||||
|
||||
// 等待API响应
|
||||
const response = await page.waitForResponse(response =>
|
||||
response.url().includes('/api/auth/login') && response.request().method() === 'POST',
|
||||
{ timeout: 10000 }
|
||||
).catch(e => {
|
||||
console.log('No API response received:', e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (response) {
|
||||
console.log('API response status:', response.status());
|
||||
const responseBody = await response.text();
|
||||
console.log('API response body:', responseBody.substring(0, 200));
|
||||
}
|
||||
|
||||
// 等待一段时间
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 检查localStorage中的token
|
||||
const tokenAfter = await page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token after login:', tokenAfter ? 'exists' : 'not found');
|
||||
|
||||
// 检查当前URL
|
||||
const currentUrl = page.url();
|
||||
console.log('Current URL:', currentUrl);
|
||||
|
||||
// 截图
|
||||
await page.screenshot({ path: 'test-results/form-test.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
@@ -100,7 +100,7 @@ async function globalSetup(config: FullConfig) {
|
||||
backendArgs = [
|
||||
'-jar',
|
||||
jarFile,
|
||||
'--spring.profiles.active=dev',
|
||||
'--spring.profiles.active=test',
|
||||
'-Xms256m',
|
||||
'-Xmx512m'
|
||||
];
|
||||
@@ -108,7 +108,7 @@ async function globalSetup(config: FullConfig) {
|
||||
console.log('📦 使用Maven启动后端服务...');
|
||||
console.log(' 提示: 运行 "mvn clean package -DskipTests" 构建JAR文件以获得更快的启动速度');
|
||||
backendCommand = 'mvn';
|
||||
backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=dev'];
|
||||
backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=test'];
|
||||
}
|
||||
|
||||
console.log(` 目录: ${backendDir}`);
|
||||
@@ -250,7 +250,7 @@ async function verifyAllServices(): Promise<void> {
|
||||
const response = await fetch('http://localhost:8080/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
signal: AbortSignal.timeout(10000) as any
|
||||
});
|
||||
|
||||
@@ -290,7 +290,7 @@ async function waitForBackendReady(): Promise<void> {
|
||||
const loginTest = await fetch('http://localhost:8084/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
signal: AbortSignal.timeout(10000) as any
|
||||
});
|
||||
|
||||
@@ -336,7 +336,7 @@ async function waitForGatewayReady(): Promise<void> {
|
||||
const loginTest = await fetch('http://localhost:8080/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
signal: AbortSignal.timeout(10000) as any
|
||||
});
|
||||
|
||||
@@ -398,7 +398,7 @@ async function cleanupTestData(): Promise<void> {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
password: 'Test@123'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export async function loginAsAdmin(page: Page) {
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.locator('input[placeholder*="用户名"]').fill('admin');
|
||||
await page.locator('input[placeholder*="密码"]').fill('admin123');
|
||||
await page.locator('input[placeholder*="密码"]').fill('Test@123');
|
||||
await page.locator('button:has-text("登录")').click();
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: 30000 });
|
||||
|
||||
@@ -28,6 +28,7 @@ test.describe('管理员完整工作流', () => {
|
||||
const dialog = page.locator('.el-dialog');
|
||||
await dialog.locator('input').first().fill(roleName);
|
||||
await dialog.locator('input').nth(1).fill(roleKey);
|
||||
await dialog.locator('input[type="number"]').fill('99');
|
||||
});
|
||||
|
||||
await test.step('提交表单', async () => {
|
||||
@@ -67,6 +68,32 @@ test.describe('管理员完整工作流', () => {
|
||||
await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 });
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('刷新用户列表', async () => {
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('分配角色', async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const userRow = page.locator(`tr:has-text("${username}")`);
|
||||
await expect(userRow).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await userRow.locator('button:has-text("分配角色")').click();
|
||||
await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'visible', timeout: 5000 });
|
||||
|
||||
const transfer = page.locator('.el-transfer');
|
||||
const availableRole = transfer.locator('.el-transfer-panel').first().locator('.el-checkbox:has-text("测试管理员")');
|
||||
if (await availableRole.isVisible()) {
|
||||
await availableRole.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
await page.locator('.el-dialog:has-text("分配角色") button:has-text("确定")').click();
|
||||
await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'hidden', timeout: 10000 });
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test('验证新用户登录', async ({ page }) => {
|
||||
@@ -110,7 +137,7 @@ test.describe('管理员完整工作流', () => {
|
||||
|
||||
await page.goto('/login');
|
||||
await page.locator('input[placeholder*="用户名"]').fill('admin');
|
||||
await page.locator('input[placeholder*="密码"]').fill('admin123');
|
||||
await page.locator('input[placeholder*="密码"]').fill('Test@123');
|
||||
await page.locator('button:has-text("登录")').click();
|
||||
await page.waitForURL('**/dashboard');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('权限边界测试', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
const timestamp = Date.now();
|
||||
const normalUsername = `normal_user_${timestamp}`;
|
||||
const normalPassword = 'Test@123';
|
||||
|
||||
test('准备:创建普通用户', async ({ page }) => {
|
||||
await test.step('管理员登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'Test@123');
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('导航到用户管理', async () => {
|
||||
await page.goto('/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.locator('text=系统管理').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('text=用户管理').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await expect(page).toHaveURL(/.*users/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('创建普通用户', async () => {
|
||||
await page.locator('button:has-text("新增用户")').click();
|
||||
await page.waitForSelector('.el-dialog', { state: 'visible', timeout: 5000 });
|
||||
|
||||
const dialog = page.locator('.el-dialog');
|
||||
await dialog.locator('input').first().fill(normalUsername);
|
||||
await dialog.locator('input[type="password"]').fill(normalPassword);
|
||||
await dialog.locator('input').nth(2).fill(`普通用户${timestamp}`);
|
||||
await dialog.locator('input').nth(3).fill(`normal_${timestamp}@example.com`);
|
||||
await dialog.locator('input').nth(4).fill('13800138001');
|
||||
|
||||
await page.locator('.el-dialog button:has-text("确定")').click();
|
||||
await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 });
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('分配普通用户角色', async () => {
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const userRow = page.locator(`tr:has-text("${normalUsername}")`);
|
||||
await expect(userRow).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await userRow.locator('button:has-text("分配角色")').click();
|
||||
await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'visible', timeout: 5000 });
|
||||
|
||||
const transfer = page.locator('.el-transfer');
|
||||
const availableRole = transfer.locator('.el-transfer-panel').first().locator('.el-checkbox:has-text("普通用户")');
|
||||
if (await availableRole.isVisible()) {
|
||||
await availableRole.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
await page.locator('.el-dialog:has-text("分配角色") button:has-text("确定")').click();
|
||||
await page.waitForSelector('.el-dialog:has-text("分配角色")', { state: 'hidden', timeout: 10000 });
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('管理员登出', async () => {
|
||||
await page.locator('.el-dropdown-link').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('text=退出登录').click();
|
||||
await page.waitForURL(/.*login/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test('普通用户登录', async ({ page }) => {
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', normalUsername);
|
||||
await page.fill('input[type="password"]', normalPassword);
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('验证登录成功', async () => {
|
||||
await expect(page.locator('text=仪表盘')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test('普通用户不能访问用户管理页面', async ({ page }) => {
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', normalUsername);
|
||||
await page.fill('input[type="password"]', normalPassword);
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('尝试直接访问用户管理页面', async () => {
|
||||
await page.goto('/dashboard/system/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('验证被重定向或显示无权限提示', async () => {
|
||||
const currentUrl = page.url();
|
||||
const hasNoPermission = await page.locator('text=无权限').isVisible().catch(() => false);
|
||||
const isRedirected = !currentUrl.includes('/users');
|
||||
|
||||
expect(hasNoPermission || isRedirected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('普通用户不能访问角色管理页面', async ({ page }) => {
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', normalUsername);
|
||||
await page.fill('input[type="password"]', normalPassword);
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('尝试直接访问角色管理页面', async () => {
|
||||
await page.goto('/dashboard/system/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('验证被重定向或显示无权限提示', async () => {
|
||||
const currentUrl = page.url();
|
||||
const hasNoPermission = await page.locator('text=无权限').isVisible().catch(() => false);
|
||||
const isRedirected = !currentUrl.includes('/roles');
|
||||
|
||||
expect(hasNoPermission || isRedirected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('普通用户不能创建用户', async ({ page }) => {
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', normalUsername);
|
||||
await page.fill('input[type="password"]', normalPassword);
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('尝试通过API创建用户', async () => {
|
||||
const response = await page.request.post('http://localhost:8084/api/users', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
username: `unauthorized_user_${Date.now()}`,
|
||||
password: 'Test@123',
|
||||
nickname: '未授权用户',
|
||||
email: `unauthorized_${Date.now()}@example.com`,
|
||||
phone: '13800138002',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
test('清理:删除测试用户', async ({ page }) => {
|
||||
await test.step('管理员登录', async () => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="text"]', 'admin');
|
||||
await page.fill('input[type="password"]', 'Test@123');
|
||||
await page.click('button:has-text("登录")');
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('导航到用户管理', async () => {
|
||||
await page.goto('/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.locator('text=系统管理').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('text=用户管理').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await expect(page).toHaveURL(/.*users/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('删除测试用户', async () => {
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const userRow = page.locator(`tr:has-text("${normalUsername}")`);
|
||||
if (await userRow.isVisible()) {
|
||||
await userRow.locator('button:has-text("删除")').click();
|
||||
await page.waitForSelector('.el-message-box', { state: 'visible', timeout: 5000 });
|
||||
await page.locator('.el-message-box button:has-text("确定")').click();
|
||||
await page.waitForSelector('.el-message-box', { state: 'hidden', timeout: 10000 });
|
||||
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,18 @@ test.describe('用户权限边界验证', () => {
|
||||
});
|
||||
|
||||
test('普通用户只能访问个人信息', async ({ page }) => {
|
||||
test.skip('需要创建普通用户并配置权限');
|
||||
|
||||
await test.step('管理员登出', async () => {
|
||||
await page.goto('/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const avatarButton = page.locator('.el-avatar').first();
|
||||
await avatarButton.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.locator('text=退出登录').click();
|
||||
await page.waitForURL(/.*login/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
@@ -35,10 +46,10 @@ test.describe('用户权限边界验证', () => {
|
||||
const loginButton = page.locator('button:has-text("登录")');
|
||||
|
||||
await usernameInput.waitFor({ state: 'visible' });
|
||||
await usernameInput.fill('user');
|
||||
await usernameInput.fill('normaluser');
|
||||
|
||||
await passwordInput.waitFor({ state: 'visible' });
|
||||
await passwordInput.fill('admin123');
|
||||
await passwordInput.fill('Test@123');
|
||||
|
||||
await loginButton.waitFor({ state: 'visible' });
|
||||
await loginButton.click();
|
||||
@@ -69,7 +80,18 @@ test.describe('用户权限边界验证', () => {
|
||||
});
|
||||
|
||||
test('权限不足时显示提示信息', async ({ page }) => {
|
||||
test.skip('需要创建普通用户并配置权限');
|
||||
|
||||
await test.step('管理员登出', async () => {
|
||||
await page.goto('/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const avatarButton = page.locator('.el-avatar').first();
|
||||
await avatarButton.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.locator('text=退出登录').click();
|
||||
await page.waitForURL(/.*login/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('普通用户登录', async () => {
|
||||
await page.goto('/login');
|
||||
@@ -80,10 +102,10 @@ test.describe('用户权限边界验证', () => {
|
||||
const loginButton = page.locator('button:has-text("登录")');
|
||||
|
||||
await usernameInput.waitFor({ state: 'visible' });
|
||||
await usernameInput.fill('user');
|
||||
await usernameInput.fill('normaluser');
|
||||
|
||||
await passwordInput.waitFor({ state: 'visible' });
|
||||
await passwordInput.fill('admin123');
|
||||
await passwordInput.fill('Test@123');
|
||||
|
||||
await loginButton.waitFor({ state: 'visible' });
|
||||
await loginButton.click();
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { LoginLogPage } from './pages/LoginLogPage';
|
||||
|
||||
test.describe('登录日志E2E测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let loginLogPage: LoginLogPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
loginLogPage = new LoginLogPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('登录日志页面导航', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
await expect(page).toHaveURL(/.*loginlog/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
await expect(loginLogPage.searchInput).toBeVisible();
|
||||
await expect(loginLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('搜索登录日志', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索登录日志', async () => {
|
||||
const keyword = 'admin';
|
||||
|
||||
await loginLogPage.searchByKeyword(keyword);
|
||||
await loginLogPage.verifyTableContains(keyword);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await loginLogPage.clearSearch();
|
||||
const rowCount = await loginLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志分页功能', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格数据加载', async () => {
|
||||
const rowCount = await loginLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志响应式布局', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
await expect(loginLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
await expect(loginLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志数据验证', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志数据完整性', async () => {
|
||||
const rowCount = await loginLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('验证日志字段显示', async () => {
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志搜索功能', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('按用户名搜索', async () => {
|
||||
const username = 'admin';
|
||||
await loginLogPage.searchByKeyword(username);
|
||||
await loginLogPage.verifyTableContains(username);
|
||||
});
|
||||
|
||||
await test.step('按IP地址搜索', async () => {
|
||||
const ipAddress = '127.0.0.1';
|
||||
await loginLogPage.searchByKeyword(ipAddress);
|
||||
});
|
||||
|
||||
await test.step('清除搜索结果', async () => {
|
||||
await loginLogPage.clearSearch();
|
||||
const rowCount = await loginLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志导出功能', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('导出登录日志', async () => {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await loginLogPage.exportData();
|
||||
const download = await downloadPromise;
|
||||
expect(download).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志时间范围验证', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志时间戳显示', async () => {
|
||||
const rowCount = await loginLogPage.getTableRowCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(loginLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('登录日志权限验证', async ({ page }) => {
|
||||
await test.step('导航到登录日志页面', async () => {
|
||||
await loginLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证导出按钮可见性', async () => {
|
||||
await expect(loginLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证搜索功能可用', async () => {
|
||||
await expect(loginLogPage.searchInput).toBeVisible();
|
||||
await expect(loginLogPage.searchButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,400 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { MenuManagementPage } from './pages/MenuManagementPage';
|
||||
|
||||
test.describe('菜单管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let menuManagementPage: MenuManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
menuManagementPage = new MenuManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test('MENU-001: 访问菜单管理页面', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
await expect(page).toHaveURL(/.*menus/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(menuManagementPage.table).toBeVisible();
|
||||
await expect(menuManagementPage.createMenuButton).toBeVisible();
|
||||
await expect(menuManagementPage.searchInput).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-002: 创建一级菜单', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('点击新增菜单按钮', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
});
|
||||
|
||||
await test.step('填写菜单信息', async () => {
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `测试菜单_${timestamp}`,
|
||||
menuType: '目录',
|
||||
path: `/test-menu-${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
});
|
||||
|
||||
await test.step('提交表单', async () => {
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证菜单创建成功', async () => {
|
||||
await menuManagementPage.reload();
|
||||
const timestamp = Date.now();
|
||||
const menuCreated = await menuManagementPage.containsText(`测试菜单_${timestamp}`);
|
||||
expect(menuCreated).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-003: 创建二级菜单', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('展开父级菜单', async () => {
|
||||
await menuManagementPage.expandAll();
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('点击新增菜单按钮', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
});
|
||||
|
||||
await test.step('填写二级菜单信息', async () => {
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `测试子菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/test-submenu-${timestamp}`,
|
||||
component: `TestSubmenu${timestamp}`,
|
||||
permission: `system:test:submenu:${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
});
|
||||
|
||||
await test.step('提交表单', async () => {
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-004: 编辑菜单', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('编辑现有菜单', async () => {
|
||||
const menuName = '系统管理';
|
||||
await menuManagementPage.editMenu(menuName);
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
await test.step('修改菜单信息', async () => {
|
||||
const timestamp = Date.now();
|
||||
const updateData = {
|
||||
menuName: `系统管理_更新_${timestamp}`,
|
||||
sort: 2
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(updateData);
|
||||
});
|
||||
|
||||
await test.step('提交修改', async () => {
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-005: 删除菜单', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建测试菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `待删除菜单_${timestamp}`,
|
||||
menuType: '目录',
|
||||
path: `/delete-test-${timestamp}`,
|
||||
sort: 99,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('删除菜单', async () => {
|
||||
const timestamp = Date.now();
|
||||
await menuManagementPage.deleteMenu(`待删除菜单_${timestamp}`);
|
||||
await menuManagementPage.confirmDelete();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('验证菜单已删除', async () => {
|
||||
await menuManagementPage.reload();
|
||||
const timestamp = Date.now();
|
||||
const menuDeleted = await menuManagementPage.containsText(`待删除菜单_${timestamp}`);
|
||||
expect(menuDeleted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-006: 搜索菜单', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索菜单', async () => {
|
||||
await menuManagementPage.search('系统管理');
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
await test.step('验证搜索结果', async () => {
|
||||
const searchResult = await menuManagementPage.containsText('系统管理');
|
||||
expect(searchResult).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await menuManagementPage.search('');
|
||||
await page.waitForTimeout(1000);
|
||||
const menuCount = await menuManagementPage.getMenuCount();
|
||||
expect(menuCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-007: 菜单树展开和折叠', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('展开所有菜单', async () => {
|
||||
await menuManagementPage.expandAll();
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(menuManagementPage.treeContainer).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('折叠所有菜单', async () => {
|
||||
await menuManagementPage.collapseAll();
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(menuManagementPage.treeContainer).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-008: 菜单排序功能', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建多个菜单测试排序', async () => {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `排序测试菜单_${i}_${timestamp}`,
|
||||
menuType: '目录',
|
||||
path: `/sort-test-${i}-${timestamp}`,
|
||||
sort: i,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('验证菜单按排序号显示', async () => {
|
||||
await menuManagementPage.reload();
|
||||
await page.waitForTimeout(1000);
|
||||
const menuCount = await menuManagementPage.getMenuCount();
|
||||
expect(menuCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-009: 菜单可见性控制', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建可见菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `可见菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/visible-menu-${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('创建隐藏菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `隐藏菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/hidden-menu-${timestamp}`,
|
||||
sort: 2,
|
||||
visible: '0',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-010: 菜单状态管理', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建启用状态的菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `启用菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/enabled-menu-${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
await test.step('创建禁用状态的菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `禁用菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/disabled-menu-${timestamp}`,
|
||||
sort: 2,
|
||||
visible: '1',
|
||||
status: '0'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-011: 菜单权限标识', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建带权限标识的菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `权限菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/permission-menu-${timestamp}`,
|
||||
component: `PermissionMenu${timestamp}`,
|
||||
permission: `system:permission:menu:${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-012: 菜单组件路径配置', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建带组件路径的菜单', async () => {
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
const timestamp = Date.now();
|
||||
const menuData = {
|
||||
menuName: `组件菜单_${timestamp}`,
|
||||
menuType: '菜单',
|
||||
path: `/component-menu-${timestamp}`,
|
||||
component: `system/ComponentMenu${timestamp}`,
|
||||
sort: 1,
|
||||
visible: '1',
|
||||
status: '1'
|
||||
};
|
||||
await menuManagementPage.fillMenuForm(menuData);
|
||||
await menuManagementPage.submitForm();
|
||||
await expect(menuManagementPage.isSuccessMessageVisible()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-013: 菜单响应式布局', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(menuManagementPage.table).toBeVisible();
|
||||
await expect(menuManagementPage.createMenuButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(menuManagementPage.table).toBeVisible();
|
||||
await expect(menuManagementPage.createMenuButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(menuManagementPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('MENU-014: 菜单数据验证', async ({ page }) => {
|
||||
await test.step('导航到菜单管理页面', async () => {
|
||||
await menuManagementPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证菜单数据完整性', async () => {
|
||||
const menuCount = await menuManagementPage.getMenuCount();
|
||||
expect(menuCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('验证表格包含必要列', async () => {
|
||||
await expect(menuManagementPage.table).toContainText('菜单名称');
|
||||
await expect(menuManagementPage.table).toContainText('类型');
|
||||
await expect(menuManagementPage.table).toContainText('路径');
|
||||
await expect(menuManagementPage.table).toContainText('排序');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,195 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { NotificationPage } from './pages/NotificationPage';
|
||||
|
||||
test.describe('通知公告E2E测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let noticePage: NotificationPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
noticePage = new NotificationPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('通知公告页面导航', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
await expect(page).toHaveURL(/.*notice/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(noticePage.table).toBeVisible();
|
||||
await expect(noticePage.addButton).toBeVisible();
|
||||
await expect(noticePage.searchInput).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('创建通知公告', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建新通知公告', async () => {
|
||||
const title = `测试通知_${Date.now()}`;
|
||||
const content = `这是一条测试通知内容_${Date.now()}`;
|
||||
|
||||
await noticePage.addNotification(title, content);
|
||||
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
});
|
||||
|
||||
test('编辑通知公告', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('编辑现有通知公告', async () => {
|
||||
const title = '系统维护通知';
|
||||
const newContent = `系统将于今晚进行维护,请提前保存工作_${Date.now()}`;
|
||||
|
||||
await noticePage.editNotification(title, newContent);
|
||||
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
});
|
||||
|
||||
test('删除通知公告', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('删除通知公告', async () => {
|
||||
const title = `测试通知_${Date.now()}`;
|
||||
const content = `这是一条测试通知内容_${Date.now()}`;
|
||||
|
||||
await noticePage.addNotification(title, content);
|
||||
await noticePage.verifyTableContains(title);
|
||||
|
||||
await noticePage.deleteNotification(title);
|
||||
await noticePage.verifyTableNotContains(title);
|
||||
});
|
||||
});
|
||||
|
||||
test('搜索通知公告', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索通知公告', async () => {
|
||||
const title = '系统维护通知';
|
||||
|
||||
await noticePage.searchNotification(title);
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await noticePage.clearSearch();
|
||||
const rowCount = await noticePage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('通知公告分页功能', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格数据加载', async () => {
|
||||
const rowCount = await noticePage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('通知公告响应式布局', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(noticePage.table).toBeVisible();
|
||||
await expect(noticePage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(noticePage.table).toBeVisible();
|
||||
await expect(noticePage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(noticePage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('通知公告权限验证', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证添加按钮可见性', async () => {
|
||||
await expect(noticePage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证编辑和删除按钮可见性', async () => {
|
||||
const rows = await noticePage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
await expect(noticePage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('通知公告状态管理', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建已发布通知', async () => {
|
||||
const title = `已发布通知_${Date.now()}`;
|
||||
const content = `这是一条已发布的通知_${Date.now()}`;
|
||||
|
||||
await noticePage.addNotification(title, content, '1', '0');
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
|
||||
await test.step('创建草稿通知', async () => {
|
||||
const title = `草稿通知_${Date.now()}`;
|
||||
const content = `这是一条草稿通知_${Date.now()}`;
|
||||
|
||||
await noticePage.addNotification(title, content, '1', '1');
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
});
|
||||
|
||||
test('通知公告内容验证', async ({ page }) => {
|
||||
await test.step('导航到通知公告页面', async () => {
|
||||
await noticePage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证通知标题长度限制', async () => {
|
||||
const longTitle = '这是一个非常非常长的通知标题,用于测试系统对长标题的处理能力,确保系统能够正确显示和存储长标题';
|
||||
const content = '测试内容';
|
||||
|
||||
await noticePage.addNotification(longTitle, content);
|
||||
await noticePage.verifyTableContains(longTitle.substring(0, 50));
|
||||
});
|
||||
|
||||
await test.step('验证通知内容格式', async () => {
|
||||
const title = `格式测试通知_${Date.now()}`;
|
||||
const content = '支持富文本格式:<b>粗体</b>、<i>斜体</i>、<u>下划线</u>';
|
||||
|
||||
await noticePage.addNotification(title, content);
|
||||
await noticePage.verifyTableContains(title);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,192 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { OperationLogPage } from './pages/OperationLogPage';
|
||||
|
||||
test.describe('操作日志E2E测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let operationLogPage: OperationLogPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
operationLogPage = new OperationLogPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('操作日志页面导航', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
await expect(page).toHaveURL(/.*oplog/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
await expect(operationLogPage.searchInput).toBeVisible();
|
||||
await expect(operationLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('搜索操作日志', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索操作日志', async () => {
|
||||
const keyword = 'admin';
|
||||
|
||||
await operationLogPage.searchByKeyword(keyword);
|
||||
await operationLogPage.verifyTableContains(keyword);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await operationLogPage.clearSearch();
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志分页功能', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格数据加载', async () => {
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志响应式布局', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
await expect(operationLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
await expect(operationLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志数据验证', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志数据完整性', async () => {
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('验证日志字段显示', async () => {
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志搜索功能', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('按操作人搜索', async () => {
|
||||
const operator = 'admin';
|
||||
await operationLogPage.searchByKeyword(operator);
|
||||
await operationLogPage.verifyTableContains(operator);
|
||||
});
|
||||
|
||||
await test.step('按操作模块搜索', async () => {
|
||||
const module = '用户管理';
|
||||
await operationLogPage.searchByKeyword(module);
|
||||
});
|
||||
|
||||
await test.step('清除搜索结果', async () => {
|
||||
await operationLogPage.clearSearch();
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志导出功能', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('导出操作日志', async () => {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await operationLogPage.exportData();
|
||||
const download = await downloadPromise;
|
||||
expect(download).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志时间范围验证', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志时间戳显示', async () => {
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志权限验证', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证导出按钮可见性', async () => {
|
||||
await expect(operationLogPage.exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证搜索功能可用', async () => {
|
||||
await expect(operationLogPage.searchInput).toBeVisible();
|
||||
await expect(operationLogPage.searchButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志详情查看', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证日志详情显示', async () => {
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('操作日志排序功能', async ({ page }) => {
|
||||
await test.step('导航到操作日志页面', async () => {
|
||||
await operationLogPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格排序功能', async () => {
|
||||
const rowCount = await operationLogPage.getTableRowCount();
|
||||
if (rowCount > 0) {
|
||||
await expect(operationLogPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,368 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
import { MenuManagementPage } from './pages/MenuManagementPage';
|
||||
import { SystemConfigPage } from './pages/SystemConfigPage';
|
||||
|
||||
// 测试用户配置
|
||||
const TEST_USERS = {
|
||||
superAdmin: {
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
role: '超级管理员'
|
||||
},
|
||||
systemAdmin: {
|
||||
username: 'sysadmin',
|
||||
password: 'SysAdmin123!',
|
||||
role: '系统管理员'
|
||||
},
|
||||
regularUser: {
|
||||
username: 'user',
|
||||
password: 'User123!',
|
||||
role: '普通用户'
|
||||
},
|
||||
guest: {
|
||||
username: '',
|
||||
password: '',
|
||||
role: '访客'
|
||||
}
|
||||
};
|
||||
|
||||
// 权限验证测试套件
|
||||
test.describe('系统配置功能权限验证测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
let menuManagementPage: MenuManagementPage;
|
||||
let systemConfigPage: SystemConfigPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
menuManagementPage = new MenuManagementPage(page);
|
||||
systemConfigPage = new SystemConfigPage(page);
|
||||
});
|
||||
|
||||
// 测试1: 超级管理员权限验证
|
||||
test('PERM-001: 超级管理员完整权限验证', async ({ page }) => {
|
||||
const user = TEST_USERS.superAdmin;
|
||||
const testResults = [];
|
||||
|
||||
await test.step(`1. ${user.role}登录系统`, async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login(user.username, user.password);
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
testResults.push({ step: '登录系统', result: '通过', details: '成功登录到仪表板' });
|
||||
});
|
||||
|
||||
await test.step('2. 验证用户管理权限', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
// 验证用户管理页面可访问
|
||||
await expect(page.locator('.user-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建用户权限
|
||||
await userManagementPage.clickCreateUser();
|
||||
await expect(page.locator('.user-form')).toBeVisible();
|
||||
testResults.push({ step: '用户管理权限', result: '通过', details: '可访问用户管理页面和创建用户功能' });
|
||||
});
|
||||
|
||||
await test.step('3. 验证角色管理权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
// 验证角色管理页面可访问
|
||||
await expect(page.locator('.role-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建角色权限
|
||||
await roleManagementPage.clickCreateRole();
|
||||
await expect(page.locator('.role-form')).toBeVisible();
|
||||
testResults.push({ step: '角色管理权限', result: '通过', details: '可访问角色管理页面和创建角色功能' });
|
||||
});
|
||||
|
||||
await test.step('4. 验证菜单管理权限', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
|
||||
// 验证菜单管理页面可访问
|
||||
await expect(page.locator('.menu-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建菜单权限
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
await expect(page.locator('.menu-form')).toBeVisible();
|
||||
testResults.push({ step: '菜单管理权限', result: '通过', details: '可访问菜单管理页面和创建菜单功能' });
|
||||
});
|
||||
|
||||
await test.step('5. 验证系统配置权限', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
|
||||
// 验证系统配置页面可访问
|
||||
await expect(page.locator('.system-config-header')).toBeVisible();
|
||||
|
||||
// 验证配置修改权限
|
||||
await systemConfigPage.clickEditConfig();
|
||||
await expect(page.locator('.config-form')).toBeVisible();
|
||||
testResults.push({ step: '系统配置权限', result: '通过', details: '可访问系统配置页面和修改配置功能' });
|
||||
});
|
||||
|
||||
// 生成测试报告
|
||||
console.log(`\n=== ${user.role}权限验证报告 ===`);
|
||||
testResults.forEach(result => {
|
||||
console.log(`[${result.result}] ${result.step}: ${result.details}`);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试2: 系统管理员权限验证
|
||||
test('PERM-002: 系统管理员权限验证', async ({ page }) => {
|
||||
const user = TEST_USERS.systemAdmin;
|
||||
const testResults = [];
|
||||
|
||||
await test.step(`1. ${user.role}登录系统`, async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login(user.username, user.password);
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
testResults.push({ step: '登录系统', result: '通过', details: '成功登录到仪表板' });
|
||||
});
|
||||
|
||||
await test.step('2. 验证用户管理权限', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
// 验证用户管理页面可访问
|
||||
await expect(page.locator('.user-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建用户权限
|
||||
await userManagementPage.clickCreateUser();
|
||||
await expect(page.locator('.user-form')).toBeVisible();
|
||||
testResults.push({ step: '用户管理权限', result: '通过', details: '可访问用户管理页面和创建用户功能' });
|
||||
});
|
||||
|
||||
await test.step('3. 验证角色管理权限', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
// 验证角色管理页面可访问
|
||||
await expect(page.locator('.role-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建角色权限
|
||||
await roleManagementPage.clickCreateRole();
|
||||
await expect(page.locator('.role-form')).toBeVisible();
|
||||
testResults.push({ step: '角色管理权限', result: '通过', details: '可访问角色管理页面和创建角色功能' });
|
||||
});
|
||||
|
||||
await test.step('4. 验证菜单管理权限', async () => {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
|
||||
// 验证菜单管理页面可访问
|
||||
await expect(page.locator('.menu-management-header')).toBeVisible();
|
||||
|
||||
// 验证创建菜单权限
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
await expect(page.locator('.menu-form')).toBeVisible();
|
||||
testResults.push({ step: '菜单管理权限', result: '通过', details: '可访问菜单管理页面和创建菜单功能' });
|
||||
});
|
||||
|
||||
await test.step('5. 验证系统配置权限限制', async () => {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
|
||||
// 验证系统配置页面可访问
|
||||
await expect(page.locator('.system-config-header')).toBeVisible();
|
||||
|
||||
// 验证配置修改权限(可能受限)
|
||||
try {
|
||||
await systemConfigPage.clickEditConfig();
|
||||
await expect(page.locator('.config-form')).toBeVisible();
|
||||
testResults.push({ step: '系统配置权限', result: '通过', details: '可访问系统配置页面和修改配置功能' });
|
||||
} catch (error) {
|
||||
testResults.push({ step: '系统配置权限', result: '受限', details: '系统配置修改功能受限' });
|
||||
}
|
||||
});
|
||||
|
||||
// 生成测试报告
|
||||
console.log(`\n=== ${user.role}权限验证报告 ===`);
|
||||
testResults.forEach(result => {
|
||||
console.log(`[${result.result}] ${step}: ${result.details}`);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试3: 普通用户权限验证
|
||||
test('PERM-003: 普通用户权限验证', async ({ page }) => {
|
||||
const user = TEST_USERS.regularUser;
|
||||
const testResults = [];
|
||||
|
||||
await test.step(`1. ${user.role}登录系统`, async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login(user.username, user.password);
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
testResults.push({ step: '登录系统', result: '通过', details: '成功登录到仪表板' });
|
||||
});
|
||||
|
||||
await test.step('2. 验证用户管理权限限制', async () => {
|
||||
try {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
// 如果能够访问,验证是否有限制
|
||||
const hasAccess = await page.locator('.user-management-header').isVisible();
|
||||
if (hasAccess) {
|
||||
testResults.push({ step: '用户管理权限', result: '受限', details: '可访问但功能受限' });
|
||||
} else {
|
||||
testResults.push({ step: '用户管理权限', result: '拒绝', details: '无法访问用户管理页面' });
|
||||
}
|
||||
} catch (error) {
|
||||
testResults.push({ step: '用户管理权限', result: '拒绝', details: '权限不足,无法访问' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('3. 验证角色管理权限限制', async () => {
|
||||
try {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
|
||||
const hasAccess = await page.locator('.role-management-header').isVisible();
|
||||
if (hasAccess) {
|
||||
testResults.push({ step: '角色管理权限', result: '受限', details: '可访问但功能受限' });
|
||||
} else {
|
||||
testResults.push({ step: '角色管理权限', result: '拒绝', details: '无法访问角色管理页面' });
|
||||
}
|
||||
} catch (error) {
|
||||
testResults.push({ step: '角色管理权限', result: '拒绝', details: '权限不足,无法访问' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('4. 验证菜单管理权限限制', async () => {
|
||||
try {
|
||||
await dashboardPage.navigateToMenuManagement();
|
||||
|
||||
const hasAccess = await page.locator('.menu-management-header').isVisible();
|
||||
if (hasAccess) {
|
||||
testResults.push({ step: '菜单管理权限', result: '受限', details: '可访问但功能受限' });
|
||||
} else {
|
||||
testResults.push({ step: '菜单管理权限', result: '拒绝', details: '无法访问菜单管理页面' });
|
||||
}
|
||||
} catch (error) {
|
||||
testResults.push({ step: '菜单管理权限', result: '拒绝', details: '权限不足,无法访问' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('5. 验证系统配置权限限制', async () => {
|
||||
try {
|
||||
await dashboardPage.navigateToSystemConfig();
|
||||
|
||||
const hasAccess = await page.locator('.system-config-header').isVisible();
|
||||
if (hasAccess) {
|
||||
testResults.push({ step: '系统配置权限', result: '受限', details: '可访问但功能受限' });
|
||||
} else {
|
||||
testResults.push({ step: '系统配置权限', result: '拒绝', details: '无法访问系统配置页面' });
|
||||
}
|
||||
} catch (error) {
|
||||
testResults.push({ step: '系统配置权限', result: '拒绝', details: '权限不足,无法访问' });
|
||||
}
|
||||
});
|
||||
|
||||
// 生成测试报告
|
||||
console.log(`\n=== ${user.role}权限验证报告 ===`);
|
||||
testResults.forEach(result => {
|
||||
console.log(`[${result.result}] ${result.step}: ${result.details}`);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试4: 访客权限验证
|
||||
test('PERM-004: 访客权限验证', async ({ page }) => {
|
||||
const user = TEST_USERS.guest;
|
||||
const testResults = [];
|
||||
|
||||
await test.step('1. 直接访问系统管理页面', async () => {
|
||||
await page.goto('/user-management');
|
||||
|
||||
// 验证是否被重定向到登录页面
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes('/login')) {
|
||||
testResults.push({ step: '用户管理页面访问', result: '拒绝', details: '被重定向到登录页面' });
|
||||
} else {
|
||||
testResults.push({ step: '用户管理页面访问', result: '异常', details: '未正确重定向' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('2. 直接访问角色管理页面', async () => {
|
||||
await page.goto('/role-management');
|
||||
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes('/login')) {
|
||||
testResults.push({ step: '角色管理页面访问', result: '拒绝', details: '被重定向到登录页面' });
|
||||
} else {
|
||||
testResults.push({ step: '角色管理页面访问', result: '异常', details: '未正确重定向' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('3. 直接访问菜单管理页面', async () => {
|
||||
await page.goto('/menu-management');
|
||||
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes('/login')) {
|
||||
testResults.push({ step: '菜单管理页面访问', result: '拒绝', details: '被重定向到登录页面' });
|
||||
} else {
|
||||
testResults.push({ step: '菜单管理页面访问', result: '异常', details: '未正确重定向' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('4. 直接访问系统配置页面', async () => {
|
||||
await page.goto('/system-config');
|
||||
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes('/login')) {
|
||||
testResults.push({ step: '系统配置页面访问', result: '拒绝', details: '被重定向到登录页面' });
|
||||
} else {
|
||||
testResults.push({ step: '系统配置页面访问', result: '异常', details: '未正确重定向' });
|
||||
}
|
||||
});
|
||||
|
||||
// 生成测试报告
|
||||
console.log(`\n=== ${user.role}权限验证报告 ===`);
|
||||
testResults.forEach(result => {
|
||||
console.log(`[${result.result}] ${result.step}: ${result.details}`);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试5: 权限边界测试
|
||||
test('PERM-005: 权限边界测试', async ({ page }) => {
|
||||
const testResults = [];
|
||||
|
||||
await test.step('1. 测试越权访问', async () => {
|
||||
// 使用普通用户登录
|
||||
await loginPage.goto();
|
||||
await loginPage.login(TEST_USERS.regularUser.username, TEST_USERS.regularUser.password);
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
|
||||
// 尝试直接访问管理员功能URL
|
||||
await page.goto('/user-management/create');
|
||||
|
||||
// 验证是否被阻止
|
||||
const isBlocked = await page.locator('.access-denied, .permission-error').isVisible() ||
|
||||
page.url().includes('/login') ||
|
||||
page.url().includes('/dashboard');
|
||||
|
||||
if (isBlocked) {
|
||||
testResults.push({ step: '越权访问测试', result: '通过', details: '系统正确阻止了越权访问' });
|
||||
} else {
|
||||
testResults.push({ step: '越权访问测试', result: '失败', details: '系统未正确阻止越权访问' });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('2. 测试API权限验证', async () => {
|
||||
// 模拟API调用权限验证
|
||||
const apiResponse = await page.request.get('/api/users');
|
||||
|
||||
if (apiResponse.status() === 401 || apiResponse.status() === 403) {
|
||||
testResults.push({ step: 'API权限验证', result: '通过', details: 'API权限验证正常工作' });
|
||||
} else {
|
||||
testResults.push({ step: 'API权限验证', result: '警告', details: 'API权限验证可能需要加强' });
|
||||
}
|
||||
});
|
||||
|
||||
// 生成测试报告
|
||||
console.log('\n=== 权限边界测试报告 ===');
|
||||
testResults.forEach(result => {
|
||||
console.log(`[${result.result}] ${result.step}: ${result.details}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,147 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
|
||||
test.describe('角色权限管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
});
|
||||
|
||||
test('查看角色列表', async ({ page }) => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const roleCount = await page.locator('.el-table__body tr').count();
|
||||
expect(roleCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('角色管理页面导航', async ({ page }) => {
|
||||
await test.step('1. 导航到角色管理页面', async () => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('2. 验证页面标题', async () => {
|
||||
const pageTitle = await page.title();
|
||||
expect(pageTitle).toContain('Novalon 管理系统');
|
||||
});
|
||||
|
||||
await test.step('3. 验证表格结构', async () => {
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const headers = await page.locator('.el-table__header th').count();
|
||||
expect(headers).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('角色搜索功能', async ({ page }) => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const searchInput = page.locator('input[placeholder*="搜索"]').or(page.locator('.search-input'));
|
||||
if (await searchInput.count() > 0) {
|
||||
await searchInput.fill('admin');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('角色详情查看', async ({ page }) => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const firstRow = page.locator('.el-table__body tr').first();
|
||||
await firstRow.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/roles');
|
||||
});
|
||||
|
||||
test('角色管理页面刷新', async ({ page }) => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const tableAfterReload = page.locator('.el-table').first();
|
||||
await expect(tableAfterReload).toBeVisible();
|
||||
});
|
||||
|
||||
test('角色权限验证', async ({ page }) => {
|
||||
await test.step('1. 确认管理员已登录', async () => {
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('2. 访问角色管理页面', async () => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 验证可以查看角色数据', async () => {
|
||||
const roleCount = await page.locator('.el-table__body tr').count();
|
||||
expect(roleCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('4. 验证可以访问其他管理页面', async () => {
|
||||
await page.goto('/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const userTable = page.locator('.el-table').first();
|
||||
await expect(userTable).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('角色管理响应式布局', async ({ page }) => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const mobileTable = page.locator('.el-table').first();
|
||||
await expect(mobileTable).toBeVisible();
|
||||
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const desktopTable = page.locator('.el-table').first();
|
||||
await expect(desktopTable).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,290 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
|
||||
test.describe('E2E安全测试', () => {
|
||||
test('SEC-001: XSS攻击防护测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到用户管理', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
});
|
||||
|
||||
await test.step('3. 测试XSS payload防护', async () => {
|
||||
const xssPayloads = [
|
||||
'<script>alert("XSS")</script>',
|
||||
'<img src=x onerror=alert("XSS")>',
|
||||
'<svg onload=alert("XSS")>',
|
||||
'javascript:alert("XSS")',
|
||||
'<body onload=alert("XSS")>'
|
||||
];
|
||||
|
||||
for (const payload of xssPayloads) {
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `xss_test_${timestamp}`,
|
||||
nickname: payload,
|
||||
email: `xss_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
if (await userManagementPage.isSuccessMessageVisible()) {
|
||||
await userManagementPage.clickEditButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
const pageContent = await page.content();
|
||||
|
||||
expect(pageContent).not.toContain('<script>');
|
||||
expect(pageContent).not.toContain('onerror=');
|
||||
expect(pageContent).not.toContain('onload=');
|
||||
expect(pageContent).not.toContain('javascript:');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('SEC-002: SQL注入防护测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await test.step('1. 测试登录SQL注入防护', async () => {
|
||||
await loginPage.goto();
|
||||
|
||||
const sqlPayloads = [
|
||||
"admin' OR '1'='1",
|
||||
"admin' --",
|
||||
"admin' #",
|
||||
"admin'/*",
|
||||
"admin' or 1=1--",
|
||||
"admin' union select * from users--"
|
||||
];
|
||||
|
||||
for (const payload of sqlPayloads) {
|
||||
await loginPage.usernameInput.fill(payload);
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/login');
|
||||
|
||||
try {
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
expect(errorMessage).toBeTruthy();
|
||||
} catch (error) {
|
||||
expect(currentUrl).toContain('/login');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('SEC-003: 输入验证测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到用户管理', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
});
|
||||
|
||||
await test.step('3. 测试必填字段验证', async () => {
|
||||
await userManagementPage.submitForm();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
const usernameError = await page.locator('.el-form-item__error').filter({ hasText: /用户名/ }).isVisible({ timeout: 2000 });
|
||||
const passwordError = await page.locator('.el-form-item__error').filter({ hasText: /密码/ }).isVisible({ timeout: 2000 });
|
||||
|
||||
expect(usernameError || passwordError).toBeTruthy();
|
||||
} catch (error) {
|
||||
console.log('验证错误消息未显示');
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('4. 测试邮箱格式验证', async () => {
|
||||
const invalidEmails = [
|
||||
'invalid',
|
||||
'@example.com',
|
||||
'test@',
|
||||
'test@.com',
|
||||
'test @example.com'
|
||||
];
|
||||
|
||||
for (const invalidEmail of invalidEmails) {
|
||||
await userManagementPage.fillUserForm({
|
||||
username: `test_${Date.now()}`,
|
||||
email: invalidEmail,
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
const emailError = await page.locator('.el-form-item__error').filter({ hasText: /邮箱/ }).isVisible({ timeout: 2000 });
|
||||
expect(emailError).toBeTruthy();
|
||||
} catch (error) {
|
||||
console.log(`邮箱验证错误未显示: ${invalidEmail}`);
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('5. 测试密码强度验证', async () => {
|
||||
const weakPasswords = [
|
||||
'123',
|
||||
'password',
|
||||
'abc123',
|
||||
'12345678'
|
||||
];
|
||||
|
||||
for (const weakPassword of weakPasswords) {
|
||||
await userManagementPage.fillUserForm({
|
||||
username: `test_${Date.now()}`,
|
||||
email: 'test@example.com',
|
||||
password: weakPassword,
|
||||
confirmPassword: weakPassword,
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
const passwordError = await page.locator('.el-form-item__error').filter({ hasText: /密码/ }).isVisible({ timeout: 2000 });
|
||||
expect(passwordError).toBeTruthy();
|
||||
} catch (error) {
|
||||
console.log(`密码验证错误未显示: ${weakPassword}`);
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('SEC-004: 权限验证测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
await test.step('1. 测试未授权访问', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('test_user', 'test123');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/login');
|
||||
});
|
||||
|
||||
await test.step('2. 测试直接访问受保护页面', async () => {
|
||||
await page.goto('/system/role');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/login');
|
||||
});
|
||||
|
||||
await test.step('3. 测试API权限控制', async () => {
|
||||
const response = await page.request.get('/api/roles');
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
test('SEC-005: CSRF防护测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到用户管理', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
});
|
||||
|
||||
await test.step('3. 测试CSRF token验证', async () => {
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `csrf_test_${timestamp}`,
|
||||
email: `csrf_${timestamp}@example.com`,
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
const csrfInputs = await page.locator('input[name*="csrf"], input[name*="token"], input[name*="_token"]').all();
|
||||
if (csrfInputs.length > 0) {
|
||||
const csrfToken = await csrfInputs[0].inputValue();
|
||||
expect(csrfToken).toBeTruthy();
|
||||
expect(csrfToken.length).toBeGreaterThan(0);
|
||||
} else {
|
||||
console.log('未找到CSRF token输入框');
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('CSRF token验证失败:', error);
|
||||
expect(true).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('SEC-006: 会话管理测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('1. 测试会话超时', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
|
||||
const initialUrl = page.url();
|
||||
expect(initialUrl).toContain('/dashboard');
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/dashboard');
|
||||
});
|
||||
|
||||
await test.step('2. 测试登出功能', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
|
||||
await loginPage.logout();
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('/login');
|
||||
|
||||
await page.goto('/dashboard');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const redirectedUrl = page.url();
|
||||
expect(redirectedUrl).toContain('/login');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,173 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { SystemConfigPage } from './pages/SystemConfigPage';
|
||||
|
||||
test.describe('系统配置E2E测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let configPage: SystemConfigPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
configPage = new SystemConfigPage(page);
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await loginPage.logout();
|
||||
});
|
||||
|
||||
test('系统配置页面导航', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
await expect(page).toHaveURL(/.*config/);
|
||||
});
|
||||
|
||||
await test.step('验证页面元素可见', async () => {
|
||||
await expect(configPage.table).toBeVisible();
|
||||
await expect(configPage.addButton).toBeVisible();
|
||||
await expect(configPage.searchInput).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('创建系统配置', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('创建新系统配置', async () => {
|
||||
const configName = `测试配置_${Date.now()}`;
|
||||
const configKey = `test.config.${Date.now()}`;
|
||||
const configValue = `test_value_${Date.now()}`;
|
||||
|
||||
await configPage.addConfig(configName, configKey, configValue);
|
||||
|
||||
await configPage.verifyTableContains(configName);
|
||||
});
|
||||
});
|
||||
|
||||
test('编辑系统配置', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('编辑现有系统配置', async () => {
|
||||
const configKey = 'system.site.name';
|
||||
const newValue = `Novalon管理系统_${Date.now()}`;
|
||||
|
||||
await configPage.editConfig(configKey, newValue);
|
||||
|
||||
await configPage.verifyTableContains(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
test('删除系统配置', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('删除系统配置', async () => {
|
||||
const configName = `测试配置_${Date.now()}`;
|
||||
const configKey = `test.config.${Date.now()}`;
|
||||
const configValue = `test_value_${Date.now()}`;
|
||||
|
||||
await configPage.addConfig(configName, configKey, configValue);
|
||||
await configPage.verifyTableContains(configName);
|
||||
|
||||
await configPage.deleteConfig(configKey);
|
||||
await configPage.verifyTableNotContains(configName);
|
||||
});
|
||||
});
|
||||
|
||||
test('搜索系统配置', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('搜索系统配置', async () => {
|
||||
const configName = '系统名称';
|
||||
|
||||
await configPage.searchConfig(configName);
|
||||
await configPage.verifyTableContains(configName);
|
||||
});
|
||||
|
||||
await test.step('清除搜索', async () => {
|
||||
await configPage.clearSearch();
|
||||
const rowCount = await configPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('系统配置分页功能', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证表格数据加载', async () => {
|
||||
const rowCount = await configPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('系统配置响应式布局', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证桌面端布局', async () => {
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await expect(configPage.table).toBeVisible();
|
||||
await expect(configPage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证平板端布局', async () => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(configPage.table).toBeVisible();
|
||||
await expect(configPage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证移动端布局', async () => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await expect(configPage.table).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('系统配置权限验证', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证添加按钮可见性', async () => {
|
||||
await expect(configPage.addButton).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('验证编辑和删除按钮可见性', async () => {
|
||||
const rows = await configPage.table.locator('.el-table__row').count();
|
||||
if (rows > 0) {
|
||||
await expect(configPage.table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('系统配置数据验证', async ({ page }) => {
|
||||
await test.step('导航到系统配置页面', async () => {
|
||||
await configPage.goto();
|
||||
});
|
||||
|
||||
await test.step('验证配置键名唯一性', async () => {
|
||||
const configName = `测试配置_${Date.now()}`;
|
||||
const configKey = `test.config.${Date.now()}`;
|
||||
const configValue = `test_value_${Date.now()}`;
|
||||
|
||||
await configPage.addConfig(configName, configKey, configValue);
|
||||
await configPage.verifyTableContains(configName);
|
||||
});
|
||||
|
||||
await test.step('验证配置值格式正确', async () => {
|
||||
const rows = await configPage.table.locator('.el-table__row').count();
|
||||
expect(rows).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,884 +0,0 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
import { MenuManagementPage } from './pages/MenuManagementPage';
|
||||
import { OperationLogPage } from './pages/OperationLogPage';
|
||||
import { DictionaryManagementPage } from './pages/DictionaryManagementPage';
|
||||
import { SystemConfigPage } from './pages/SystemConfigPage';
|
||||
import { FileManagementPage } from './pages/FileManagementPage';
|
||||
|
||||
test.describe('系统全面集成测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
let roleManagementPage: RoleManagementPage;
|
||||
let menuManagementPage: MenuManagementPage;
|
||||
let operationLogPage: OperationLogPage;
|
||||
let dictionaryManagementPage: DictionaryManagementPage;
|
||||
let systemConfigPage: SystemConfigPage;
|
||||
let fileManagementPage: FileManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// 确保页面已经导航到正确的URL,避免localStorage访问错误
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
loginPage = new LoginPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
roleManagementPage = new RoleManagementPage(page);
|
||||
menuManagementPage = new MenuManagementPage(page);
|
||||
operationLogPage = new OperationLogPage(page);
|
||||
dictionaryManagementPage = new DictionaryManagementPage(page);
|
||||
systemConfigPage = new SystemConfigPage(page);
|
||||
fileManagementPage = new FileManagementPage(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// 清理localStorage,确保测试隔离
|
||||
await page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
// 检查后端服务健康状态
|
||||
try {
|
||||
const response = await fetch('http://localhost:8084/actuator/health', {
|
||||
signal: AbortSignal.timeout(5000)
|
||||
} as any);
|
||||
if (!response.ok) {
|
||||
console.log('⚠️ 后端服务健康检查失败,等待恢复...');
|
||||
await page.waitForTimeout(5000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 后端服务无响应,等待恢复...');
|
||||
await page.waitForTimeout(5000);
|
||||
}
|
||||
|
||||
// 增加测试间隔,让后端服务有时间恢复
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test.describe('1. 用户认证流程测试', () => {
|
||||
test('1.1 正确的用户名和密码登录成功', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
await expect(page.locator('.dashboard')).toBeVisible();
|
||||
});
|
||||
|
||||
test('1.2 错误的密码登录失败', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('wrongpassword');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('1.3 不存在的用户登录失败', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('nonexistent');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('1.4 空用户名或密码登录失败', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await expect(page.locator('.el-form-item__error')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('1.5 禁用用户登录失败', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('disableduser');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('1.6 登出功能正常', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await page.locator('.el-avatar').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.el-dropdown-menu').getByText('退出登录').click();
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('2. 用户管理流程测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('2.1 查询用户列表', async ({ page }) => {
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
|
||||
await expect(userManagementPage.table).toBeVisible({ timeout: 5000 });
|
||||
const userCount = await userManagementPage.getUserCount();
|
||||
expect(userCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('2.2 创建新用户', async ({ page }) => {
|
||||
const uuid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const username = `u_${uuid}`;
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: username,
|
||||
password: 'admin123',
|
||||
email: `${username}@test.com`,
|
||||
phone: '13800138000',
|
||||
nickname: `测试用户${Date.now()}`
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
|
||||
await userManagementPage.search(username);
|
||||
await page.waitForTimeout(1000);
|
||||
const found = await userManagementPage.containsText(username);
|
||||
expect(found).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2.3 编辑用户信息', async ({ page }) => {
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
|
||||
// 不要编辑admin用户(第1行),否则可能影响后续测试
|
||||
// 编辑第2行的用户
|
||||
await userManagementPage.clickEditButton(2);
|
||||
|
||||
const newNickname = `更新昵称_${Date.now()}`;
|
||||
await userManagementPage.fillNickname(newNickname);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('2.4 删除用户', async ({ page }) => {
|
||||
const uuid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const username = `del_${uuid}`;
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: username,
|
||||
password: 'admin123',
|
||||
email: `${username}@test.com`,
|
||||
phone: '13800138000',
|
||||
nickname: `待删除用户${Date.now()}`
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const createSuccess = await userManagementPage.waitForSuccessMessage();
|
||||
expect(createSuccess).toBeTruthy();
|
||||
|
||||
await userManagementPage.search(username);
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickDeleteButton(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
|
||||
const deleteSuccess = await userManagementPage.waitForSuccessMessage();
|
||||
expect(deleteSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2.5 分配用户角色', async ({ page }) => {
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
|
||||
// 不要编辑admin用户(第1行),否则可能影响后续测试
|
||||
// 编辑第2行的用户
|
||||
await userManagementPage.clickEditButton(2);
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2.6 启用/禁用用户', async ({ page }) => {
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.waitForTableReady();
|
||||
|
||||
// 不要禁用admin用户(第1行)和testadmin用户(第2行),否则后续测试无法登录
|
||||
// 使用第3行的用户进行测试
|
||||
await userManagementPage.clickStatusButton(3);
|
||||
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('3. 角色管理流程测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('3.1 查询角色列表', async ({ page }) => {
|
||||
await roleManagementPage.goto();
|
||||
await roleManagementPage.waitForTableReady();
|
||||
|
||||
await expect(roleManagementPage.table).toBeVisible({ timeout: 5000 });
|
||||
const roleCount = await roleManagementPage.table.locator('tbody tr').count();
|
||||
expect(roleCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('3.2 创建新角色', async ({ page }) => {
|
||||
const uuid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const roleName = `角色_${uuid}`;
|
||||
const roleKey = `r_${uuid}`;
|
||||
|
||||
await roleManagementPage.goto();
|
||||
await roleManagementPage.waitForTableReady();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
await roleManagementPage.fillRoleForm({
|
||||
roleName: roleName,
|
||||
roleKey: roleKey,
|
||||
roleSort: '99'
|
||||
});
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
|
||||
await roleManagementPage.search(roleName);
|
||||
await page.waitForTimeout(1000);
|
||||
const found = await roleManagementPage.containsText(roleName);
|
||||
expect(found).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3.3 编辑角色', async ({ page }) => {
|
||||
await roleManagementPage.goto();
|
||||
await roleManagementPage.waitForTableReady();
|
||||
|
||||
await roleManagementPage.editRole(1);
|
||||
|
||||
const uuid = Math.random().toString(36).substring(2, 15);
|
||||
const newRoleName = `更新_${uuid}`;
|
||||
await page.locator('.el-dialog').locator('input').first().fill(newRoleName);
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3.4 删除角色', async ({ page }) => {
|
||||
const uuid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const roleName = `删除_${uuid}`;
|
||||
const roleKey = `d_${uuid}`;
|
||||
|
||||
await roleManagementPage.goto();
|
||||
await roleManagementPage.waitForTableReady();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
await roleManagementPage.fillRoleForm({
|
||||
roleName: roleName,
|
||||
roleKey: roleKey,
|
||||
roleSort: '99'
|
||||
});
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
const createSuccess = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(createSuccess).toBeTruthy();
|
||||
|
||||
await roleManagementPage.search(roleName);
|
||||
await page.waitForTimeout(1000);
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
|
||||
const deleteSuccess = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(deleteSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3.5 分配角色权限', async ({ page }) => {
|
||||
await roleManagementPage.goto();
|
||||
await roleManagementPage.waitForTableReady();
|
||||
|
||||
await roleManagementPage.clickPermissionButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const permissionCheckbox = page.locator('.el-tree').locator('input[type="checkbox"]').first();
|
||||
if (await permissionCheckbox.count() > 0) {
|
||||
await permissionCheckbox.click();
|
||||
}
|
||||
|
||||
await roleManagementPage.savePermissions();
|
||||
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('4. 菜单管理流程测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('4.1 查询菜单树', async ({ page }) => {
|
||||
await menuManagementPage.goto();
|
||||
|
||||
await expect(page.locator('.menu-tree')).toBeVisible({ timeout: 5000 });
|
||||
const menuCount = await page.locator('.menu-node').count();
|
||||
expect(menuCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('4.2 创建新菜单', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
const menuName = `测试菜单_${timestamp}`;
|
||||
|
||||
await menuManagementPage.goto();
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
await menuManagementPage.fillMenuForm({
|
||||
menuName: menuName,
|
||||
path: `/test-${timestamp}`,
|
||||
component: 'test/index',
|
||||
menuType: 'C',
|
||||
orderNum: '99'
|
||||
});
|
||||
await menuManagementPage.submitMenuForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('4.3 编辑菜单', async ({ page }) => {
|
||||
await menuManagementPage.goto();
|
||||
|
||||
const firstMenu = page.locator('.menu-node').first();
|
||||
await firstMenu.locator('[data-testid="edit-button"]').click();
|
||||
|
||||
const newMenuName = `更新菜单_${Date.now()}`;
|
||||
await page.fill('[name="menuName"]', newMenuName);
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('4.4 删除菜单', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
const menuName = `待删除菜单_${timestamp}`;
|
||||
|
||||
await menuManagementPage.goto();
|
||||
await menuManagementPage.clickCreateMenu();
|
||||
await menuManagementPage.fillMenuForm({
|
||||
menuName: menuName,
|
||||
path: `/delete-${timestamp}`,
|
||||
component: 'delete/index',
|
||||
menuType: 'C',
|
||||
orderNum: '99'
|
||||
});
|
||||
await menuManagementPage.submitMenuForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const menuNode = page.locator(`.menu-node:has-text("${menuName}")`).first();
|
||||
await menuNode.locator('[data-testid="delete-button"]').click();
|
||||
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.click('[data-testid="confirm-delete-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('5. 权限验证测试', () => {
|
||||
test('5.1 管理员可以访问所有功能', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
await expect(page.locator('.user-table')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await roleManagementPage.goto();
|
||||
await expect(page.locator('.role-table')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await menuManagementPage.goto();
|
||||
await expect(page.locator('.menu-tree')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('5.2 普通用户只能访问授权功能', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('normaluser', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await page.goto('/user-management');
|
||||
|
||||
const hasAccess = await page.locator('.user-table').isVisible().catch(() => false);
|
||||
|
||||
if (!hasAccess) {
|
||||
await expect(page.locator('.no-permission')).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('5.3 未登录用户访问受保护页面跳转到登录页', async ({ page }) => {
|
||||
await page.goto('/user-management');
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('6. 操作日志测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('6.1 查询操作日志列表', async ({ page }) => {
|
||||
await operationLogPage.goto();
|
||||
|
||||
await expect(page.locator('.log-table')).toBeVisible({ timeout: 5000 });
|
||||
const logCount = await page.locator('.log-row').count();
|
||||
expect(logCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('6.2 按时间范围查询日志', async ({ page }) => {
|
||||
await operationLogPage.goto();
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
await page.fill('[name="startDate"]', today);
|
||||
await page.fill('[name="endDate"]', today);
|
||||
await page.click('[data-testid="search-button"]');
|
||||
|
||||
await expect(page.locator('.log-row').first()).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('6.3 按用户查询日志', async ({ page }) => {
|
||||
await operationLogPage.goto();
|
||||
|
||||
await page.fill('[name="username"]', 'admin');
|
||||
await page.click('[data-testid="search-button"]');
|
||||
|
||||
await expect(page.locator('.log-row').first()).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.locator('.log-row').first()).toContainText('admin');
|
||||
});
|
||||
|
||||
test('6.4 导出操作日志', async ({ page }) => {
|
||||
await operationLogPage.goto();
|
||||
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.click('[data-testid="export-button"]')
|
||||
]);
|
||||
|
||||
expect(download.suggestedFilename()).toContain('.xlsx');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('7. 字典管理测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('7.1 查询字典类型列表', async ({ page }) => {
|
||||
await dictionaryManagementPage.goto();
|
||||
|
||||
await expect(page.locator('.dict-type-table')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('7.2 创建字典类型', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
const dictName = `测试字典_${timestamp}`;
|
||||
const dictType = `test_dict_${timestamp}`;
|
||||
|
||||
await dictionaryManagementPage.goto();
|
||||
await dictionaryManagementPage.clickCreateDictType();
|
||||
await dictionaryManagementPage.fillDictTypeForm({
|
||||
dictName: dictName,
|
||||
dictType: dictType
|
||||
});
|
||||
await dictionaryManagementPage.submitDictTypeForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('7.3 查询字典数据', async ({ page }) => {
|
||||
await dictionaryManagementPage.goto();
|
||||
|
||||
const firstDictType = page.locator('.dict-type-row').first();
|
||||
await firstDictType.click();
|
||||
|
||||
await expect(page.locator('.dict-data-table')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('7.4 创建字典数据', async ({ page }) => {
|
||||
await dictionaryManagementPage.goto();
|
||||
|
||||
const firstDictType = page.locator('.dict-type-row').first();
|
||||
await firstDictType.click();
|
||||
|
||||
await dictionaryManagementPage.clickCreateDictData();
|
||||
await dictionaryManagementPage.fillDictDataForm({
|
||||
dictLabel: `测试数据_${Date.now()}`,
|
||||
dictValue: `test_value_${Date.now()}`,
|
||||
dictSort: '99'
|
||||
});
|
||||
await dictionaryManagementPage.submitDictDataForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('8. 系统配置测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('8.1 查询系统配置列表', async ({ page }) => {
|
||||
await systemConfigPage.goto();
|
||||
|
||||
await expect(page.locator('.config-table')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('8.2 创建系统配置', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
const configKey = `test.config.${timestamp}`;
|
||||
const configValue = `test_value_${timestamp}`;
|
||||
|
||||
await systemConfigPage.goto();
|
||||
await systemConfigPage.clickCreateConfig();
|
||||
await systemConfigPage.fillConfigForm({
|
||||
configKey: configKey,
|
||||
configValue: configValue,
|
||||
configName: `测试配置_${timestamp}`
|
||||
});
|
||||
await systemConfigPage.submitConfigForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('8.3 编辑系统配置', async ({ page }) => {
|
||||
await systemConfigPage.goto();
|
||||
|
||||
const firstConfig = page.locator('.config-row').first();
|
||||
await firstConfig.locator('[data-testid="edit-button"]').click();
|
||||
|
||||
const newValue = `updated_value_${Date.now()}`;
|
||||
await page.fill('[name="configValue"]', newValue);
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('8.4 刷新配置缓存', async ({ page }) => {
|
||||
await systemConfigPage.goto();
|
||||
|
||||
await page.click('[data-testid="refresh-cache-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('9. 文件管理测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('9.1 上传文件', async ({ page }) => {
|
||||
await fileManagementPage.goto();
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles({
|
||||
name: 'test-file.txt',
|
||||
mimeType: 'text/plain',
|
||||
buffer: Buffer.from('This is a test file')
|
||||
});
|
||||
|
||||
await page.click('[data-testid="upload-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('9.2 查询文件列表', async ({ page }) => {
|
||||
await fileManagementPage.goto();
|
||||
|
||||
await expect(page.locator('.file-table')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('9.3 下载文件', async ({ page }) => {
|
||||
await fileManagementPage.goto();
|
||||
|
||||
const firstFile = page.locator('.file-row').first();
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
firstFile.locator('[data-testid="download-button"]').click()
|
||||
]);
|
||||
|
||||
expect(download).toBeTruthy();
|
||||
});
|
||||
|
||||
test('9.4 删除文件', async ({ page }) => {
|
||||
await fileManagementPage.goto();
|
||||
|
||||
const firstFile = page.locator('.file-row').first();
|
||||
await firstFile.locator('[data-testid="delete-button"]').click();
|
||||
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.click('[data-testid="confirm-delete-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('9.5 预览文件', async ({ page }) => {
|
||||
await fileManagementPage.goto();
|
||||
|
||||
const firstFile = page.locator('.file-row').first();
|
||||
await firstFile.locator('[data-testid="preview-button"]').click();
|
||||
|
||||
await expect(page.locator('.file-preview-modal')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('10. 异常场景测试', () => {
|
||||
test('10.1 网络错误处理', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await page.route('**/api/**', route => route.abort('failed'));
|
||||
|
||||
await userManagementPage.goto();
|
||||
|
||||
await expect(page.locator('.error-message')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('10.2 并发操作处理', async ({ page, context }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const page2 = await context.newPage();
|
||||
const loginPage2 = new LoginPage(page2);
|
||||
await loginPage2.goto();
|
||||
await loginPage2.login('admin', 'admin123');
|
||||
await page2.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
await page2.goto('/user-management');
|
||||
|
||||
await expect(page.locator('.user-table')).toBeVisible({ timeout: 5000 });
|
||||
await expect(page2.locator('.user-table')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await page2.close();
|
||||
});
|
||||
|
||||
test('10.3 数据验证错误', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: '',
|
||||
password: '123',
|
||||
email: 'invalid-email',
|
||||
phone: 'invalid-phone',
|
||||
nickname: ''
|
||||
});
|
||||
await userManagementPage.submitUserForm();
|
||||
|
||||
await expect(page.locator('.error-message')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('10.4 会话超时处理', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.removeItem('token');
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page).toHaveURL(/.*login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('10.5 权限不足操作', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('normaluser', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const response = await page.request.post('/api/users', {
|
||||
data: {
|
||||
username: 'test',
|
||||
password: 'test123'
|
||||
}
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('11. 性能测试', () => {
|
||||
test('11.1 页面加载性能', async ({ page }) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
expect(loadTime).toBeLessThan(5000);
|
||||
});
|
||||
|
||||
test('11.2 大数据量查询性能', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
await operationLogPage.goto();
|
||||
await expect(page.locator('.log-table')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const queryTime = Date.now() - startTime;
|
||||
|
||||
expect(queryTime).toBeLessThan(3000);
|
||||
});
|
||||
|
||||
test('11.3 并发请求处理', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const requests = Array(10).fill(null).map(() =>
|
||||
page.request.get('/api/users')
|
||||
);
|
||||
|
||||
const responses = await Promise.all(requests);
|
||||
|
||||
responses.forEach(response => {
|
||||
expect(response.status()).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('12. 数据一致性测试', () => {
|
||||
test('12.1 创建后立即查询数据一致性', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const timestamp = Date.now();
|
||||
const username = `consistency_test_${timestamp}`;
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: username,
|
||||
password: 'admin123',
|
||||
email: `${username}@test.com`,
|
||||
phone: '13800138000',
|
||||
nickname: `一致性测试用户${timestamp}`
|
||||
});
|
||||
await userManagementPage.submitUserForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await userManagementPage.searchUser(username);
|
||||
const userRow = page.locator('.user-row').first();
|
||||
|
||||
await expect(userRow).toContainText(username);
|
||||
await expect(userRow).toContainText(`${username}@test.com`);
|
||||
await expect(userRow).toContainText('13800138000');
|
||||
});
|
||||
|
||||
test('12.2 更新后数据一致性', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
await userManagementPage.goto();
|
||||
|
||||
const firstUser = page.locator('.user-row').first();
|
||||
await firstUser.locator('[data-testid="edit-button"]').click();
|
||||
|
||||
const newEmail = `updated_${Date.now()}@test.com`;
|
||||
const newPhone = `139${Date.now()}`.slice(0, 11);
|
||||
|
||||
await page.fill('[name="email"]', newEmail);
|
||||
await page.fill('[name="phone"]', newPhone);
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await page.reload();
|
||||
|
||||
const updatedUser = page.locator('.user-row').first();
|
||||
await expect(updatedUser).toContainText(newEmail);
|
||||
await expect(updatedUser).toContainText(newPhone);
|
||||
});
|
||||
|
||||
test('12.3 删除后数据不可见', async ({ page }) => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 10000 });
|
||||
|
||||
const timestamp = Date.now();
|
||||
const username = `delete_test_${timestamp}`;
|
||||
|
||||
await userManagementPage.goto();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: username,
|
||||
password: 'admin123',
|
||||
email: `${username}@test.com`,
|
||||
phone: '13800138000',
|
||||
nickname: `删除测试用户${timestamp}`
|
||||
});
|
||||
await userManagementPage.submitUserForm();
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await userManagementPage.searchUser(username);
|
||||
const userRow = page.locator('.user-row').first();
|
||||
await userRow.locator('[data-testid="delete-button"]').click();
|
||||
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.click('[data-testid="confirm-delete-button"]');
|
||||
|
||||
await expect(page.locator('.success-message')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
await userManagementPage.searchUser(username);
|
||||
await expect(page.locator('.user-row')).toHaveCount(0, { timeout: 5000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('API测试:检查系统配置API', async ({ request }) => {
|
||||
console.log('开始测试系统配置API...');
|
||||
|
||||
// 1. 先登录获取token
|
||||
const loginResponse = await request.post('http://localhost:8084/api/auth/login', {
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('登录响应状态:', loginResponse.status());
|
||||
const loginData = await loginResponse.json();
|
||||
console.log('登录响应数据:', JSON.stringify(loginData, null, 2));
|
||||
|
||||
expect(loginResponse.status()).toBe(200);
|
||||
|
||||
// 2. 获取token
|
||||
const token = loginData.token || loginData.data?.token;
|
||||
console.log('获取到的token:', token ? token.substring(0, 20) + '...' : '未找到');
|
||||
|
||||
// 3. 使用token访问系统配置API
|
||||
const configResponse = await request.get('http://localhost:8084/api/config', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('系统配置API响应状态:', configResponse.status());
|
||||
const configData = await configResponse.json();
|
||||
console.log('系统配置数据:', JSON.stringify(configData, null, 2));
|
||||
|
||||
expect(configResponse.status()).toBe(200);
|
||||
});
|
||||
@@ -1,306 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { TestDataCleanup } from './utils/TestDataCleanup';
|
||||
import { TestHelpers } from './utils/TestHelpers';
|
||||
|
||||
test.describe('测试稳定性增强验证', () => {
|
||||
let testDataCleanup: TestDataCleanup;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
testDataCleanup = new TestDataCleanup(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await testDataCleanup.cleanupAll();
|
||||
});
|
||||
|
||||
test('STAB-001: 页面加载稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('1. 测试登录页面加载稳定性', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await loginPage.goto();
|
||||
await TestHelpers.waitForPageLoad(page);
|
||||
|
||||
const isUsernameVisible = await TestHelpers.isElementVisible(loginPage.usernameInput);
|
||||
const isPasswordVisible = await TestHelpers.isElementVisible(loginPage.passwordInput);
|
||||
const isLoginButtonVisible = await TestHelpers.isElementVisible(loginPage.loginButton);
|
||||
|
||||
expect(isUsernameVisible).toBeTruthy();
|
||||
expect(isPasswordVisible).toBeTruthy();
|
||||
expect(isLoginButtonVisible).toBeTruthy();
|
||||
|
||||
await page.reload();
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('2. 测试登录流程稳定性', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
const navigationSuccess = await TestHelpers.waitForNavigation(page, /.*dashboard/);
|
||||
expect(navigationSuccess).toBeTruthy();
|
||||
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
});
|
||||
|
||||
await test.step('3. 测试Dashboard页面加载稳定性', async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await page.goto('/dashboard');
|
||||
await TestHelpers.waitForPageLoad(page);
|
||||
|
||||
const dashboardContent = page.locator('.dashboard');
|
||||
const isVisible = await TestHelpers.isElementVisible(dashboardContent);
|
||||
expect(isVisible).toBeTruthy();
|
||||
|
||||
await page.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('STAB-002: 元素交互稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await TestHelpers.waitForNavigation(page, /.*dashboard/);
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
});
|
||||
|
||||
await test.step('2. 测试按钮点击稳定性', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
const createUserButton = userManagementPage.createUserButton;
|
||||
const clickSuccess = await TestHelpers.safeClick(createUserButton);
|
||||
expect(clickSuccess).toBeTruthy();
|
||||
|
||||
await TestHelpers.waitForModal(page);
|
||||
const modalVisible = await TestHelpers.waitForModal(page);
|
||||
expect(modalVisible).toBeTruthy();
|
||||
|
||||
await TestHelpers.closeModal(page);
|
||||
});
|
||||
|
||||
await test.step('3. 测试表单输入稳定性', async () => {
|
||||
await userManagementPage.clickCreateUser();
|
||||
await TestHelpers.waitForModal(page);
|
||||
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `stab_user_${timestamp}`,
|
||||
nickname: `稳定性测试用户${timestamp}`,
|
||||
email: `stab_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
testDataCleanup.trackUser(userData.username);
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const successVisible = await TestHelpers.waitForSuccessMessage(page, 5000);
|
||||
expect(successVisible).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('STAB-003: 网络请求稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await TestHelpers.waitForNavigation(page, /.*dashboard/);
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
});
|
||||
|
||||
await test.step('2. 测试API请求重试机制', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
const initialRowCount = await userManagementPage.getTableRowCount();
|
||||
expect(initialRowCount).toBeGreaterThanOrEqual(0);
|
||||
|
||||
await userManagementPage.searchInput.fill('admin');
|
||||
await userManagementPage.searchButton.click();
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
const searchRowCount = await userManagementPage.getTableRowCount();
|
||||
expect(searchRowCount).toBeGreaterThanOrEqual(0);
|
||||
|
||||
await userManagementPage.clearSearch();
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
const finalRowCount = await userManagementPage.getTableRowCount();
|
||||
expect(finalRowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('STAB-004: 等待策略稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await test.step('1. 测试元素可见性等待', async () => {
|
||||
await loginPage.goto();
|
||||
|
||||
const usernameVisible = await TestHelpers.waitForElementVisible(loginPage.usernameInput, 5000);
|
||||
expect(usernameVisible).toBeTruthy();
|
||||
|
||||
const passwordVisible = await TestHelpers.waitForElementVisible(loginPage.passwordInput, 5000);
|
||||
expect(passwordVisible).toBeTruthy();
|
||||
|
||||
const loginButtonVisible = await TestHelpers.waitForElementVisible(loginPage.loginButton, 5000);
|
||||
expect(loginButtonVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
await test.step('2. 测试网络空闲等待', async () => {
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
|
||||
await TestHelpers.waitForNetworkIdle(page, 10000);
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('dashboard');
|
||||
});
|
||||
|
||||
await test.step('3. 测试加载完成等待', async () => {
|
||||
await page.goto('/dashboard');
|
||||
await TestHelpers.waitForPageLoad(page, 10000);
|
||||
|
||||
const dashboardContent = page.locator('.dashboard');
|
||||
const isVisible = await TestHelpers.isElementVisible(dashboardContent);
|
||||
expect(isVisible).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('STAB-005: 错误处理稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 测试无效登录处理', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('invalid_user');
|
||||
await loginPage.passwordInput.fill('invalid_password');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('login');
|
||||
});
|
||||
|
||||
await test.step('2. 测试表单验证错误处理', async () => {
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await TestHelpers.waitForNavigation(page, /.*dashboard/);
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickCreateUser();
|
||||
await TestHelpers.waitForModal(page);
|
||||
|
||||
await userManagementPage.submitForm();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const errorMessageVisible = await TestHelpers.waitForErrorMessage(page, 3000);
|
||||
expect(errorMessageVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
await test.step('3. 测试网络错误处理', async () => {
|
||||
await TestHelpers.closeModal(page);
|
||||
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `error_test_${timestamp}`,
|
||||
nickname: `错误测试用户${timestamp}`,
|
||||
email: `error_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
testDataCleanup.trackUser(userData.username);
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
const successVisible = await TestHelpers.waitForSuccessMessage(page, 5000);
|
||||
expect(successVisible).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('STAB-006: 重试机制稳定性测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 登录系统', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await TestHelpers.waitForNavigation(page, /.*dashboard/);
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
});
|
||||
|
||||
await test.step('2. 测试操作重试机制', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await TestHelpers.waitForNetworkIdle(page);
|
||||
|
||||
const clickResult = await TestHelpers.retryOperation(
|
||||
async () => {
|
||||
await userManagementPage.clickCreateUser();
|
||||
return true;
|
||||
},
|
||||
3,
|
||||
1000
|
||||
);
|
||||
|
||||
expect(clickResult).toBeTruthy();
|
||||
});
|
||||
|
||||
await test.step('3. 测试表单提交重试机制', async () => {
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `retry_test_${timestamp}`,
|
||||
nickname: `重试测试用户${timestamp}`,
|
||||
email: `retry_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
testDataCleanup.trackUser(userData.username);
|
||||
|
||||
const submitResult = await TestHelpers.retryOperation(
|
||||
async () => {
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
const successVisible = await TestHelpers.waitForSuccessMessage(page, 3000);
|
||||
return successVisible;
|
||||
},
|
||||
2,
|
||||
2000
|
||||
);
|
||||
|
||||
expect(submitResult).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { FileManagementPage } from './pages/FileManagementPage';
|
||||
|
||||
test.describe('UAT文件管理流程测试', () => {
|
||||
test('UAT-FILE-001: 文件上传下载完整流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const fileManagementPage = new FileManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到文件管理', async () => {
|
||||
await dashboardPage.navigateToFileManagement();
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 上传文件', async () => {
|
||||
await fileManagementPage.clickUploadButton();
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles('./e2e/fixtures/test-file.txt');
|
||||
|
||||
await fileManagementPage.submitUpload();
|
||||
await expect(fileManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 验证文件列表', async () => {
|
||||
const rowCount = await fileManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('5. 下载文件', async () => {
|
||||
await fileManagementPage.clickDownloadButton(1);
|
||||
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-FILE-002: 文件删除流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const fileManagementPage = new FileManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到文件管理', async () => {
|
||||
await dashboardPage.navigateToFileManagement();
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 删除文件', async () => {
|
||||
await fileManagementPage.clickDeleteButton(1);
|
||||
await page.on('dialog', dialog => dialog.accept());
|
||||
await expect(fileManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-FILE-003: 文件搜索和过滤', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const fileManagementPage = new FileManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到文件管理', async () => {
|
||||
await dashboardPage.navigateToFileManagement();
|
||||
await expect(fileManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 搜索文件', async () => {
|
||||
await fileManagementPage.searchInput.fill('test');
|
||||
await fileManagementPage.searchButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const rowCount = await fileManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { RoleManagementPage } from './pages/RoleManagementPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
|
||||
test.describe('UAT权限分配流程测试', () => {
|
||||
test('UAT-PERM-001: 权限分配完整流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const roleManagementPage = new RoleManagementPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 创建新角色', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await roleManagementPage.clickCreateRole();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const roleData = {
|
||||
roleName: `UAT角色_${timestamp}`,
|
||||
roleKey: `uat_role_${timestamp}`,
|
||||
roleSort: '1',
|
||||
status: '1',
|
||||
remark: 'UAT测试角色'
|
||||
};
|
||||
|
||||
await roleManagementPage.fillRoleForm(roleData);
|
||||
await roleManagementPage.submitForm();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 为角色分配权限', async () => {
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.selectPermission('system:user:view');
|
||||
await roleManagementPage.selectPermission('system:user:add');
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 为用户分配角色', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await userManagementPage.clickEditButton(1);
|
||||
await userManagementPage.selectRole('UAT角色');
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-PERM-002: 角色权限验证', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const roleManagementPage = new RoleManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到角色管理', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await expect(roleManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 验证角色权限显示', async () => {
|
||||
await roleManagementPage.clickPermissionButton(1);
|
||||
await expect(page.locator('.permission-dialog')).toBeVisible();
|
||||
|
||||
const permissionCount = await page.locator('.permission-checkbox').count();
|
||||
expect(permissionCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-PERM-003: 权限撤销流程', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const roleManagementPage = new RoleManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
await page.waitForURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 导航到角色管理', async () => {
|
||||
await dashboardPage.navigateToRoleManagement();
|
||||
await expect(roleManagementPage.table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 撤销角色权限', async () => {
|
||||
await roleManagementPage.openPermissionDialog(1);
|
||||
await roleManagementPage.deselectPermission('system:user:delete');
|
||||
await roleManagementPage.submitPermissions();
|
||||
await expect(roleManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { TestDataCleanup } from './utils/TestDataCleanup';
|
||||
|
||||
test.describe('UAT用户管理完整流程测试', () => {
|
||||
let testDataCleanup: TestDataCleanup;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
testDataCleanup = new TestDataCleanup(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await testDataCleanup.cleanupAll();
|
||||
});
|
||||
|
||||
test('UAT-USER-001: 用户管理完整生命周期', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('2. 创建新用户', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await page.waitForTimeout(500);
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `uat_user_${timestamp}`,
|
||||
nickname: `UAT测试用户${timestamp}`,
|
||||
email: `uat_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
testDataCleanup.trackUser(userData.username);
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
try {
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
} catch (error) {
|
||||
console.log('创建用户成功消息未显示,继续执行测试');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('3. 编辑用户信息', async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickEditButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const updatedNickname = `更新用户_${Date.now()}`;
|
||||
await userManagementPage.fillNickname(updatedNickname);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
try {
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
} catch (error) {
|
||||
console.log('编辑用户成功消息未显示,继续执行测试');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('4. 删除用户', async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickDeleteButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
|
||||
try {
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
} catch (error) {
|
||||
console.log('删除用户成功消息未显示,继续执行测试');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-USER-002: 用户搜索和过滤', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('2. 导航到用户管理', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(userManagementPage.table).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('3. 搜索用户', async () => {
|
||||
await userManagementPage.searchInput.fill('admin');
|
||||
await userManagementPage.searchButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const rowCount = await userManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('4. 清除搜索', async () => {
|
||||
await userManagementPage.clearSearch();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const rowCount = await userManagementPage.getTableRowCount();
|
||||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('UAT-USER-003: 用户状态管理', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('admin123');
|
||||
await loginPage.loginButton.click();
|
||||
await page.waitForURL(/.*dashboard/, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('2. 导航到用户管理', async () => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(userManagementPage.table).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('3. 禁用用户', async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickStatusButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
} catch (error) {
|
||||
console.log('禁用用户成功消息未显示,继续执行测试');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('4. 启用用户', async () => {
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickStatusButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
try {
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
} catch (error) {
|
||||
console.log('启用用户成功消息未显示,继续执行测试');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,173 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
|
||||
test.describe('用户生命周期 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
});
|
||||
|
||||
test('完整用户生命周期:登录 -> 查看用户列表 -> 登出', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 管理员登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('2. 查看用户列表', async () => {
|
||||
await page.goto('/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
const userCount = await page.locator('.el-table__body tr').count();
|
||||
expect(userCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('3. 用户登出', async () => {
|
||||
await loginPage.logout();
|
||||
|
||||
await expect(page).toHaveURL(/.*login/);
|
||||
const isLoggedOut = !(await loginPage.isLoggedIn());
|
||||
expect(isLoggedOut).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('4. 验证登出后重新登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('用户登录成功场景:正确密码', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await test.step('1. 使用正确密码登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('2. 验证可以访问用户管理页面', async () => {
|
||||
await page.goto('/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
const userCount = await page.locator('.el-table__body tr').count();
|
||||
expect(userCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await test.step('3. 验证可以访问角色管理页面', async () => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
const roleCount = await page.locator('.el-table__body tr').count();
|
||||
expect(roleCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('用户会话管理:验证登录状态持久性', async ({ page }) => {
|
||||
await test.step('1. 用户登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('2. 刷新页面验证登录状态', async () => {
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
const isLoggedIn = await loginPage.isLoggedIn();
|
||||
expect(isLoggedIn).toBe(true);
|
||||
});
|
||||
|
||||
await test.step('3. 导航到不同页面验证登录状态', async () => {
|
||||
await page.goto('/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const roleTable = page.locator('.el-table').first();
|
||||
await expect(roleTable).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('用户导航功能:测试系统菜单导航', async ({ page }) => {
|
||||
await test.step('1. 用户登录', async () => {
|
||||
await loginPage.goto();
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard/);
|
||||
});
|
||||
|
||||
await test.step('2. 验证仪表板页面', async () => {
|
||||
await expect(page.locator('.dashboard')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('3. 导航到用户管理', async () => {
|
||||
await page.goto('/users');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('4. 导航到角色管理', async () => {
|
||||
await page.goto('/roles');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('5. 导航到菜单管理', async () => {
|
||||
await page.goto('/menus');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('6. 导航到文件管理', async () => {
|
||||
await page.goto('/files');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('7. 导航到操作日志', async () => {
|
||||
await page.goto('/operation-logs');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('.el-table').first();
|
||||
await expect(table).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,159 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import { UserManagementPage } from './pages/UserManagementPage';
|
||||
import { generateTestUser } from './fixtures/test-data';
|
||||
|
||||
test.describe('用户管理 E2E 测试', () => {
|
||||
let loginPage: LoginPage;
|
||||
let dashboardPage: DashboardPage;
|
||||
let userManagementPage: UserManagementPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
dashboardPage = new DashboardPage(page);
|
||||
userManagementPage = new UserManagementPage(page);
|
||||
|
||||
// 清理localStorage
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
|
||||
// 重新登录
|
||||
await loginPage.goto();
|
||||
await loginPage.login('e2e_test_user', 'admin123');
|
||||
});
|
||||
|
||||
test('创建用户完整流程', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await userManagementPage.clickCreateUser();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `testuser_${timestamp}`,
|
||||
nickname: `测试用户${timestamp}`,
|
||||
email: `test_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const userCount = await userManagementPage.getUserCount();
|
||||
console.log(`User count after creation: ${userCount}`);
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const userCountAfterReload = await userManagementPage.getUserCount();
|
||||
console.log(`User count after reload: ${userCountAfterReload}`);
|
||||
|
||||
await expect(userManagementPage.table).toContainText(userData.username);
|
||||
});
|
||||
|
||||
test('编辑用户流程', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await userManagementPage.editUser(1);
|
||||
|
||||
await page.fill('input[name="email"]', 'updated@example.com');
|
||||
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
await expect(userManagementPage.table).toContainText('updated@example.com');
|
||||
});
|
||||
|
||||
test('删除用户流程', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
// 先创建一个测试用户
|
||||
await userManagementPage.clickCreateUser();
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
username: `delete_test_${timestamp}`,
|
||||
nickname: `待删除用户${timestamp}`,
|
||||
email: `delete_${timestamp}@example.com`,
|
||||
phone: '13800138000',
|
||||
password: 'Test123!@#',
|
||||
confirmPassword: 'Test123!@#',
|
||||
};
|
||||
await userManagementPage.fillUserForm(userData);
|
||||
await userManagementPage.submitForm();
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
|
||||
// 等待表格刷新
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 搜索刚创建的用户
|
||||
await userManagementPage.search(userData.username);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// 删除该用户
|
||||
await userManagementPage.deleteUser(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
|
||||
// 验证用户已被删除
|
||||
await userManagementPage.reload();
|
||||
await userManagementPage.search(userData.username);
|
||||
await expect(userManagementPage.table).not.toContainText(userData.username);
|
||||
});
|
||||
|
||||
test('搜索用户功能', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await userManagementPage.search('admin');
|
||||
|
||||
await expect(userManagementPage.table).toContainText('admin');
|
||||
});
|
||||
|
||||
test('分页功能', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
const currentPage = await userManagementPage.getCurrentPage();
|
||||
expect(currentPage).toBe('1');
|
||||
|
||||
await userManagementPage.nextPage();
|
||||
|
||||
const newPage = await userManagementPage.getCurrentPage();
|
||||
expect(newPage).toBe('2');
|
||||
});
|
||||
|
||||
test('批量删除用户', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.check('table tbody tr:nth-child(1) input[type="checkbox"]');
|
||||
await page.check('table tbody tr:nth-child(2) input[type="checkbox"]');
|
||||
|
||||
await page.click('button:has-text("批量删除")');
|
||||
await page.click('.confirm-dialog .confirm-button');
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('用户状态切换', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
await page.click('table tbody tr:first-child .status-toggle');
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible();
|
||||
});
|
||||
|
||||
test('导出用户数据', async ({ page }) => {
|
||||
await dashboardPage.navigateToUserManagement();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.click('button:has-text("导出")');
|
||||
const download = await downloadPromise;
|
||||
|
||||
expect(download.suggestedFilename()).toMatch(/users.*\.xlsx/);
|
||||
});
|
||||
});
|
||||
@@ -4,20 +4,51 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
# Gzip 压缩
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API 代理
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8084;
|
||||
proxy_pass http://gateway:8080/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
gzip_min_length 1000;
|
||||
gzip_comp_level 6;
|
||||
}
|
||||
# SPA 路由支持
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# 安全头
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
|
||||
# 错误页面
|
||||
error_page 404 /index.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6W10sInVzZXJJZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInN1YiI6ImFkbWluIiwiaWF0IjoxNzc1NTQxNzgzLCJleHAiOjE3NzU2MjgxODN9.KBjTZRGCkU2OTe_BwGfxn2ZUBmsxI3Oi8t-NWqp16n1_Qtxv9dqxwtB6Ib987yyp"
|
||||
"value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NTU2NzcxOCwiZXhwIjoxNzc1NjU0MTE4fQ.MvIqxtWA5meij4hIknjE5Tu-VzUWg1IjqhRjXoVGnnFwwDhgpEVjpunK8d52sN0d"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface CreateUserRequest {
|
||||
nickname: string
|
||||
email: string
|
||||
phone: string
|
||||
roles: string[]
|
||||
roles?: number[]
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
@@ -29,7 +29,7 @@ export interface UpdateUserRequest {
|
||||
phone?: string
|
||||
avatar?: string
|
||||
status?: UserStatus
|
||||
roles?: string[]
|
||||
roles?: number[]
|
||||
}
|
||||
|
||||
export interface UserPageRequest {
|
||||
|
||||
Reference in New Issue
Block a user