diff --git a/Jenkinsfile b/Jenkinsfile index 65995822..5ceca915 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,13 +1,13 @@ // Pipeline CI/CD — Aduanasoft Anexo76 -// Ejecuta: tests (backend + frontend en paralelo) → security scan → docker build/push → deploy dev → smoke test +// Ejecuta: tests → security scan → docker build/push → deploy dev → E2E → smoke test // // Credenciales requeridas en Jenkins: // - harbor-credentials : username/password para dev.aduanasoft.com // - a76-public-url-dev : Secret Text — URL pública base (ej. https://anexo76-dev.aduanasoft.com) -// Se usa para construir VITE_API_URL y VITE_KEYCLOAK_URL en el build del frontend // - dev-server-user : Secret Text — usuario SSH del servidor dev // - dev-server-host : Secret Text — hostname/IP del servidor dev // - dev-server-ssh : SSH Username with Private Key — llave privada para deploy +// - a76-e2e-credentials : Username with password — usuario de prueba en Workspace // // El agente Jenkins solo necesita: Docker CLI @@ -22,8 +22,7 @@ pipeline { // Debe coincidir con @playwright/test del frontend (ver frontend/pnpm-lock.yaml) PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.56.1-noble' DEPLOY_PATH = '~/projects/anexo76' - // URL de Keycloak/Workspace accesible desde el agente Jenkins. - // Difiere de A76_URL/kcauth porque Keycloak vive en Workspace, no en el servidor de Anexo76. + // URL de Keycloak/Workspace — Keycloak vive en Workspace, no en el servidor de Anexo76 KC_URL = 'https://workspace.aduanasoft.com/kcauth' } @@ -36,7 +35,7 @@ pipeline { stages { - // ── Paso 5b — Tests (backend y frontend en paralelo) ────────────────── + // ── Tests (backend y frontend unit en paralelo) ─────────────────────── stage('Tests') { parallel { @@ -134,138 +133,40 @@ pipeline { } } - stage('Test — Frontend') { - stages { + stage('Test — Frontend (Vitest)') { + steps { + sh ''' + set -euo pipefail + C="a76-test-fe-${BUILD_NUMBER}" + cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } + trap cleanup EXIT - stage('Unit Tests (Vitest)') { - steps { - sh ''' - set -euo pipefail - C="a76-test-fe-${BUILD_NUMBER}" - cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } - trap cleanup EXIT - - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity - docker exec "$C" mkdir -p /workspace - docker cp "$WORKSPACE/." "$C:/workspace" - - docker exec \ - -e CI=true \ - -e NODE_ENV=test \ - -e "JENKINS_URL=${JENKINS_URL}" \ - -e JENKINS_VITEST_FULL=1 \ - -w /workspace/frontend \ - "$C" bash -lc ' - set -euxo pipefail - npm install -g pnpm@9 --quiet - pnpm install --frozen-lockfile - pnpm run i18n:compile - pnpm run test:unit -- --run - ' - ''' - } - } - - stage('E2E (Playwright)') { - steps { - // auth.setup.ts navega a /login → Workspace → llena form → /dashboard - // y guarda storageState para los tests dependientes. - // - // Credenciales requeridas en Jenkins: - // a76-public-url-dev : URL pública base de Anexo76 - // a76-e2e-credentials : Username with password — usuario de prueba en Workspace - withCredentials([ - string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'), - usernamePassword( - credentialsId: 'a76-e2e-credentials', - usernameVariable: 'E2E_USER', - passwordVariable: 'E2E_PASS' - ) - ]) { - sh ''' - set -euo pipefail - C="a76-test-e2e-${BUILD_NUMBER}" - cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } - trap cleanup EXIT - - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity - docker exec "$C" mkdir -p /workspace - docker cp "$WORKSPACE/." "$C:/workspace" - - docker exec \ - -e CI=true \ - -e NODE_ENV=test \ - -e "JENKINS_URL=${JENKINS_URL}" \ - -e PLAYWRIGHT_TEST_BASE_URL=http://localhost:5173 \ - -e "VITE_API_URL=${A76_URL}/api/" \ - -e "INTERNAL_API_URL=${A76_URL}/api/" \ - -e "VITE_KEYCLOAK_URL=${KC_URL}" \ - -e "KEYCLOAK_URL=${KC_URL}" \ - -e VITE_KEYCLOAK_REALM=master \ - -e KEYCLOAK_REALM=master \ - -e VITE_KEYCLOAK_CLIENT_ID=hub-frontend \ - -e KEYCLOAK_CLIENT_ID=hub-frontend \ - -e ORIGIN=http://localhost:5173 \ - -e "KC_URL=${KC_URL}" \ - -e "E2E_TEST_USER=${E2E_USER}" \ - -e "E2E_TEST_PASSWORD=${E2E_PASS}" \ - -w /workspace/frontend \ - "$C" bash -lc ' - set -euxo pipefail - npm install -g pnpm@9 --quiet - pnpm install --frozen-lockfile - pnpm run i18n:compile - - # Arrancar dev server - pnpm run dev & - DEV_PID=$! - - # Esperar a que el dev server esté listo (máx 90s, primer request lento por compilación) - READY=0 - for i in $(seq 1 45); do - if curl -sf --max-time 5 http://localhost:5173 >/dev/null 2>&1; then - READY=1; break - fi - sleep 2 - done - if [ "$READY" != "1" ]; then - echo "ERROR: Dev server no arrancó en 90s." - kill "$DEV_PID" 2>/dev/null || true - exit 1 - fi - - # Ejecutar E2E; capturar exit code para no perder el reporte - E2E_EXIT=0 - PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ - pnpm exec playwright test --reporter=junit,html || E2E_EXIT=$? - - kill "$DEV_PID" 2>/dev/null || true - exit "$E2E_EXIT" - ' - docker cp "$C:/workspace/frontend/playwright-results.xml" \ - "$WORKSPACE/frontend/playwright-results.xml" 2>/dev/null || true - docker cp "$C:/workspace/frontend/playwright-report" \ - "$WORKSPACE/frontend/playwright-report" 2>/dev/null || true - ''' - } - } - post { - always { - junit allowEmptyResults: true, testResults: 'frontend/playwright-results.xml' - archiveArtifacts artifacts: 'frontend/playwright-report/**', allowEmptyArchive: true - } - } - } + docker rm -f "$C" >/dev/null 2>&1 || true + docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity + docker exec "$C" mkdir -p /workspace + docker cp "$WORKSPACE/." "$C:/workspace" + docker exec \ + -e CI=true \ + -e NODE_ENV=test \ + -e "JENKINS_URL=${JENKINS_URL}" \ + -e JENKINS_VITEST_FULL=1 \ + -w /workspace/frontend \ + "$C" bash -lc ' + set -euxo pipefail + npm install -g pnpm@9 --quiet + pnpm install --frozen-lockfile + pnpm run i18n:compile + pnpm run test:unit -- --run + ' + ''' } } } } - // ── Pasos 5c-5d — Escaneo de vulnerabilidades ──────────────────────── + // ── Escaneo de vulnerabilidades ─────────────────────────────────────── stage('Security Scan') { parallel { stage('pip-audit') { @@ -340,7 +241,7 @@ pipeline { } } - // ── Paso 5e — Docker Build + Push ───────────────────────────────────── + // ── Docker Build + Push ─────────────────────────────────────────────── stage('Docker Build') { when { anyOf { branch 'main'; branch 'development' } @@ -391,7 +292,7 @@ pipeline { } } - // ── Paso 5f — Deploy Dev ─────────────────────────────────────────────── + // ── Deploy Dev ──────────────────────────────────────────────────────── stage('Deploy — Dev') { when { branch 'development' } steps { @@ -441,6 +342,66 @@ pipeline { } } + // ── E2E (Playwright) — contra la URL desplegada ─────────────────────── + // Corre después del deploy para que Workspace confíe en el dominio de retorno. + // No levanta dev server — Playwright apunta directamente a A76_URL. + stage('E2E (Playwright)') { + when { branch 'development' } + steps { + withCredentials([ + string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'), + usernamePassword( + credentialsId: 'a76-e2e-credentials', + usernameVariable: 'E2E_USER', + passwordVariable: 'E2E_PASS' + ) + ]) { + sh ''' + set -euo pipefail + C="a76-test-e2e-${BUILD_NUMBER}" + cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } + trap cleanup EXIT + + docker rm -f "$C" >/dev/null 2>&1 || true + docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity + docker exec "$C" mkdir -p /workspace + docker cp "$WORKSPACE/." "$C:/workspace" + + docker exec \ + -e CI=true \ + -e "JENKINS_URL=${JENKINS_URL}" \ + -e "PLAYWRIGHT_TEST_BASE_URL=${A76_URL}" \ + -e "E2E_TEST_USER=${E2E_USER}" \ + -e "E2E_TEST_PASSWORD=${E2E_PASS}" \ + -w /workspace/frontend \ + "$C" bash -lc ' + set -euxo pipefail + npm install -g pnpm@9 --quiet + pnpm install --frozen-lockfile + pnpm run i18n:compile + + # Ejecutar E2E contra la URL desplegada + E2E_EXIT=0 + PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ + pnpm exec playwright test --reporter=junit,html || E2E_EXIT=$? + + exit "$E2E_EXIT" + ' + docker cp "$C:/workspace/frontend/playwright-results.xml" \ + "$WORKSPACE/frontend/playwright-results.xml" 2>/dev/null || true + docker cp "$C:/workspace/frontend/playwright-report" \ + "$WORKSPACE/frontend/playwright-report" 2>/dev/null || true + ''' + } + } + post { + always { + junit allowEmptyResults: true, testResults: 'frontend/playwright-results.xml' + archiveArtifacts artifacts: 'frontend/playwright-report/**', allowEmptyArchive: true + } + } + } + // ── Smoke Test ──────────────────────────────────────────────────────── stage('Smoke Test') { when { branch 'development' }