develop #2

Merged
jenkins merged 49 commits from develop into main 2026-04-08 19:55:46 +08:00
63 changed files with 1005 additions and 7331 deletions
Showing only changes of commit 0c8c993995 - Show all commits
Vendored
+310
View File
@@ -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 '⚠️ 流水线执行不稳定!'
// 可以添加不稳定状态通知
}
}
}
+122
View File
@@ -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
@@ -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: application:
name: manage-app name: manage-app
r2dbc: r2dbc:
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE url: r2dbc:postgresql://localhost:55432/manage_system
username: sa username: novalon
password: password: novalon123
pool: pool:
initial-size: 5 initial-size: 5
max-size: 20 max-size: 20
max-idle-time: 30m max-idle-time: 30m
max-life-time: 1h max-life-time: 1h
acquire-timeout: 5s 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: flyway:
enabled: true enabled: true
locations: classpath:db/migration locations: classpath:db/migration
baseline-on-migrate: true baseline-on-migrate: true
h2: validate-on-migrate: true
console: sql:
enabled: true init:
path: /h2-console mode: never
security: security:
user: user:
name: disabled name: disabled
@@ -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 VALUES
(1, '超级管理员', 'admin', 1, 1, 'system', 'system'), (1, '超级管理员', 'admin', 1, 1, 'system', 'system'),
(2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'), (2, '测试管理员', 'test_admin', 2, 1, 'system', 'system'),
@@ -11,7 +12,8 @@ VALUES
-- 插入测试用户 -- 插入测试用户
-- BCrypt哈希值对应明文密码: Test@123 -- 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 VALUES
(1, 'admin', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'admin@novalon.com', '13800138000', '超级管理员', 1, 'system', 'system'), (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'), (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);
@@ -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,6 +1,5 @@
package cn.novalon.manage.app.integration; package cn.novalon.manage.app.integration;
import cn.novalon.manage.app.config.TestDatabaseConfig;
import cn.novalon.manage.common.util.StatusConstants; import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.sys.core.domain.SysUser; import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysRole; 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.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
@@ -27,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.*;
/** /**
* 用户服务集成测试 * 用户服务集成测试
* *
* 使用H2内存数据库进行集成测试 * 使用PostgreSQL数据库进行集成测试
* *
* 注意:此测试需要完整的Spring上下文,暂时禁用。 * 注意:此测试需要完整的Spring上下文,暂时禁用。
* TODO: 优化集成测试配置 * TODO: 优化集成测试配置
@@ -38,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.*;
@Disabled("暂时禁用:集成测试配置需要优化") @Disabled("暂时禁用:集成测试配置需要优化")
@SpringBootTest @SpringBootTest
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestDatabaseConfig.class)
class SysUserServiceIntegrationTest { class SysUserServiceIntegrationTest {
@Autowired @Autowired
@@ -1,27 +1,22 @@
spring: spring:
r2dbc: r2dbc:
url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE url: r2dbc:postgresql://localhost:55432/manage_system
username: sa username: novalon
password: password: novalon123
pool: pool:
enabled: true enabled: true
initial-size: 2 initial-size: 2
max-size: 10 max-size: 10
h2: flyway:
console: enabled: true
enabled: true locations: classpath:db/migration
path: /h2-console baseline-on-migrate: true
validate-on-migrate: true
sql: sql:
init: init:
mode: always mode: never
continue-on-error: false
schema-locations: classpath:schema-h2.sql
data-locations: classpath:data-h2.sql
flyway:
enabled: false
security: security:
enabled: false enabled: false
@@ -1,6 +1,7 @@
package cn.novalon.manage.db.entity; package cn.novalon.manage.db.entity;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -11,7 +12,7 @@ import java.time.LocalDateTime;
* @author 张翔 * @author 张翔
* @date 2026-03-13 * @date 2026-03-13
*/ */
public abstract class BaseEntity { public abstract class BaseEntity implements Persistable<Long> {
@Id @Id
private Long id; private Long id;
@@ -31,6 +32,7 @@ public abstract class BaseEntity {
@Column("deleted_at") @Column("deleted_at")
private LocalDateTime deletedAt; private LocalDateTime deletedAt;
@Override
public Long getId() { public Long getId() {
return id; return id;
} }
@@ -78,4 +80,13 @@ public abstract class BaseEntity {
public void setDeletedAt(LocalDateTime deletedAt) { public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt; this.deletedAt = deletedAt;
} }
/**
* 判断实体是否为新的
* 如果createdAt为null,则认为是新实体
*/
@Override
public boolean isNew() {
return createdAt == null;
}
} }
@@ -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));
@@ -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;
@@ -3,7 +3,7 @@
-- 描述: 创建所有核心表结构 -- 描述: 创建所有核心表结构
-- 用户表 -- 用户表
CREATE TABLE IF NOT EXISTS sys_user ( CREATE TABLE IF NOT EXISTS sys_user (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE, username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL,
email VARCHAR(100), email VARCHAR(100),
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS sys_user (
); );
-- 角色表 -- 角色表
CREATE TABLE IF NOT EXISTS sys_role ( CREATE TABLE IF NOT EXISTS sys_role (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
role_name VARCHAR(100) NOT NULL, role_name VARCHAR(100) NOT NULL,
role_key VARCHAR(100) NOT NULL UNIQUE, role_key VARCHAR(100) NOT NULL UNIQUE,
role_sort INTEGER DEFAULT 0, role_sort INTEGER DEFAULT 0,
@@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS sys_role (
); );
-- 菜单表(统一使用sys_menu表名) -- 菜单表(统一使用sys_menu表名)
CREATE TABLE IF NOT EXISTS sys_menu ( CREATE TABLE IF NOT EXISTS sys_menu (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
menu_name VARCHAR(50) NOT NULL, menu_name VARCHAR(50) NOT NULL,
parent_id BIGINT DEFAULT 0, parent_id BIGINT DEFAULT 0,
order_num INTEGER 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 ( CREATE TABLE IF NOT EXISTS sys_dict_type (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
dict_name VARCHAR(100) NOT NULL, dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL UNIQUE, dict_type VARCHAR(100) NOT NULL UNIQUE,
status VARCHAR(1) DEFAULT '0', 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 ( CREATE TABLE IF NOT EXISTS sys_dict_data (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
dict_sort INTEGER DEFAULT 0, dict_sort INTEGER DEFAULT 0,
dict_label VARCHAR(100) NOT NULL, dict_label VARCHAR(100) NOT NULL,
dict_value 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 ( CREATE TABLE IF NOT EXISTS sys_dictionary (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
type VARCHAR(100) NOT NULL, type VARCHAR(100) NOT NULL,
code VARCHAR(100) NOT NULL, code VARCHAR(100) NOT NULL,
name 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 ( CREATE TABLE IF NOT EXISTS sys_config (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
config_name VARCHAR(100) NOT NULL, config_name VARCHAR(100) NOT NULL,
config_key VARCHAR(100) NOT NULL UNIQUE, config_key VARCHAR(100) NOT NULL UNIQUE,
config_value VARCHAR(500) NOT NULL, 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 ( CREATE TABLE IF NOT EXISTS sys_login_log (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
username VARCHAR(50), username VARCHAR(50),
ip VARCHAR(50), ip VARCHAR(50),
location VARCHAR(255), location VARCHAR(255),
@@ -117,7 +117,7 @@ CREATE TABLE IF NOT EXISTS sys_login_log (
); );
-- 异常日志表 -- 异常日志表
CREATE TABLE IF NOT EXISTS sys_exception_log ( CREATE TABLE IF NOT EXISTS sys_exception_log (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
username VARCHAR(50), username VARCHAR(50),
title VARCHAR(100), title VARCHAR(100),
exception_name 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 ( CREATE TABLE IF NOT EXISTS operation_log (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
username VARCHAR(50), username VARCHAR(50),
operation VARCHAR(100), operation VARCHAR(100),
method VARCHAR(200), method VARCHAR(200),
@@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS operation_log (
); );
-- 系统公告表 -- 系统公告表
CREATE TABLE IF NOT EXISTS sys_notice ( CREATE TABLE IF NOT EXISTS sys_notice (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
notice_title VARCHAR(50) NOT NULL, notice_title VARCHAR(50) NOT NULL,
notice_type VARCHAR(1) NOT NULL, notice_type VARCHAR(1) NOT NULL,
notice_content TEXT, notice_content TEXT,
@@ -161,7 +161,7 @@ CREATE TABLE IF NOT EXISTS sys_notice (
); );
-- 用户消息表 -- 用户消息表
CREATE TABLE IF NOT EXISTS sys_user_message ( CREATE TABLE IF NOT EXISTS sys_user_message (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
notice_id BIGINT, notice_id BIGINT,
message_title VARCHAR(255), message_title VARCHAR(255),
@@ -176,7 +176,7 @@ CREATE TABLE IF NOT EXISTS sys_user_message (
); );
-- 文件管理表 -- 文件管理表
CREATE TABLE IF NOT EXISTS sys_file ( CREATE TABLE IF NOT EXISTS sys_file (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
file_name VARCHAR(255) NOT NULL, file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL, file_path VARCHAR(500) NOT NULL,
file_size BIGINT, file_size BIGINT,
@@ -191,7 +191,7 @@ CREATE TABLE IF NOT EXISTS sys_file (
); );
-- OAuth2客户端表 -- OAuth2客户端表
CREATE TABLE IF NOT EXISTS oauth2_client ( CREATE TABLE IF NOT EXISTS oauth2_client (
id BIGSERIAL PRIMARY KEY, id BIGINT PRIMARY KEY,
client_id VARCHAR(100) NOT NULL UNIQUE, client_id VARCHAR(100) NOT NULL UNIQUE,
client_secret VARCHAR(255) NOT NULL, client_secret VARCHAR(255) NOT NULL,
client_name VARCHAR(100), client_name VARCHAR(100),
@@ -1,5 +1,6 @@
package cn.novalon.manage.sys.core.domain; package cn.novalon.manage.sys.core.domain;
import cn.novalon.manage.common.util.SnowflakeId;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
@@ -64,4 +65,14 @@ public abstract class BaseDomain {
public void setDeletedAt(LocalDateTime deletedAt) { public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt; this.deletedAt = deletedAt;
} }
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
} }
@@ -78,16 +78,6 @@ public class SysPermission extends BaseDomain {
this.status = status; this.status = status;
} }
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/** /**
* 删除权限 * 删除权限
*/ */
@@ -58,16 +58,6 @@ public class SysRole extends BaseDomain {
this.status = status; this.status = status;
} }
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/** /**
* 删除角色 * 删除角色
*/ */
@@ -33,14 +33,4 @@ public class SysRolePermission extends BaseDomain {
public void setPermissionId(Long permissionId) { public void setPermissionId(Long permissionId) {
this.permissionId = permissionId; this.permissionId = permissionId;
} }
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
} }
@@ -101,16 +101,6 @@ public class SysUser extends BaseDomain {
this.status = status; this.status = status;
} }
/**
* 生成主键ID
*
* @return 主键ID
*/
public Long generateId() {
this.id = SnowflakeId.nextId();
return this.id;
}
/** /**
* 删除用户 * 删除用户
*/ */
@@ -82,6 +82,7 @@ public class SysRoleService implements ISysRoleService {
@Override @Override
public Mono<SysRole> createRole(CreateRoleCommand command) { public Mono<SysRole> createRole(CreateRoleCommand command) {
SysRole role = new SysRole(); SysRole role = new SysRole();
role.generateId();
role.setRoleName(command.roleName()); role.setRoleName(command.roleName());
role.setRoleKey(command.roleKey()); role.setRoleKey(command.roleKey());
role.setRoleSort(command.roleSort()); role.setRoleSort(command.roleSort());
@@ -44,15 +44,15 @@ public class SysUserService implements ISysUserService {
private final IUserRoleRepository userRoleRepository; private final IUserRoleRepository userRoleRepository;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
public SysUserService(ISysUserRepository userRepository, public SysUserService(ISysUserRepository userRepository,
ISysRoleRepository roleRepository, ISysRoleRepository roleRepository,
IUserRoleRepository userRoleRepository, IUserRoleRepository userRoleRepository,
@Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) { @Qualifier("passwordEncoder") PasswordEncoder passwordEncoder) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.roleRepository = roleRepository; this.roleRepository = roleRepository;
this.userRoleRepository = userRoleRepository; this.userRoleRepository = userRoleRepository;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
logger.info("使用的密码编码器类型: {}", passwordEncoder.getClass().getName()); logger.info("使用的密码编码器类型: {}", passwordEncoder.getClass().getName());
} }
@@ -98,6 +98,7 @@ public class SysUserService implements ISysUserService {
logger.info("SysUserService.createUser - 用户名: {}, 密码前缀: {}", logger.info("SysUserService.createUser - 用户名: {}, 密码前缀: {}",
user.getUsername(), user.getUsername(),
user.getPassword() != null ? user.getPassword().substring(0, 7) : "null"); user.getPassword() != null ? user.getPassword().substring(0, 7) : "null");
user.generateId();
if (user.getPassword() != null && !user.getPassword().startsWith("$2a$") if (user.getPassword() != null && !user.getPassword().startsWith("$2a$")
&& !user.getPassword().startsWith("$2b$")) { && !user.getPassword().startsWith("$2b$")) {
logger.info("密码不以$2a$或$2b$开头,重新编码"); logger.info("密码不以$2a$或$2b$开头,重新编码");
@@ -106,7 +107,6 @@ public class SysUserService implements ISysUserService {
} else { } else {
logger.info("密码已编码,跳过重新编码"); logger.info("密码已编码,跳过重新编码");
} }
user.setCreatedAt(LocalDateTime.now());
if (user.getStatus() == null) { if (user.getStatus() == null) {
user.setStatus(StatusConstants.ENABLED); user.setStatus(StatusConstants.ENABLED);
} }
@@ -116,6 +116,7 @@ public class SysUserService implements ISysUserService {
@Override @Override
public Mono<SysUser> createUser(CreateUserCommand command) { public Mono<SysUser> createUser(CreateUserCommand command) {
SysUser user = new SysUser(); SysUser user = new SysUser();
user.generateId();
user.setUsername(command.username().getValue()); user.setUsername(command.username().getValue());
user.setPassword(passwordEncoder.encode(command.password().getValue())); user.setPassword(passwordEncoder.encode(command.password().getValue()));
user.setEmail(command.email().getValue()); user.setEmail(command.email().getValue());
@@ -123,7 +124,6 @@ public class SysUserService implements ISysUserService {
user.setPhone(command.phone()); user.setPhone(command.phone());
user.setRoleId(command.roleId()); user.setRoleId(command.roleId());
user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED); user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
user.setCreatedAt(LocalDateTime.now());
return userRepository.save(user); return userRepository.save(user);
} }
@@ -164,7 +164,7 @@ public class SysUserService implements ISysUserService {
@Transactional @Transactional
public Mono<Void> deleteUser(Long id) { public Mono<Void> deleteUser(Long id) {
logger.debug("开始删除用户,ID: {}", id); logger.debug("开始删除用户,ID: {}", id);
return userRepository.findById(id) return userRepository.findById(id)
.switchIfEmpty(Mono.error(new RuntimeException("User not found"))) .switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.flatMap(user -> { .flatMap(user -> {
@@ -244,31 +244,30 @@ public class SysUserService implements ISysUserService {
@Transactional @Transactional
public Mono<Void> assignRolesToUser(Long userId, List<Long> roleIds) { public Mono<Void> assignRolesToUser(Long userId, List<Long> roleIds) {
logger.debug("开始为用户分配角色,用户ID: {}, 角色IDs: {}", userId, roleIds); logger.debug("开始为用户分配角色,用户ID: {}, 角色IDs: {}", userId, roleIds);
if (roleIds == null || roleIds.isEmpty()) { if (roleIds == null || roleIds.isEmpty()) {
logger.debug("角色列表为空,删除用户的所有角色关联"); logger.debug("角色列表为空,删除用户的所有角色关联");
return userRoleRepository.deleteByUserId(userId) return userRoleRepository.deleteByUserId(userId)
.doOnSuccess(v -> logger.debug("成功删除用户的所有角色关联")) .doOnSuccess(v -> logger.debug("成功删除用户的所有角色关联"))
.doOnError(e -> logger.error("删除用户角色关联失败", e)); .doOnError(e -> logger.error("删除用户角色关联失败", e));
} }
return userRoleRepository.deleteByUserId(userId) return userRoleRepository.deleteByUserId(userId)
.doOnSuccess(v -> logger.debug("成功删除用户的旧角色关联")) .doOnSuccess(v -> logger.debug("成功删除用户的旧角色关联"))
.doOnError(e -> logger.error("删除用户旧角色关联失败", e)) .doOnError(e -> logger.error("删除用户旧角色关联失败", e))
.then( .then(
Flux.fromIterable(roleIds) Flux.fromIterable(roleIds)
.concatMap(roleId -> { .concatMap(roleId -> {
logger.debug("为用户分配角色ID: {}", roleId); logger.debug("为用户分配角色ID: {}", roleId);
UserRole userRole = new UserRole(); UserRole userRole = new UserRole();
userRole.setUserId(userId); userRole.setUserId(userId);
userRole.setRoleId(roleId); userRole.setRoleId(roleId);
userRole.setCreatedAt(LocalDateTime.now()); userRole.setCreatedAt(LocalDateTime.now());
return userRoleRepository.save(userRole) return userRoleRepository.save(userRole)
.doOnSuccess(v -> logger.debug("成功保存用户角色关联")) .doOnSuccess(v -> logger.debug("成功保存用户角色关联"))
.doOnError(e -> logger.error("保存用户角色关联失败", e)); .doOnError(e -> logger.error("保存用户角色关联失败", e));
}) })
.then() .then());
);
} }
@Override @Override
@@ -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;
}
}
@@ -6,6 +6,8 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.util.List;
/** /**
* 用户注册请求DTO * 用户注册请求DTO
* *
@@ -42,6 +44,9 @@ public class UserRegisterRequest {
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone; private String phone;
@Schema(description = "角色ID列表", example = "[1, 2]")
private List<Long> roles;
public String getUsername() { public String getUsername() {
return username; return username;
} }
@@ -81,4 +86,12 @@ public class UserRegisterRequest {
public void setPhone(String phone) { public void setPhone(String phone) {
this.phone = phone; this.phone = phone;
} }
public List<Long> getRoles() {
return roles;
}
public void setRoles(List<Long> roles) {
this.roles = roles;
}
} }
@@ -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.domain.SysUser;
import cn.novalon.manage.sys.core.service.ISysUserService; import cn.novalon.manage.sys.core.service.ISysUserService;
import cn.novalon.manage.common.dto.PageRequest; 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.PasswordChangeRequest;
import cn.novalon.manage.sys.dto.request.UserRegisterRequest; import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
import cn.novalon.manage.sys.dto.request.UserUpdateRequest; import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
@@ -135,6 +136,14 @@ public class SysUserHandler {
null null
)) ))
.flatMap(userService::createUser) .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)); .flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
}); });
} }
@@ -249,9 +258,8 @@ public class SysUserHandler {
@OperationLog(operation = "分配角色", module = "用户管理") @OperationLog(operation = "分配角色", module = "用户管理")
public Mono<ServerResponse> assignRoles(ServerRequest request) { public Mono<ServerResponse> assignRoles(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id")); Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(new org.springframework.core.ParameterizedTypeReference<List<Long>>() { return request.bodyToMono(AssignRolesRequest.class)
}) .flatMap(req -> userService.assignRolesToUser(id, req.getRoleIds()))
.flatMap(roleIds -> userService.assignRolesToUser(id, roleIds))
.then(ServerResponse.ok().build()) .then(ServerResponse.ok().build())
.onErrorResume(error -> { .onErrorResume(error -> {
logger.error("分配角色失败", error); logger.error("分配角色失败", error);
+22 -6
View File
@@ -1,18 +1,34 @@
FROM node:18-alpine AS builder # 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
COPY package*.json ./ # 安装 pnpm
RUN npm ci RUN npm install -g pnpm@8.15.0
# 复制 package.json 和 lock 文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install
# 复制源代码
COPY . . COPY . .
RUN npm run build
# 构建生产版本
RUN pnpm run build:prod
# 生产阶段
FROM nginx:alpine FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html # 复制自定义 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 暴露端口
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] # 启动 nginx
CMD ["nginx", "-g", "daemon off;"]
+21
View File
@@ -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"]
+9 -9
View File
@@ -20,7 +20,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-001: 管理员查看操作日志', async ({ page }) => { test('AUDIT-001: 管理员查看操作日志', async ({ page }) => {
await test.step('管理员登录', async () => { await test.step('管理员登录', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/); await expect(page).toHaveURL(/.*dashboard/);
}); });
@@ -47,7 +47,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-002: 按关键词搜索操作日志', async ({ page }) => { test('AUDIT-002: 按关键词搜索操作日志', async ({ page }) => {
await test.step('管理员登录并导航到操作日志', async () => { await test.step('管理员登录并导航到操作日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await operationLogPage.goto(); await operationLogPage.goto();
}); });
@@ -67,7 +67,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-003: 导出操作日志', async ({ page }) => { test('AUDIT-003: 导出操作日志', async ({ page }) => {
await test.step('管理员登录并导航到操作日志', async () => { await test.step('管理员登录并导航到操作日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await operationLogPage.goto(); await operationLogPage.goto();
}); });
@@ -82,7 +82,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-004: 管理员查看登录日志', async ({ page }) => { test('AUDIT-004: 管理员查看登录日志', async ({ page }) => {
await test.step('管理员登录', async () => { await test.step('管理员登录', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await expect(page).toHaveURL(/.*dashboard/); await expect(page).toHaveURL(/.*dashboard/);
}); });
@@ -109,7 +109,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-005: 按IP地址搜索登录日志', async ({ page }) => { test('AUDIT-005: 按IP地址搜索登录日志', async ({ page }) => {
await test.step('管理员登录并导航到登录日志', async () => { await test.step('管理员登录并导航到登录日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await loginLogPage.goto(); await loginLogPage.goto();
}); });
@@ -129,7 +129,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-006: 导出登录日志', async ({ page }) => { test('AUDIT-006: 导出登录日志', async ({ page }) => {
await test.step('管理员登录并导航到登录日志', async () => { await test.step('管理员登录并导航到登录日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await loginLogPage.goto(); await loginLogPage.goto();
}); });
@@ -164,7 +164,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-008: 验证操作日志时间排序', async ({ page }) => { test('AUDIT-008: 验证操作日志时间排序', async ({ page }) => {
await test.step('管理员登录并导航到操作日志', async () => { await test.step('管理员登录并导航到操作日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await operationLogPage.goto(); await operationLogPage.goto();
}); });
@@ -177,7 +177,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-009: 验证登录日志状态显示', async ({ page }) => { test('AUDIT-009: 验证登录日志状态显示', async ({ page }) => {
await test.step('管理员登录并导航到登录日志', async () => { await test.step('管理员登录并导航到登录日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await loginLogPage.goto(); await loginLogPage.goto();
}); });
@@ -189,7 +189,7 @@ test.describe('审计功能 E2E 测试', () => {
test('AUDIT-010: 验证审计日志数据完整性', async ({ page }) => { test('AUDIT-010: 验证审计日志数据完整性', async ({ page }) => {
await test.step('管理员登录并导航到操作日志', async () => { await test.step('管理员登录并导航到操作日志', async () => {
await loginPage.goto(); await loginPage.goto();
await loginPage.login('admin', 'admin123'); await loginPage.login('admin', 'Test@123');
await operationLogPage.goto(); await operationLogPage.goto();
}); });
+1 -1
View File
@@ -7,7 +7,7 @@ setup('authenticate', async ({ page }) => {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.locator('input[placeholder*="用户名"]').fill('admin'); 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.locator('button:has-text("登录")').click();
await page.waitForURL('**/dashboard', { timeout: 30000 }); await page.waitForURL('**/dashboard', { timeout: 30000 });
-68
View File
@@ -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/);
});
});
-27
View File
@@ -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);
});
});
});
-534
View File
@@ -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();
});
});
});
-63
View File
@@ -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 });
});
});
+6 -6
View File
@@ -100,7 +100,7 @@ async function globalSetup(config: FullConfig) {
backendArgs = [ backendArgs = [
'-jar', '-jar',
jarFile, jarFile,
'--spring.profiles.active=dev', '--spring.profiles.active=test',
'-Xms256m', '-Xms256m',
'-Xmx512m' '-Xmx512m'
]; ];
@@ -108,7 +108,7 @@ async function globalSetup(config: FullConfig) {
console.log('📦 使用Maven启动后端服务...'); console.log('📦 使用Maven启动后端服务...');
console.log(' 提示: 运行 "mvn clean package -DskipTests" 构建JAR文件以获得更快的启动速度'); console.log(' 提示: 运行 "mvn clean package -DskipTests" 构建JAR文件以获得更快的启动速度');
backendCommand = 'mvn'; backendCommand = 'mvn';
backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=dev']; backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=test'];
} }
console.log(` 目录: ${backendDir}`); console.log(` 目录: ${backendDir}`);
@@ -250,7 +250,7 @@ async function verifyAllServices(): Promise<void> {
const response = await fetch('http://localhost:8080/api/auth/login', { const response = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, 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 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', { const loginTest = await fetch('http://localhost:8084/api/auth/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, 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 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', { const loginTest = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, 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 signal: AbortSignal.timeout(10000) as any
}); });
@@ -398,7 +398,7 @@ async function cleanupTestData(): Promise<void> {
}, },
body: JSON.stringify({ body: JSON.stringify({
username: 'admin', username: 'admin',
password: 'admin123' password: 'Test@123'
}) })
}); });
+1 -1
View File
@@ -5,7 +5,7 @@ export async function loginAsAdmin(page: Page) {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.locator('input[placeholder*="用户名"]').fill('admin'); 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.locator('button:has-text("登录")').click();
await page.waitForURL('**/dashboard', { timeout: 30000 }); await page.waitForURL('**/dashboard', { timeout: 30000 });
@@ -28,6 +28,7 @@ test.describe('管理员完整工作流', () => {
const dialog = page.locator('.el-dialog'); const dialog = page.locator('.el-dialog');
await dialog.locator('input').first().fill(roleName); await dialog.locator('input').first().fill(roleName);
await dialog.locator('input').nth(1).fill(roleKey); await dialog.locator('input').nth(1).fill(roleKey);
await dialog.locator('input[type="number"]').fill('99');
}); });
await test.step('提交表单', async () => { await test.step('提交表单', async () => {
@@ -67,6 +68,32 @@ test.describe('管理员完整工作流', () => {
await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 }); await page.waitForSelector('.el-dialog', { state: 'hidden', timeout: 10000 });
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 }); 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 }) => { test('验证新用户登录', async ({ page }) => {
@@ -110,7 +137,7 @@ test.describe('管理员完整工作流', () => {
await page.goto('/login'); await page.goto('/login');
await page.locator('input[placeholder*="用户名"]').fill('admin'); 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.locator('button:has-text("登录")').click();
await page.waitForURL('**/dashboard'); 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('普通用户只能访问个人信息', 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 test.step('普通用户登录', async () => {
await page.goto('/login'); await page.goto('/login');
@@ -35,10 +46,10 @@ test.describe('用户权限边界验证', () => {
const loginButton = page.locator('button:has-text("登录")'); const loginButton = page.locator('button:has-text("登录")');
await usernameInput.waitFor({ state: 'visible' }); await usernameInput.waitFor({ state: 'visible' });
await usernameInput.fill('user'); await usernameInput.fill('normaluser');
await passwordInput.waitFor({ state: 'visible' }); await passwordInput.waitFor({ state: 'visible' });
await passwordInput.fill('admin123'); await passwordInput.fill('Test@123');
await loginButton.waitFor({ state: 'visible' }); await loginButton.waitFor({ state: 'visible' });
await loginButton.click(); await loginButton.click();
@@ -69,7 +80,18 @@ test.describe('用户权限边界验证', () => {
}); });
test('权限不足时显示提示信息', async ({ page }) => { 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 test.step('普通用户登录', async () => {
await page.goto('/login'); await page.goto('/login');
@@ -80,10 +102,10 @@ test.describe('用户权限边界验证', () => {
const loginButton = page.locator('button:has-text("登录")'); const loginButton = page.locator('button:has-text("登录")');
await usernameInput.waitFor({ state: 'visible' }); await usernameInput.waitFor({ state: 'visible' });
await usernameInput.fill('user'); await usernameInput.fill('normaluser');
await passwordInput.waitFor({ state: 'visible' }); await passwordInput.waitFor({ state: 'visible' });
await passwordInput.fill('admin123'); await passwordInput.fill('Test@123');
await loginButton.waitFor({ state: 'visible' }); await loginButton.waitFor({ state: 'visible' });
await loginButton.click(); await loginButton.click();
-166
View File
@@ -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('排序');
});
});
});
-195
View File
@@ -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();
});
});
-290
View File
@@ -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/);
});
});
+39 -8
View File
@@ -4,20 +4,51 @@ server {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
location / { # Gzip 压缩
try_files $uri $uri/ /index.html; 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/ { 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 Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; 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; # SPA 路由支持
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; location / {
gzip_min_length 1000; try_files $uri $uri/ /index.html;
gzip_comp_level 6; }
}
# 安全头
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", "name": "token",
"value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6W10sInVzZXJJZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInN1YiI6ImFkbWluIiwiaWF0IjoxNzc1NTQxNzgzLCJleHAiOjE3NzU2MjgxODN9.KBjTZRGCkU2OTe_BwGfxn2ZUBmsxI3Oi8t-NWqp16n1_Qtxv9dqxwtB6Ib987yyp" "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NTU2NzcxOCwiZXhwIjoxNzc1NjU0MTE4fQ.MvIqxtWA5meij4hIknjE5Tu-VzUWg1IjqhRjXoVGnnFwwDhgpEVjpunK8d52sN0d"
} }
] ]
} }
+2 -2
View File
@@ -20,7 +20,7 @@ export interface CreateUserRequest {
nickname: string nickname: string
email: string email: string
phone: string phone: string
roles: string[] roles?: number[]
} }
export interface UpdateUserRequest { export interface UpdateUserRequest {
@@ -29,7 +29,7 @@ export interface UpdateUserRequest {
phone?: string phone?: string
avatar?: string avatar?: string
status?: UserStatus status?: UserStatus
roles?: string[] roles?: number[]
} }
export interface UserPageRequest { export interface UserPageRequest {