fix(e2e): 修复前端服务启动冲突问题
问题: - Playwright的webServer配置会自动启动前端服务 - global-setup.ts也在启动前端服务 - 导致端口3002冲突 修复: - 移除global-setup.ts中的前端服务启动逻辑 - 移除global-setup.ts中的前端服务停止逻辑 - 移除前端服务健康检查验证 - 让Playwright的webServer统一管理前端服务 优势: - 避免端口冲突 - 简化测试环境设置 - 统一服务管理
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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('✅ 全局测试环境清理完成');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user