UNPKG

estructura_automation

Version:

Paquete de estructura de automation

199 lines (170 loc) 6.44 kB
/* eslint-disable @typescript-eslint/no-require-imports */ /** * ======================================================================================== * TEST SCANNER - Escaneo y Parsing de Archivos de Prueba * ---------------------------------------------------------------------------------------- * Responsabilidades: * - Parsear archivos .feature (Gherkin) * - Escanear directorios recursivamente * - Construir estructura jerárquica de datos * - Formatear nombres para visualización * ======================================================================================== */ const fs = require('fs'); const path = require('path'); /** * Analiza el contenido de un archivo .feature y extrae su estructura * @param {string} content - Contenido del archivo .feature * @returns {Object} Objeto estructurado con escenarios y sus pasos */ const parseFeatureFile = (content) => { const lines = content.split('\n'); const feature = { scenarios: [] }; let currentScenario = null; let inScenario = false; let pendingTags = []; lines.forEach(line => { const trimmedLine = line.trim(); // Detectar tags como @manual if (trimmedLine.startsWith('@')) { pendingTags.push(trimmedLine); return; } if (trimmedLine.startsWith('Scenario:') || trimmedLine.startsWith('Scenario Outline:')) { inScenario = true; if (currentScenario) { feature.scenarios.push(currentScenario); } currentScenario = { name: trimmedLine.substring(trimmedLine.indexOf(':') + 1).trim(), steps: [], tags: pendingTags.slice() // Copiar tags pendientes }; pendingTags = []; // Limpiar tags para el próximo escenario } else if (inScenario && ( trimmedLine.startsWith('Given ') || trimmedLine.startsWith('When ') || trimmedLine.startsWith('Then ') || trimmedLine.startsWith('And ') || trimmedLine.startsWith('But ') )) { // Eliminar las palabras reservadas de Gherkin completamente let stepText = trimmedLine; // Eliminar palabras reservadas al inicio de la línea stepText = stepText.replace(/^Given /, ''); stepText = stepText.replace(/^When /, ''); stepText = stepText.replace(/^Then /, ''); stepText = stepText.replace(/^And /, ''); stepText = stepText.replace(/^But /, ''); currentScenario.steps.push(stepText); } }); if (currentScenario) { feature.scenarios.push(currentScenario); } return feature; }; /** * Formatea un nombre para visualización * @param {string} name - Nombre a formatear * @returns {string} Nombre formateado */ const formatName = (name) => { return name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' '); }; /** * Recorre recursivamente el directorio de features y construye la estructura jerárquica * @param {string} featuresDir - Directorio base de features * @returns {Object} Estructura de datos organizada jerárquicamente */ const buildHierarchicalData = (featuresDir) => { const hierarchicalData = { apps: {} }; /** * Función recursiva mejorada para procesar directorios de cualquier profundidad * @param {string} currentPath - Ruta actual * @param {string} parentId - ID del padre * @param {Array} pathArray - Array con la ruta completa para breadcrumbs * @returns {Object} Nodo procesado */ const processDirectory = (currentPath, parentId = '', pathArray = []) => { const entries = fs.readdirSync(currentPath, { withFileTypes: true }); const baseName = path.basename(currentPath); const result = { id: parentId || baseName.toLowerCase().replace(/\s+/g, '-'), name: formatName(baseName), path: pathArray.slice(), // Copia del path completo testCases: [], children: {}, hasSubfolders: false, hasFeatures: false }; // 1. ORDENAR: Carpetas primero, luego archivos .feature const directories = entries.filter(entry => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)); const featureFiles = entries.filter(entry => !entry.isDirectory() && entry.name.endsWith('.feature')).sort((a, b) => a.name.localeCompare(b.name)); // 2. PROCESAR ARCHIVOS .FEATURE (casos de prueba en este nivel) if (featureFiles.length > 0) { result.hasFeatures = true; let globalTestCaseCounter = 1; // Contador global para este directorio featureFiles.forEach(featureFile => { const featureContent = fs.readFileSync(path.join(currentPath, featureFile.name), 'utf8'); const feature = parseFeatureFile(featureContent); feature.scenarios.forEach((scenario) => { const testCaseId = `${result.id}_tc_${globalTestCaseCounter}`; const isManual = scenario.tags && scenario.tags.includes('@manual'); result.testCases.push({ id: testCaseId, name: scenario.name, isManual: isManual, tags: scenario.tags || [], featureFile: featureFile.name, steps: scenario.steps.map((step, stepIndex) => ({ id: `${testCaseId}_step_${stepIndex + 1}`, text: step })) }); globalTestCaseCounter++; }); }); } // 3. PROCESAR SUBDIRECTORIOS RECURSIVAMENTE if (directories.length > 0) { result.hasSubfolders = true; directories.forEach(dir => { const childId = `${result.id}_${dir.name.toLowerCase().replace(/\s+/g, '-')}`; const childPath = [...pathArray, result.name]; result.children[childId] = processDirectory( path.join(currentPath, dir.name), childId, childPath ); }); } return result; }; // Verificar que el directorio existe if (!fs.existsSync(featuresDir)) { throw new Error(`El directorio ${featuresDir} no existe`); } // Procesar cada app como raíz const appDirs = fs.readdirSync(featuresDir, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .sort((a, b) => a.name.localeCompare(b.name)) .map(dir => dir.name); if (appDirs.length === 0) { throw new Error('No se encontraron carpetas dentro del directorio de features'); } appDirs.forEach(appName => { const appId = appName.toLowerCase().replace(/\s+/g, '-'); hierarchicalData.apps[appId] = processDirectory(path.join(featuresDir, appName), appId, []); }); return hierarchicalData; }; module.exports = { parseFeatureFile, formatName, buildHierarchicalData };