estructura_automation
Version:
Paquete de estructura de automation
429 lines (373 loc) • 14.4 kB
JavaScript
/**
* ========================================================================================
* HTML GENERATOR - Generación de Documentación Web Interactiva
* ----------------------------------------------------------------------------------------
* Responsabilidades:
* - Generar HTML principal con estructura completa
* - Crear vistas dinámicas para navegación jerárquica
* - Generar breadcrumbs y navegación
* - Implementar sistema de búsqueda inteligente
* - Embeber JavaScript para interactividad
* ========================================================================================
*/
/**
* Genera breadcrumbs dinámicos con navegación completa hacia atrás
* @param {Array} pathArray - Array con la ruta
* @returns {string} HTML de breadcrumbs
*/
const generateBreadcrumbs = (pathArray) => {
let breadcrumbs = `<a href="http://127.0.0.1:5501/report/matrizPrueba.html">Aplicaciones</a>`;
// Hacer clickeables todos los niveles anteriores (excepto el último)
pathArray.forEach((pathItem, index) => {
breadcrumbs += `<span class="separator">/</span>`;
if (index < pathArray.length - 1) {
// Elementos intermedios - clickeables para navegar hacia atrás
const nodeIdParts = pathArray.slice(0, index + 1).map(part =>
part.toLowerCase().replace(/\s+/g, '-')
);
const nodeId = nodeIdParts.join('_');
breadcrumbs += `<a href="#" onclick="navigateToNode('${nodeId}'); return false;">${pathItem}</a>`;
} else {
// Último elemento - no clickeable (es la página actual)
breadcrumbs += `<span>${pathItem}</span>`;
}
});
return breadcrumbs;
};
/**
* Genera vistas dinámicas para cualquier nodo (carpeta o conjunto de test cases)
* @param {Object} node - Nodo actual
* @param {Array} pathArray - Ruta completa hasta este nodo
* @returns {string} HTML generado
*/
const generateNodeViews = (node, pathArray) => {
let html = '';
// 1. VISTA DE NAVEGACIÓN (si tiene subcarpetas o test cases)
if (node.hasSubfolders || node.hasFeatures) {
const viewId = `${node.id}-view`;
const fullPath = [...pathArray, node.name];
// Solo la vista raíz (sin pathArray) se muestra inicialmente
const isRootView = pathArray.length === 0;
const hiddenClass = isRootView ? '' : 'hidden';
html += `
<div id="${viewId}" class="node-view ${hiddenClass}">
<div class="breadcrumbs">
${generateBreadcrumbs(fullPath)}
</div>
<div class="grid-container">`;
// MOSTRAR SUBCARPETAS PRIMERO (ordenadas)
Object.values(node.children || {}).forEach(child => {
const childTestCount = child.testCases.length;
const childSubfolderCount = Object.keys(child.children || {}).length;
html += `
<div class="card" onclick="navigateToNode('${child.id}')">
<div class="card-header">${child.name}</div>
<div class="card-content">`;
if (childSubfolderCount > 0) {
html += `
<div class="count-badge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2l5 0 2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
${childSubfolderCount} carpeta${childSubfolderCount !== 1 ? 's' : ''}
</div>`;
}
if (childTestCount > 0) {
html += `
<div class="count-badge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 11 12 14 22 4"></polyline>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</svg>
${childTestCount} test${childTestCount !== 1 ? 's' : ''}
</div>`;
}
html += `
</div>
</div>`;
});
// MOSTRAR TEST CASES DESPUÉS (si los hay en este nivel)
node.testCases.forEach(testCase => {
const totalSteps = testCase.steps.length;
html += `
<div class="card test-case-card" data-id="${testCase.id}" data-name="${testCase.name}" onclick="showSteps('${testCase.id}')">
<div class="card-header">${testCase.name}</div>
<div class="card-content">
<div class="count-badge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 11 12 14 22 4"></polyline>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</svg>
${totalSteps} paso${totalSteps !== 1 ? 's' : ''}
</div>
${testCase.isManual ? `
<div class="manual-badge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
</svg>
Manual
</div>` : ''}
<div class="test-id-badge">ID: ${testCase.id}</div>
</div>
</div>`;
});
html += `
</div>
</div>`;
}
// 2. VISTAS DE PASOS para cada test case
node.testCases.forEach(testCase => {
const fullPath = [...pathArray, node.name];
html += `
<div id="${testCase.id}-steps-view" class="steps-view hidden">
<div class="breadcrumbs">
${generateBreadcrumbs([...fullPath, testCase.name])}
</div>
<div class="test-steps-container">
<div class="test-steps-header">
<span>${testCase.name}</span>
<span class="test-case-id">ID: ${testCase.id}</span>
</div>`;
testCase.steps.forEach(step => {
html += `<div class="step-item">${step.text}</div>`;
});
html += `
</div>
</div>`;
});
// 3. RECURSIÓN para todos los hijos
Object.values(node.children || {}).forEach(child => {
html += generateNodeViews(child, [...pathArray, node.name]);
});
return html;
};
/**
* Genera el JavaScript embebido para la funcionalidad del frontend
* @returns {string} Código JavaScript completo
*/
const generateEmbeddedJavaScript = () => {
return `
<script>
// Sistema de navegación dinámico universal
const navigateToNode = (nodeId) => {
hideAllViews();
const targetView = document.getElementById(nodeId + '-view');
if (targetView) {
targetView.classList.remove('hidden');
} else {
console.error('Vista no encontrada:', nodeId + '-view');
}
}
const showSteps = (testCaseId) => {
hideAllViews();
const targetView = document.getElementById(testCaseId + '-steps-view');
if (targetView) {
targetView.classList.remove('hidden');
}
}
const hideAllViews = () => {
const allViews = document.querySelectorAll('.node-view, .steps-view');
allViews.forEach(view => view.classList.add('hidden'));
// Limpiar vistas de búsqueda
const searchView = document.getElementById('search-results-view');
if (searchView) {
searchView.remove();
}
}
// Funcionalidad de búsqueda mejorada
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
const searchButton = document.getElementById('searchButton');
const searchTestCases = () => {
const searchTerm = searchInput.value.toLowerCase().trim();
if (!searchTerm) {
hideAllViews();
const firstView = document.querySelector('.node-view:not(.hidden)') || document.querySelector('.node-view');
if (firstView) {
firstView.classList.remove('hidden');
}
return;
}
const allTestCards = document.querySelectorAll('.test-case-card');
let exactMatch = null;
let foundCards = [];
allTestCards.forEach((card) => {
const cardName = card.getAttribute('data-name');
const cardId = card.getAttribute('data-id');
if (!cardName || !cardId) return;
const cardNameLower = cardName.toLowerCase();
const cardIdLower = cardId.toLowerCase();
if (cardIdLower === searchTerm) {
exactMatch = card;
return;
}
if (cardNameLower.includes(searchTerm) || cardIdLower.includes(searchTerm)) {
foundCards.push(card);
}
});
if (exactMatch) {
showSearchResults([exactMatch], searchTerm, true);
} else if (foundCards.length > 0) {
showSearchResults(foundCards, searchTerm, false);
} else {
alert('No se encontró: ' + searchTerm);
}
}
const showSearchResults = (matchingCards, searchTerm, isExactMatch = false) => {
// Limpiar vista anterior
const oldView = document.getElementById('search-results-view');
if (oldView) oldView.remove();
// Crear nueva vista
const searchResultsView = document.createElement('div');
searchResultsView.id = 'search-results-view';
searchResultsView.className = 'node-view';
const resultType = isExactMatch ? '🎯 ID Exacto' : '🔍 Resultados';
const breadcrumbsHTML = '<div class="breadcrumbs">' +
'<a href="http://127.0.0.1:5501/report/matrizPrueba.html">Aplicaciones</a>' +
'<span class="separator">/</span>' +
'<span>' + resultType + ': "' + searchTerm + '" (' + matchingCards.length + ')</span>' +
'</div>';
let cardsHTML = '';
matchingCards.forEach(card => {
cardsHTML += card.outerHTML;
});
const gridHTML = '<div class="grid-container">' + cardsHTML + '</div>';
searchResultsView.innerHTML = breadcrumbsHTML + gridHTML;
// Ocultar todo y mostrar resultados
hideAllViews();
document.getElementById('content').appendChild(searchResultsView);
searchResultsView.style.display = 'block';
// Resaltar la tarjeta
const newCards = searchResultsView.querySelectorAll('.test-case-card');
newCards.forEach(card => {
card.classList.add('highlight');
});
}
searchButton.addEventListener('click', function(e) {
e.preventDefault();
searchTestCases();
});
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
searchTestCases();
}
});
// La vista raíz ya está visible por defecto
});
</script>`;
};
/**
* Genera el HTML principal completo
* @param {Object} app - Datos de la aplicación
* @param {Object} options - Opciones de configuración
* @returns {string} HTML completo
*/
const generateHTML = (app, options = {}) => {
const {
title = 'Casos de Prueba - Paguetodo',
logoPath = 'styles/logo_paguetodo.png',
cssPath = 'styles/styles.css'
} = options;
const html = `
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="${cssPath}" rel="stylesheet">
</head>
<body>
<div class="logo-container">
<img src="${logoPath}" alt="PagueTodo Logo" class="company-logo">
</div>
<div class="container">
<h1>${title}</h1>
<div class="fecha">Fecha de generación: ${new Date().toLocaleDateString()}</div>
<div class="search-box">
<input type="text" id="searchInput" placeholder="Buscar por nombre o ID de caso...">
<button id="searchButton">Buscar</button>
</div>
<div id="content">
${generateNodeViews(app, [])}
</div>
</div>
${generateEmbeddedJavaScript()}
</body>
</html>`;
return html;
};
/**
* Genera un reporte de estadísticas en HTML
* @param {Object} metadata - Metadata de tests
* @returns {string} HTML del reporte
*/
const generateStatsReport = (metadata) => {
const { statistics } = metadata;
return `
<div class="stats-report">
<h2>📊 Estadísticas de Tests</h2>
<div class="stats-grid">
<div class="stat-card">
<h3>Tests Totales</h3>
<span class="stat-number">${metadata.totalTests}</span>
</div>
<div class="stat-card">
<h3>Tests Manuales</h3>
<span class="stat-number">${metadata.manualTests}</span>
<span class="stat-percentage">${metadata.manualPercentage}%</span>
</div>
<div class="stat-card">
<h3>Tests Automatizables</h3>
<span class="stat-number">${metadata.automatableTests}</span>
<span class="stat-percentage">${metadata.automatablePercentage}%</span>
</div>
<div class="stat-card">
<h3>Pasos Totales</h3>
<span class="stat-number">${statistics.totalSteps}</span>
</div>
<div class="stat-card">
<h3>Promedio Pasos/Test</h3>
<span class="stat-number">${statistics.averageStepsPerTest}</span>
</div>
<div class="stat-card">
<h3>Tags Únicos</h3>
<span class="stat-number">${statistics.tagStatistics.totalUniqueTags}</span>
</div>
</div>
</div>`;
};
/**
* Genera una vista de debugging para desarrollo
* @param {Object} hierarchicalData - Datos jerárquicos
* @param {Object} metadata - Metadata
* @returns {string} HTML de debugging
*/
const generateDebugView = (hierarchicalData, metadata) => {
return `
<div class="debug-view" style="display: none;" id="debug-view">
<h2>🔧 Vista de Debugging</h2>
<details>
<summary>Estructura Jerárquica</summary>
<pre>${JSON.stringify(hierarchicalData, null, 2)}</pre>
</details>
<details>
<summary>Metadata</summary>
<pre>${JSON.stringify(metadata, null, 2)}</pre>
</details>
<button onclick="document.getElementById('debug-view').style.display='none'">Cerrar</button>
</div>
<button onclick="document.getElementById('debug-view').style.display='block'"
style="position: fixed; top: 10px; right: 10px; z-index: 1000;">
🔧 Debug
</button>`;
};
module.exports = {
generateBreadcrumbs,
generateNodeViews,
generateHTML,
generateEmbeddedJavaScript,
generateStatsReport,
generateDebugView
};