From 7704c764e82fa9a86725e8db27b5d8826c9ac799 Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 14 Feb 2025 13:34:16 +0100 Subject: [PATCH] test: refs #6695 run e2e in parallel in local --- cypress.config.js | 6 +- docker-compose.e2e.local.yml | 99 +++++++++++++++++ docker-compose.e2e.yml | 13 ++- test/cypress/.gitignore | 1 + test/cypress/Dockerfile | 12 +++ test/cypress/docker/run/cleanup.sh | 24 +++++ test/cypress/docker/run/main.sh | 26 +++++ test/cypress/docker/run/run_group.sh | 102 ++++++++++++++++++ test/cypress/docker/run/setup.sh | 45 ++++++++ test/cypress/docker/run/summary.sh | 15 +++ .../integration/claim/claimNotes.spec.js | 6 +- 11 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 docker-compose.e2e.local.yml create mode 100644 test/cypress/Dockerfile create mode 100644 test/cypress/docker/run/cleanup.sh create mode 100644 test/cypress/docker/run/main.sh create mode 100644 test/cypress/docker/run/run_group.sh create mode 100644 test/cypress/docker/run/setup.sh create mode 100644 test/cypress/docker/run/summary.sh diff --git a/cypress.config.js b/cypress.config.js index fa8b23d63..24b2f1ed4 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -20,7 +20,7 @@ export default defineConfig({ downloadsFolder: 'test/cypress/downloads', video: false, specPattern: 'test/cypress/integration/**/*.spec.js', - // specPattern: 'test/cypress/integration/route/routeList.spec.js', + // specPattern: 'test/cypress/integration/client/clientList.spec.js', experimentalRunAllSpecs: true, watchForFileChanges: true, reporter: 'cypress-mochawesome-reporter', @@ -39,8 +39,8 @@ export default defineConfig({ }, setupNodeEvents: async (on, config) => { on('file:preprocessor', vitePreprocessor()); - const plugin = await import('cypress-mochawesome-reporter/plugin'); - plugin.default(on); + // const plugin = await import('cypress-mochawesome-reporter/plugin'); + // plugin.default(on); return config; }, viewportWidth: 1280, diff --git a/docker-compose.e2e.local.yml b/docker-compose.e2e.local.yml new file mode 100644 index 000000000..e881598fa --- /dev/null +++ b/docker-compose.e2e.local.yml @@ -0,0 +1,99 @@ +version: '3.7' +services: + back: + image: registry.verdnatura.es/salix-back:25.08.0-build1314 + # image: back_try + volumes: + - ./test/cypress/storage:/salix/storage + - ./test/cypress/back/datasources.json:/salix/loopback/server/datasources.json + depends_on: + - vn-database + # ports: + # - '3000:3000' + front: + image: alexmorenovn/vndev:latest + command: quasar dev + volumes: + - .:/app:delegated + working_dir: /app + environment: + - TZ=Europe/Madrid + # ports: + # - '9000:9000' + + e2e: + image: cypress-setup:latest + command: sh -c "while [ ! -d node_modules/cypress ]; do sleep 1; done && pnpm exec cypress run --browser chromium --spec ${CYPRESS_SPEC:?}" + environment: + - TZ=Europe/Madrid + volumes: + - .:/app + working_dir: /app + cypress-setup: + image: cypress-setup:latest + build: + context: . + dockerfile: ./test/cypress/Dockerfile + command: sh -c "pnpm install --frozen-lockfile && pnpm exec cypress install" + volumes: + - .:/app:delegated + vn-database: + image: alexmorenovn/vn_db:latest + # ports: + # - '3306:3306' + + # e2e: + # command: npx cypress run --browser chromium + # build: + # context: . + # dockerfile: ./Dockerfile.e2e + # volumes: + # - .:/app + # working_dir: /app + + # front: + # # command: pnpx quasar dev + # # command: npx quasar serve --history --proxy ./proxy.mjs --hostname 127.0.0.1 --port 9000 + # build: + # context: . + # dockerfile: ./Dockerfile.e2e + # network_mode: host + # e2e: + # command: pnpx cypress run --browser chromium + # build: + # context: . + # dockerfile: ./Dockerfile.e2e + # network_mode: host + # volumes: + # - ./node_modules:/app/node_modules + # db: + # image: db + # command: npx myt run -t --ci -d -n front_default + # build: + # context: . + # dockerfile: test/cypress/db/Dockerfile + # network_mode: host + # privileged: true + # volumes: + # - /var/run/docker.sock:/var/run/docker.sock + + # back: + # image: back + # build: + # context: ./salix + # dockerfile: salix/back/Dockerfile + # # depends_on: + # # - db + # ports: + # - 3000:3000 + # - 5000:5000 + # volumes: + # - ./test/cypress/storage:/salix/storage + + # e2e-2: + # image: registry.verdnatura.es/salix-frontend:${VERSION:?} + # command: npx cypress run --config-file test/cypress/configs/cypress.config.2.js + # build: + # context: . + # dockerfile: ./Dockerfile.e2e + # diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 3e7c4a217..ed81ee188 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -21,14 +21,21 @@ services: # ports: # - '9000:9000' e2e: - image: alexmorenovn/vndev:latest - # command: pnpm exec cypress run --browser chromium - command: sh -c "pnpm exec cypress install && pnpm exec cypress run --headed" + image: cypress-setup:latest + command: sh -c "while [ ! -d node_modules/cypress ]; do sleep 1; done && pnpm exec cypress run --browser chromium" environment: - TZ=Europe/Madrid volumes: - .:/app working_dir: /app + cypress-setup: + image: cypress-setup:latest + build: + context: . + dockerfile: ./test/cypress/Dockerfile + command: sh -c "pnpm install --frozen-lockfile && pnpm exec cypress install" + volumes: + - .:/app vn-database: image: alexmorenovn/vn_db:latest # ports: diff --git a/test/cypress/.gitignore b/test/cypress/.gitignore index 21e8b4bd8..38a5ca941 100644 --- a/test/cypress/.gitignore +++ b/test/cypress/.gitignore @@ -3,3 +3,4 @@ screenshots/* storage/* reports/* downloads/* +docker/logs/* diff --git a/test/cypress/Dockerfile b/test/cypress/Dockerfile new file mode 100644 index 000000000..33a8f2210 --- /dev/null +++ b/test/cypress/Dockerfile @@ -0,0 +1,12 @@ +FROM alexmorenovn/vndev:latest + +WORKDIR /app + +# Copiar los archivos de package.json y pnpm-lock.yaml para evitar reinstalar dependencias innecesariamente +COPY package.json pnpm-lock.yaml ./ + +# Instalar solo Cypress sin instalar todas las dependencias del proyecto +RUN pnpm install --frozen-lockfile && pnpm exec cypress install + +# Definir el directorio de trabajo por defecto +WORKDIR /app diff --git a/test/cypress/docker/run/cleanup.sh b/test/cypress/docker/run/cleanup.sh new file mode 100644 index 000000000..db0c897f1 --- /dev/null +++ b/test/cypress/docker/run/cleanup.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +cleanup() { + echo "⏹ Deteniendo ejecución..." + + # Detener todos los procesos en paralelo + kill "${pids[@]}" 2>/dev/null + + # Buscar y eliminar contenedores que comiencen con NETWORK + containers=$(docker ps -aq --filter "name=^${NETWORK}") + if [[ -n "$containers" ]]; then + # echo "🧹 Eliminando contenedores: $containers" + docker rm -fv $containers >/dev/null 2>&1 || true + echo "✅ → ⏹🧹 Detenido y eliminado contenedores correctamente" + fi + + # Buscar y eliminar redes que comiencen con NETWORK + networks=$(docker network ls --format '{{.Name}}' | grep "^${NETWORK}" || true) + if [[ -n "$networks" ]]; then + # echo "🧹 Eliminando redes: $networks" + docker network rm $networks >/dev/null 2>&1 || true + echo "✅ → 🧹 Redes eliminadas correctamente" + fi +} diff --git a/test/cypress/docker/run/main.sh b/test/cypress/docker/run/main.sh new file mode 100644 index 000000000..23a0d6b9f --- /dev/null +++ b/test/cypress/docker/run/main.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Cargar módulos +source "$(dirname "$0")/cleanup.sh" +source "$(dirname "$0")/setup.sh" +source "$(dirname "$0")/run_group.sh" +source "$(dirname "$0")/summary.sh" + +# Manejo de señales para limpiar si se interrumpe el script +trap cleanup SIGINT + +# Ejecutar grupos en paralelo y almacenar PIDs +for i in "${!groups[@]}"; do + run_group "${groups[$i]}" "$((i+1))" & # Ejecutar en segundo plano + pids+=($!) # Guardar el PID del proceso +done + +# Esperar a que terminen todos los procesos en segundo plano +wait "${pids[@]}" + +# Generar el resumen final +generate_summary + +# Limpiar contenedores al finalizar +cleanup +exit 0 diff --git a/test/cypress/docker/run/run_group.sh b/test/cypress/docker/run/run_group.sh new file mode 100644 index 000000000..f22a18704 --- /dev/null +++ b/test/cypress/docker/run/run_group.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# Función para esperar a que un servicio devuelva un JSON con `{ "status": true }` en la red de Docker +wait_for_api_ready() { + local service_name="$1" + local container_name="$2" + local port="$3" + local path="$4" + local network="$5" + local max_retries=30 # Máximo de intentos (30 segundos) + local retries=0 + local url="http://$container_name:$port$path" + + # echo "⏳ Esperando a que $service_name devuelva exactamente 'true' en $url..." + + while [[ $retries -lt $max_retries ]]; do + response=$(docker run --rm --network="$network" curlimages/curl -s "$url" || echo "error") + + # echo "🔍 Respuesta recibida de $service_name: '$response'" + + if [[ "$response" == "true" ]]; then + # echo "✅ Conectado al servicio $service_name → $url!" + return 0 + fi + + sleep 1 + ((retries++)) + done + + echo "❌ ERROR: $service_name no respondió con 'true' en $url después de $max_retries intentos." + exit 1 +} + +run_group() { + local group="$1" + local parallelIndex="$2" + local groupIndex=1 + + echo "=== Ejecutando grupo paralelo ${parallelIndex} ===" + docker-compose -p lilium-e2e -f docker-compose.e2e.local.yml build cypress-setup >/dev/null 2>&1 + + for testFolder in $group; do + folderName=$(basename "$testFolder" | tr -cd 'a-zA-Z0-9_-') + uniqueName="${NETWORK}_${folderName}_${parallelIndex}_${groupIndex}" + + echo "🔹 $folderName (Grupo: $parallelIndex, Índice: $groupIndex)" + + export CYPRESS_SPEC="test/cypress/integration/${folderName}/**/*.spec.js" + + # Iniciar servicios del backend y frontend + docker-compose -p "$uniqueName" -f docker-compose.e2e.local.yml up -d back >/dev/null 2>&1 + docker-compose -p "$uniqueName" -f docker-compose.e2e.local.yml up -d front >/dev/null 2>&1 + + # 🔹 Esperar a que la API en /api/Applications/status devuelva { "status": true } + wait_for_api_ready "Aplicación" "front" 9000 "/api/Applications/status" "${uniqueName}_default" + + # 🚀 Ejecutar pruebas en modo detach + docker-compose -p "$uniqueName" -f docker-compose.e2e.local.yml up -d e2e >/dev/null 2>&1 + + # 🔹 Esperar hasta que el contenedor de Cypress finalice + container_id="" + max_retries=10 + retries=0 + while [[ -z "$container_id" && $retries -lt $max_retries ]]; do + sleep 2 + container_id=$(docker-compose -p "$uniqueName" -f docker-compose.e2e.local.yml ps -q e2e) + ((retries++)) + done + + if [[ -z "$container_id" ]]; then + echo "⚠️ No se pudo obtener el contenedor para ${folderName} después de $max_retries intentos" + failedTests+=("$folderName") + continue + fi + + # echo "📦 Contenedor $container_id encontrado. Esperando a que finalice..." + + # 🔹 Esperar activamente a que el contenedor finalice + while true; do + container_status=$(docker inspect -f '{{.State.Running}}' "$container_id" 2>/dev/null || echo "false") + if [[ "$container_status" == "false" ]]; then + break + fi + sleep 2 + done + + # Verificar el código de salida + exit_code=$(docker inspect -f '{{.State.ExitCode}}' "$container_id" 2>/dev/null || echo "1") + + if [[ "$exit_code" -ne 0 ]]; then + echo "⚠️ Error en la ejecución de ${folderName} (Exit Code: $exit_code)" + buildResult="UNSTABLE" + docker logs "$container_id" > "test/cypress/docker/logs/${folderName}.log" 2>/dev/null || true + failedTests+=("$folderName") + fi + + # Limpiar contenedores + docker-compose -p "$uniqueName" -f docker-compose.e2e.local.yml down >/dev/null 2>&1 || true + + ((groupIndex++)) + done +} diff --git a/test/cypress/docker/run/setup.sh b/test/cypress/docker/run/setup.sh new file mode 100644 index 000000000..4841e0b67 --- /dev/null +++ b/test/cypress/docker/run/setup.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Configuración: Número de grupos en paralelo (por defecto 4, pero puede sobreescribirse con el primer argumento) +numParallelGroups=${1:-4} +NETWORK="lilium-e2e" +pids=() # Para almacenar los procesos en paralelo +failedTests=() # Para almacenar las carpetas que fallaron + +# Limpiar la carpeta de logs antes de cada ejecución +LOG_DIR="test/cypress/docker/logs" +if [[ -d "$LOG_DIR" ]]; then + echo "🧹 Borrando logs anteriores en $LOG_DIR..." + rm -rf "$LOG_DIR" +fi +mkdir -p "$LOG_DIR" + +# Verificar si se pasó una carpeta específica como segundo parámetro +if [[ -n "$2" ]]; then + if [[ -d "test/cypress/integration/$2" ]]; then + folders=() + for ((i = 0; i < numParallelGroups; i++)); do + folders+=("test/cypress/integration/$2/") + done + echo "🔍 Ejecutando '$2' en $numParallelGroups instancias paralelas." + else + echo "❌ La carpeta especificada '$2' no existe." + exit 1 + fi +else + # Obtener todas las carpetas de pruebas si no se especificó una + folders=($(ls -d test/cypress/integration/*/ 2>/dev/null)) + if [[ ${#folders[@]} -eq 0 ]]; then + echo "No se encontraron carpetas de pruebas." + exit 0 + fi +fi + +# Calcular el tamaño de cada grupo +groupSize=$(( (${#folders[@]} + numParallelGroups - 1) / numParallelGroups )) # Redondeo hacia arriba + +# Dividir las carpetas en grupos +groups=() +for ((i = 0; i < ${#folders[@]}; i += groupSize)); do + groups+=("$(printf "%s " "${folders[@]:i:groupSize}")") +done diff --git a/test/cypress/docker/run/summary.sh b/test/cypress/docker/run/summary.sh new file mode 100644 index 000000000..04e4c6a87 --- /dev/null +++ b/test/cypress/docker/run/summary.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +generate_summary() { + # Verificar si hay archivos en el directorio de logs (indicando fallos) + if [[ -d "$LOG_DIR" && "$(ls -A "$LOG_DIR")" ]]; then + echo "❌ Se encontraron fallos en los siguientes tests:" + for log_file in "$LOG_DIR"/*.log; do + test_name=$(basename "$log_file" .log) + echo " - $test_name (log en $log_file)" + done + exit 1 # Devolver código de error para que Jenkins lo detecte + else + echo "✅ Todas las pruebas han pasado correctamente." + fi +} diff --git a/test/cypress/integration/claim/claimNotes.spec.js b/test/cypress/integration/claim/claimNotes.spec.js index d7a918db1..576671a38 100644 --- a/test/cypress/integration/claim/claimNotes.spec.js +++ b/test/cypress/integration/claim/claimNotes.spec.js @@ -8,7 +8,11 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; - cy.get('.q-textarea').type(message); + cy.get('.q-textarea') + .should('be.visible') + .should('not.be.disabled') + .type(message); + cy.get(saveBtn).click(); cy.get(firstNote).should('have.text', message); });