Files
张翔 3ec80f82da ci(e2e): 增强 E2E 测试 stage
启动独立 PostgreSQL 容器 + 后端服务容器作为测试环境;
等待数据库和后端就绪后再执行 Playwright 测试;
E2E 测试支持重试机制(RETRY_COUNT 次);
post.always 中自动清理测试容器。
2026-05-06 19:43:39 +08:00

371 lines
14 KiB
Groovy

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测试...'
sh '''
# 启动测试数据库
docker run -d --name e2e-postgres-${BUILD_NUMBER} \
-e POSTGRES_DB=${DB_NAME} \
-e POSTGRES_USER=${DB_USER} \
-e POSTGRES_PASSWORD=${DB_PASSWORD} \
-p 5433:5432 \
postgres:16-alpine
# 等待数据库就绪
for i in $(seq 1 30); do
if docker exec e2e-postgres-${BUILD_NUMBER} pg_isready -U ${DB_USER} -d ${DB_NAME} > /dev/null 2>&1; then
echo "数据库已就绪"
break
fi
echo "等待数据库启动... ($i/30)"
sleep 2
done
# 启动后端服务
docker run -d --name e2e-backend-${BUILD_NUMBER} \
--link e2e-postgres-${BUILD_NUMBER}:postgres \
-e SPRING_R2DBC_URL=r2dbc:postgresql://postgres:5432/${DB_NAME} \
-e SPRING_R2DBC_USERNAME=${DB_USER} \
-e SPRING_R2DBC_PASSWORD=${DB_PASSWORD} \
-e SPRING_FLYWAY_URL=jdbc:postgresql://postgres:5432/${DB_NAME} \
-e SPRING_FLYWAY_USER=${DB_USER} \
-e SPRING_FLYWAY_PASSWORD=${DB_PASSWORD} \
-p 8081:8080 \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_BACKEND}:latest || true
# 等待后端就绪
for i in $(seq 1 60); do
if curl -sf http://localhost:8081/actuator/health > /dev/null 2>&1; then
echo "后端服务已就绪"
break
fi
echo "等待后端启动... ($i/60)"
sleep 3
done
'''
dir(FRONTEND_DIR) {
sh '''
# 安装Playwright浏览器
pnpm exec playwright install --with-deps chromium
# 执行E2E测试(带重试)
RETRY=0
MAX_RETRY=${RETRY_COUNT}
until [ $RETRY -ge $MAX_RETRY ]; do
pnpm run test:e2e:journeys && break
RETRY=$((RETRY+1))
echo "E2E测试第${RETRY}次重试..."
sleep 10
done
if [ $RETRY -ge $MAX_RETRY ]; then
echo "E2E测试在${MAX_RETRY}次重试后仍然失败"
exit 1
fi
'''
}
}
post {
always {
sh '''
# 清理E2E测试容器
docker stop e2e-backend-${BUILD_NUMBER} 2>/dev/null || true
docker rm e2e-backend-${BUILD_NUMBER} 2>/dev/null || true
docker stop e2e-postgres-${BUILD_NUMBER} 2>/dev/null || true
docker rm e2e-postgres-${BUILD_NUMBER} 2>/dev/null || true
'''
dir(FRONTEND_DIR) {
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 '⚠️ 流水线执行不稳定!'
// 可以添加不稳定状态通知
}
}
}