fix(e2e): 修复前端服务启动冲突问题

问题:
- Playwright的webServer配置会自动启动前端服务
- global-setup.ts也在启动前端服务
- 导致端口3002冲突

修复:
- 移除global-setup.ts中的前端服务启动逻辑
- 移除global-setup.ts中的前端服务停止逻辑
- 移除前端服务健康检查验证
- 让Playwright的webServer统一管理前端服务

优势:
- 避免端口冲突
- 简化测试环境设置
- 统一服务管理
This commit is contained in:
张翔
2026-04-07 11:24:50 +08:00
parent 92df794cc8
commit d65537529a
2 changed files with 77 additions and 138 deletions
+14
View File
@@ -0,0 +1,14 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class TestBCrypt {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
String password = "admin123";
String hash = "$2b$12$SFefXlGRFMA0fvxIufpWPuIAl0OPLgRDoCZPThCvjpiJGPYS8yNYy";
System.out.println("测试密码验证:");
System.out.println("密码: " + password);
System.out.println("哈希: " + hash);
System.out.println("验证结果: " + encoder.matches(password, hash));
}
}
+63 -138
View File
@@ -9,7 +9,6 @@ const __dirname = path.dirname(__filename);
let backendProcess: ChildProcess | null = null;
let gatewayProcess: ChildProcess | null = null;
let frontendProcess: ChildProcess | null = null;
let healthCheckInterval: NodeJS.Timeout | null = null;
async function checkBackendHealth(): Promise<boolean> {
@@ -57,12 +56,12 @@ function startHealthMonitoring() {
if (healthCheckInterval) {
clearInterval(healthCheckInterval);
}
healthCheckInterval = setInterval(async () => {
const backendHealthy = await checkBackendHealth();
const gatewayHealthy = await checkGatewayHealth();
const frontendHealthy = await checkFrontendHealth();
if (!backendHealthy) {
console.error('⚠️ 后端服务健康检查失败!');
}
@@ -84,16 +83,16 @@ function stopHealthMonitoring() {
async function globalSetup(config: FullConfig) {
console.log('🚀 开始全局测试环境设置...');
process.env.NODE_ENV = 'test';
process.env.PLAYWRIGHT_HEADLESS = 'false';
const backendDir = path.resolve(__dirname, '../../novalon-manage-api/manage-app');
const jarFile = path.join(backendDir, 'target/manage-app-1.0.0.jar');
let backendCommand: string;
let backendArgs: string[];
if (existsSync(jarFile)) {
console.log('📦 使用JAR文件启动后端服务...');
console.log(` JAR文件: ${jarFile}`);
@@ -111,10 +110,10 @@ async function globalSetup(config: FullConfig) {
backendCommand = 'mvn';
backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=dev'];
}
console.log(` 目录: ${backendDir}`);
console.log(` 命令: ${backendCommand} ${backendArgs.join(' ')}`);
backendProcess = spawn(backendCommand, backendArgs, {
cwd: backendDir,
stdio: 'pipe',
@@ -122,7 +121,7 @@ async function globalSetup(config: FullConfig) {
detached: false,
env: { ...process.env, SPRING_PROFILES_ACTIVE: 'test' }
});
if (backendProcess.stdout) {
backendProcess.stdout.on('data', (data) => {
const output = data.toString();
@@ -131,7 +130,7 @@ async function globalSetup(config: FullConfig) {
}
});
}
if (backendProcess.stderr) {
backendProcess.stderr.on('data', (data) => {
const output = data.toString();
@@ -140,26 +139,26 @@ async function globalSetup(config: FullConfig) {
}
});
}
backendProcess.on('error', (error) => {
console.error('❌ 后端服务启动失败:', error);
});
backendProcess.on('exit', (code, signal) => {
if (code !== 0 && code !== null) {
console.error(`❌ 后端服务异常退出,退出码: ${code}, 信号: ${signal}`);
}
});
console.log('⏳ 等待后端服务就绪...');
await waitForBackendReady();
const gatewayDir = path.resolve(__dirname, '../../novalon-manage-api/manage-gateway');
const gatewayJarFile = path.join(gatewayDir, 'target/manage-gateway-1.0.0.jar');
let gatewayCommand: string;
let gatewayArgs: string[];
if (existsSync(gatewayJarFile)) {
console.log('🚪 使用JAR文件启动网关服务...');
console.log(` JAR文件: ${gatewayJarFile}`);
@@ -177,10 +176,10 @@ async function globalSetup(config: FullConfig) {
gatewayCommand = 'mvn';
gatewayArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=dev'];
}
console.log(` 目录: ${gatewayDir}`);
console.log(` 命令: ${gatewayCommand} ${gatewayArgs.join(' ')}`);
gatewayProcess = spawn(gatewayCommand, gatewayArgs, {
cwd: gatewayDir,
stdio: 'pipe',
@@ -188,7 +187,7 @@ async function globalSetup(config: FullConfig) {
detached: false,
env: { ...process.env, SPRING_PROFILES_ACTIVE: 'dev' }
});
if (gatewayProcess.stdout) {
gatewayProcess.stdout.on('data', (data) => {
const output = data.toString();
@@ -197,7 +196,7 @@ async function globalSetup(config: FullConfig) {
}
});
}
if (gatewayProcess.stderr) {
gatewayProcess.stderr.on('data', (data) => {
const output = data.toString();
@@ -206,71 +205,28 @@ async function globalSetup(config: FullConfig) {
}
});
}
gatewayProcess.on('error', (error) => {
console.error('❌ 网关服务启动失败:', error);
});
gatewayProcess.on('exit', (code, signal) => {
if (code !== 0 && code !== null) {
console.error(`❌ 网关服务异常退出,退出码: ${code}, 信号: ${signal}`);
}
});
console.log('⏳ 等待网关服务就绪...');
await waitForGatewayReady();
const frontendDir = path.resolve(__dirname, '..');
console.log('🌐 启动前端服务...');
console.log(` 目录: ${frontendDir}`);
frontendProcess = spawn('pnpm', ['run', 'dev'], {
cwd: frontendDir,
stdio: 'pipe',
shell: true,
detached: false,
env: { ...process.env, NODE_ENV: 'test' }
});
if (frontendProcess.stdout) {
frontendProcess.stdout.on('data', (data) => {
const output = data.toString();
if (output.includes('Local:') || output.includes('localhost:3002')) {
console.log('✅ 前端服务启动成功');
}
});
}
if (frontendProcess.stderr) {
frontendProcess.stderr.on('data', (data) => {
const output = data.toString();
if (output.includes('ERROR') || output.includes('error')) {
console.error('❌ 前端服务启动错误:', output);
}
});
}
frontendProcess.on('error', (error) => {
console.error('❌ 前端服务启动失败:', error);
});
frontendProcess.on('exit', (code, signal) => {
if (code !== 0 && code !== null) {
console.error(`❌ 前端服务异常退出,退出码: ${code}, 信号: ${signal}`);
}
});
console.log('⏳ 等待前端服务就绪...');
await waitForFrontendReady();
console.log('🔍 验证所有服务连通性...');
await verifyAllServices();
console.log('🧹 清理测试数据...');
await cleanupTestData();
startHealthMonitoring();
console.log('✅ 全局测试环境设置完成');
}
@@ -281,21 +237,14 @@ async function verifyAllServices(): Promise<void> {
throw new Error('❌ 后端服务验证失败');
}
console.log(' ✅ 后端服务正常');
console.log(' 验证网关服务...');
const gatewayOk = await checkGatewayHealth();
if (!gatewayOk) {
throw new Error('❌ 网关服务验证失败');
}
console.log(' ✅ 网关服务正常');
console.log(' 验证前端服务...');
const frontendOk = await checkFrontendHealth();
if (!frontendOk) {
throw new Error('❌ 前端服务验证失败');
}
console.log(' ✅ 前端服务正常');
console.log(' 验证网关到后端的连通性...');
try {
const response = await fetch('http://localhost:8080/api/auth/login', {
@@ -304,28 +253,28 @@ async function verifyAllServices(): Promise<void> {
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
signal: AbortSignal.timeout(10000) as any
});
if (!response.ok) {
throw new Error(`网关到后端连通性验证失败,状态码: ${response.status}`);
}
const data = await response.json();
if (!data.token) {
throw new Error('网关到后端连通性验证失败,未返回token');
}
console.log(' ✅ 网关到后端连通性正常');
} catch (error) {
throw new Error(`❌ 网关到后端连通性验证失败: ${error}`);
}
console.log('✅ 所有服务验证通过');
}
async function waitForBackendReady(): Promise<void> {
const maxRetries = 90;
const retryInterval = 1000;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('http://localhost:8084/actuator/health', {
@@ -335,7 +284,7 @@ async function waitForBackendReady(): Promise<void> {
const data = await response.json();
if (data.status === 'UP') {
console.log(`✅ 后端服务健康检查通过 (尝试 ${i + 1}/${maxRetries})`);
// 验证服务连通性:测试登录API
try {
const loginTest = await fetch('http://localhost:8084/api/auth/login', {
@@ -344,7 +293,7 @@ async function waitForBackendReady(): Promise<void> {
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
signal: AbortSignal.timeout(10000) as any
});
if (loginTest.ok) {
console.log('✅ 后端服务连通性验证通过(登录API可用)');
return;
@@ -359,19 +308,19 @@ async function waitForBackendReady(): Promise<void> {
} catch (error) {
// 服务还未就绪,继续等待
}
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryInterval));
}
}
throw new Error('❌ 后端服务启动超时');
}
async function waitForGatewayReady(): Promise<void> {
const maxRetries = 90;
const retryInterval = 1000;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('http://localhost:8080/actuator/health', {
@@ -381,7 +330,7 @@ async function waitForGatewayReady(): Promise<void> {
const data = await response.json();
if (data.status === 'UP') {
console.log(`✅ 网关服务健康检查通过 (尝试 ${i + 1}/${maxRetries})`);
// 验证网关连通性:通过网关测试登录API
try {
const loginTest = await fetch('http://localhost:8080/api/auth/login', {
@@ -390,7 +339,7 @@ async function waitForGatewayReady(): Promise<void> {
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
signal: AbortSignal.timeout(10000) as any
});
if (loginTest.ok) {
console.log('✅ 网关服务连通性验证通过(登录API可用)');
return;
@@ -405,19 +354,19 @@ async function waitForGatewayReady(): Promise<void> {
} catch (error) {
// 服务还未就绪,继续等待
}
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryInterval));
}
}
throw new Error('❌ 网关服务启动超时');
}
async function waitForFrontendReady(): Promise<void> {
const maxRetries = 90;
const retryInterval = 1000;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('http://localhost:3002', {
@@ -430,12 +379,12 @@ async function waitForFrontendReady(): Promise<void> {
} catch (error) {
// 服务还未就绪,继续等待
}
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryInterval));
}
}
throw new Error('❌ 前端服务启动超时');
}
@@ -452,25 +401,25 @@ async function cleanupTestData(): Promise<void> {
password: 'admin123'
})
});
if (!loginResponse.ok) {
console.log('⚠️ 无法登录,跳过数据清理');
return;
}
const loginData = await loginResponse.json();
const token = loginData.token;
// 获取所有用户
const usersResponse = await fetch('http://localhost:8080/api/users', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (usersResponse.ok) {
const users = await usersResponse.json();
// 删除测试创建的用户(保留ID 1-10的初始用户)
for (const user of users) {
if (user.id > 10) {
@@ -488,17 +437,17 @@ async function cleanupTestData(): Promise<void> {
}
}
}
// 获取所有角色
const rolesResponse = await fetch('http://localhost:8080/api/roles', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (rolesResponse.ok) {
const roles = await rolesResponse.json();
// 删除测试创建的角色(保留ID 1-4的初始角色)
for (const role of roles) {
if (role.id > 4) {
@@ -516,7 +465,7 @@ async function cleanupTestData(): Promise<void> {
}
}
}
console.log('✅ 测试数据清理完成');
} catch (error) {
console.log('⚠️ 数据清理失败,继续执行测试');
@@ -526,20 +475,20 @@ async function cleanupTestData(): Promise<void> {
async function globalTeardown() {
console.log('🧹 开始全局测试环境清理...');
stopHealthMonitoring();
if (backendProcess) {
console.log('🛑 停止后端服务...');
backendProcess.kill('SIGTERM');
await new Promise<void>((resolve) => {
if (backendProcess) {
backendProcess.on('exit', () => {
console.log('✅ 后端服务已停止');
resolve();
});
setTimeout(() => {
if (backendProcess) {
backendProcess.kill('SIGKILL');
@@ -552,18 +501,18 @@ async function globalTeardown() {
}
});
}
if (gatewayProcess) {
console.log('🛑 停止网关服务...');
gatewayProcess.kill('SIGTERM');
await new Promise<void>((resolve) => {
if (gatewayProcess) {
gatewayProcess.on('exit', () => {
console.log('✅ 网关服务已停止');
resolve();
});
setTimeout(() => {
if (gatewayProcess) {
gatewayProcess.kill('SIGKILL');
@@ -576,31 +525,7 @@ async function globalTeardown() {
}
});
}
if (frontendProcess) {
console.log('🛑 停止前端服务...');
frontendProcess.kill('SIGTERM');
await new Promise<void>((resolve) => {
if (frontendProcess) {
frontendProcess.on('exit', () => {
console.log('✅ 前端服务已停止');
resolve();
});
setTimeout(() => {
if (frontendProcess) {
frontendProcess.kill('SIGKILL');
console.log('⚠️ 强制停止前端服务');
resolve();
}
}, 10000);
} else {
resolve();
}
});
}
console.log('✅ 全局测试环境清理完成');
}