super-pancake-automation
Version:
A lightweight DOM-based UI automation framework using Chrome DevTools Protocol
716 lines (647 loc) • 31.1 kB
JavaScript
/**
* Base HTML template for the test report
* Generates the core HTML structure without inline CSS
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageRoot = path.resolve(__dirname, '../..');
export function generateSelfContainedTemplate(summary, results) {
// Read and embed all CSS and JavaScript files from package directory
const cssContent = fs.readFileSync(path.join(packageRoot, 'reporter/styles/reportStyles.css'), 'utf8');
const dataProcessingJs = fs.readFileSync(path.join(packageRoot, 'reporter/utils/dataProcessing.js'), 'utf8');
const tabNavigationJs = fs.readFileSync(path.join(packageRoot, 'reporter/utils/tabNavigation.js'), 'utf8');
const filterUtilsJs = fs.readFileSync(path.join(packageRoot, 'reporter/utils/filterUtils.js'), 'utf8');
const chartUtilsJs = fs.readFileSync(path.join(packageRoot, 'reporter/charts/chartUtils.js'), 'utf8');
const overviewTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/overviewTab.js'), 'utf8');
const performanceTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/performanceTab.js'), 'utf8');
const flakyTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/flakyTab.js'), 'utf8');
const coverageTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/coverageTab.js'), 'utf8');
const memoryTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/memoryTab.js'), 'utf8');
const parallelTabJs = fs.readFileSync(path.join(packageRoot, 'reporter/templates/parallelTab.js'), 'utf8');
// Use the passed results data instead of reading the raw JSON file
const testDataJson = JSON.stringify(results || [], null, 2);
// Format duration helper function for individual tests
const formatDuration = (duration) => {
const ms = duration || 0;
// For values >= 1 second, show in seconds
if (ms >= 1000) {
return (ms / 1000).toFixed(2) + 's';
}
// For smaller values, show in milliseconds
return (Math.round(ms * 100) / 100).toFixed(2) + 'ms';
};
// Format total duration with minutes and seconds for larger values
const formatTotalDuration = (duration) => {
const ms = duration || 0;
// For values >= 60 seconds, show in minutes and seconds format
if (ms >= 60000) {
const totalSeconds = Math.floor(ms / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes}m ${seconds}s`;
}
// For values >= 10 seconds, show in seconds with 1 decimal
if (ms >= 10000) {
return (ms / 1000).toFixed(1) + 's';
}
// For values >= 1 second, show in seconds with 2 decimals
if (ms >= 1000) {
return (ms / 1000).toFixed(2) + 's';
}
// For smaller values, show in milliseconds
return (Math.round(ms * 100) / 100).toFixed(2) + 'ms';
};
return `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🥞 Super Pancake Test Report</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
<style>
${cssContent}
</style>
</head>
<body>
<div class="trends-dashboard-content">
<div class="container">
<header class="header">
<div class="logo">
<i class="fas fa-layer-group"></i>
<h1>Super Pancake Test Report</h1>
</div>
<p class="subtitle">Comprehensive Test Analytics & Performance Insights</p>
<div class="header-meta">
<span><i class="fas fa-calendar"></i> ${new Date().toLocaleDateString()}</span>
<span><i class="fas fa-clock"></i> ${new Date().toLocaleTimeString()}</span>
<span><i class="fas fa-code-branch"></i> ${process.env.CI_BRANCH || 'main'}</span>
</div>
<div class="theme-toggle">
<button id="themeToggle" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
<span>Dark Mode</span>
</button>
</div>
</header>
<div class="dashboard">
<div class="summary-grid">
<div class="summary-card total">
<div class="card-icon">
<i class="fas fa-list-check"></i>
</div>
<div class="card-content">
<h3>${summary?.totalTests || 0}</h3>
<p>Total Tests</p>
</div>
</div>
<div class="summary-card passed">
<div class="card-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="card-content">
<h3>${summary?.passedTests || 0}</h3>
<p>Passed</p>
</div>
</div>
<div class="summary-card failed">
<div class="card-icon">
<i class="fas fa-times-circle"></i>
</div>
<div class="card-content">
<h3>${summary?.failedTests || 0}</h3>
<p>Failed</p>
</div>
</div>
<div class="summary-card skipped">
<div class="card-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="card-content">
<h3>${summary?.skippedTests || 0}</h3>
<p>Skipped</p>
</div>
</div>
<div class="summary-card success-rate">
<div class="card-icon">
<i class="fas fa-percentage"></i>
</div>
<div class="card-content">
<h3>${summary?.totalTests > 0 ? Math.round((summary.passedTests / summary.totalTests) * 100) : 0}%</h3>
<p>Success Rate</p>
</div>
</div>
<div class="summary-card duration">
<div class="card-icon">
<i class="fas fa-stopwatch"></i>
</div>
<div class="card-content">
<h3>${formatTotalDuration(summary?.totalDuration || 0)}</h3>
<p>Total Duration</p>
</div>
</div>
</div>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-segment passed" style="width: ${summary?.totalTests > 0 ? (summary.passedTests / summary.totalTests) * 100 : 0}%"></div>
<div class="progress-segment failed" style="width: ${summary?.totalTests > 0 ? (summary.failedTests / summary.totalTests) * 100 : 0}%"></div>
<div class="progress-segment skipped" style="width: ${summary?.totalTests > 0 ? (summary.skippedTests / summary.totalTests) * 100 : 0}%"></div>
</div>
<div class="progress-labels">
<span class="label passed">${summary?.passedTests || 0} Passed</span>
<span class="label failed">${summary?.failedTests || 0} Failed</span>
<span class="label skipped">${summary?.skippedTests || 0} Skipped</span>
</div>
</div>
</div>
</div>
<div class="analytics-dashboard">
<nav class="tab-navigation">
<button class="tab-btn active" onclick="switchTab('overview')">
<i class="fas fa-chart-pie"></i> Overview
</button>
<button class="tab-btn" onclick="switchTab('performance')">
<i class="fas fa-tachometer-alt"></i> Performance
</button>
<button class="tab-btn" onclick="switchTab('flaky')">
<i class="fas fa-exclamation-triangle"></i> Flaky Tests
</button>
<button class="tab-btn" onclick="switchTab('coverage')">
<i class="fas fa-shield-alt"></i> Coverage
</button>
<button class="tab-btn" onclick="switchTab('memory')">
<i class="fas fa-memory"></i> Memory
</button>
<button class="tab-btn" onclick="switchTab('parallel')">
<i class="fas fa-sitemap"></i> Parallel
</button>
<button class="tab-btn" onclick="switchTab('results')">
<i class="fas fa-list"></i> All Results
</button>
</nav>
<!-- Tab Content -->
<div class="tab-content">
<!-- Overview Tab -->
<div id="overviewTab" class="tab-panel active">
<div class="test-results-overview">
<div class="container">
<div class="overview-header">
<h2>
<i class="fas fa-list-ul"></i>
Test Results Overview
</h2>
<div class="overview-controls">
<input type="text" id="testFileSearch" placeholder="Search test files..." onkeyup="filterTestFiles()">
<button class="control-btn" onclick="expandAllFiles()">
<i class="fas fa-expand"></i> Expand All
</button>
<button class="control-btn" onclick="collapseAllFiles()">
<i class="fas fa-compress"></i> Collapse All
</button>
</div>
</div>
<div class="test-files-overview">
<div class="test-files-container"></div>
</div>
</div>
</div>
</div>
<!-- Performance Tab -->
<div id="performanceTab" class="tab-panel">
<div id="performanceContent"></div>
</div>
<!-- Flaky Tab -->
<div id="flakyTab" class="tab-panel">
<div id="flakyContent"></div>
</div>
<!-- Coverage Tab -->
<div id="coverageTab" class="tab-panel">
<div id="coverageContent"></div>
</div>
<!-- Memory Tab -->
<div id="memoryTab" class="tab-panel">
<div id="memoryContent"></div>
</div>
<!-- Parallel Tab -->
<div id="parallelTab" class="tab-panel">
<div id="parallelContent"></div>
</div>
<!-- Results Tab -->
<div id="resultsTab" class="tab-panel">
<div id="resultsContent"></div>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-info">
<p><strong>🥞 Super Pancake Automation Framework v2.8.0</strong></p>
<p>Generated on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}</p>
</div>
<div class="footer-links">
<a href="https://github.com/your-repo/automation-framework" target="_blank">
<i class="fab fa-github"></i> GitHub
</a>
<a href="#" onclick="downloadReport()">
<i class="fas fa-download"></i> Download
</a>
</div>
</div>
</div>
</footer>
</div>
<!-- Embedded Test Data -->
<script>
// Embedded test data - no need to fetch from external file
window.embeddedTestData = ${testDataJson};
console.log('📊 Embedded test data loaded:', window.embeddedTestData.length, 'tests');
</script>
<!-- Embedded JavaScript -->
<script>
// Custom loadTestData function for embedded data
function loadTestData() {
try {
console.log('📊 Loading embedded test data...');
const testData = window.embeddedTestData || [];
// Process and set the data
window.allTests = testData;
window.filteredTests = [...testData];
// Group tests by file for overview
window.testsByFile = groupTestsByFile(testData);
// Set data loaded flag
window.dataLoaded = true;
console.log('✅ Test data loaded successfully:', testData.length, 'tests');
// Initialize the report (will be called later)
return testData;
} catch (error) {
console.error('❌ Failed to load embedded test data:', error);
window.allTests = [];
window.filteredTests = [];
window.testsByFile = {};
window.dataLoaded = true;
// Still initialize report even with no data
if (typeof initializeReport === 'function') {
initializeReport();
}
return [];
}
}
// Add missing helper functions
window.isDataLoaded = function() {
return window.dataLoaded === true;
};
window.getAllTests = function() {
return window.allTests || [];
};
window.setFilteredTests = function(tests) {
window.filteredTests = tests;
};
window.getFilteredTests = function() {
return window.filteredTests || [];
};
window.getCurrentTab = function() {
const activeTab = document.querySelector('.tab-btn.active');
if (activeTab && activeTab.onclick) {
const onclickStr = activeTab.getAttribute('onclick');
const match = onclickStr.match(/switchTab\\s*\\(\\s*['"]([^'"]+)['"]\\s*\\)/);
return match ? match[1] : 'overview';
}
return 'overview';
};
// Initialize the report - render all tab content
window.initializeReport = function() {
console.log('🎯 Initializing report tabs...');
const testData = window.getAllTests();
console.log('📊 Test data in initializeReport:', testData ? testData.length : 'No data', 'tests');
console.log('📊 Functions available:', {
renderTestFiles: typeof renderTestFiles,
renderPerformanceTab: typeof renderPerformanceTab,
renderFlakyTab: typeof renderFlakyTab,
renderMemoryTab: typeof renderMemoryTab,
renderParallelTab: typeof renderParallelTab,
renderResultsTab: typeof renderResultsTab,
groupTestsByFile: typeof groupTestsByFile
});
// Render overview tab
if (typeof renderTestFiles === 'function') {
console.log('🔄 Rendering overview tab...');
renderTestFiles();
} else {
console.error('❌ renderTestFiles function not found');
}
// Render other tabs with data
if (testData && testData.length > 0) {
// Performance tab
if (typeof renderPerformanceTab === 'function') {
renderPerformanceTab(testData);
}
// Flaky tests tab
if (typeof renderFlakyTab === 'function') {
renderFlakyTab(testData);
}
// Memory tab
if (typeof renderMemoryTab === 'function') {
renderMemoryTab(testData);
}
// Parallel tab
if (typeof renderParallelTab === 'function') {
renderParallelTab(testData);
}
// Results tab
if (typeof renderResultsTab === 'function') {
renderResultsTab(testData);
}
// Coverage tab (if coverage data exists)
const hasCoverage = testData.some(test => test.coverageData);
if (hasCoverage && typeof renderCoverageTab === 'function') {
renderCoverageTab(testData);
} else {
const coverageContent = document.getElementById('coverageContent');
if (coverageContent) {
coverageContent.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);">No coverage data available</div>';
}
}
}
console.log('✅ Report tabs initialized');
};
// Include data processing utilities but exclude original loadTestData
${dataProcessingJs.replace(/async function loadTestData\(\)[\s\S]*?^}/gm, '').replace(/\/\/ Export.*loadTestData.*$/gm, '')}
</script>
<script>
${tabNavigationJs}
</script>
<script>
${filterUtilsJs}
</script>
<script>
${chartUtilsJs}
</script>
<script>
${overviewTabJs}
</script>
<script>
${performanceTabJs}
</script>
<script>
${flakyTabJs}
</script>
<script>
${coverageTabJs}
</script>
<script>
${memoryTabJs}
</script>
<script>
${parallelTabJs}
</script>
<script>
// Initialize the report when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 Starting Super Pancake Test Report...');
// Initialize theme first
initializeTheme();
// Add a small delay to ensure all scripts are loaded
setTimeout(() => {
console.log('🔄 Delayed initialization starting...');
loadTestData();
// Initialize report after all scripts are loaded
setTimeout(() => {
if (typeof initializeReport === 'function') {
console.log('🎯 Calling initializeReport...');
initializeReport();
} else {
console.error('❌ initializeReport function not found');
}
}, 100);
}, 200);
});
</script>
</body>
</html>`;
}
export function generateBaseTemplate(summary, results) {
return `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🥞 Super Pancake Test Report</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="./reporter/styles/reportStyles.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
</head>
<body>
<div class="trends-dashboard-content">
<div class="container">
<header class="header">
<div class="logo">
<i class="fas fa-layer-group"></i>
<h1>Super Pancake Test Report</h1>
</div>
<p class="subtitle">Comprehensive Test Analytics & Performance Insights</p>
<div class="header-meta">
<span><i class="fas fa-calendar"></i> ${new Date().toLocaleDateString()}</span>
<span><i class="fas fa-clock"></i> ${new Date().toLocaleTimeString()}</span>
<span><i class="fas fa-code-branch"></i> ${process.env.CI_BRANCH || 'main'}</span>
</div>
<div class="theme-toggle">
<button id="themeToggle" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
<span>Dark Mode</span>
</button>
</div>
</header>
<div class="dashboard">
<div class="summary-grid">
<div class="summary-card total">
<div class="card-icon">
<i class="fas fa-list-check"></i>
</div>
<div class="card-content">
<h3>${summary.totalTests}</h3>
<p>Total Tests</p>
</div>
</div>
<div class="summary-card passed">
<div class="card-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="card-content">
<h3>${summary.passedTests}</h3>
<p>Passed</p>
</div>
</div>
<div class="summary-card failed">
<div class="card-icon">
<i class="fas fa-times-circle"></i>
</div>
<div class="card-content">
<h3>${summary.failedTests}</h3>
<p>Failed</p>
</div>
</div>
<div class="summary-card skipped">
<div class="card-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="card-content">
<h3>${summary.skippedTests}</h3>
<p>Skipped</p>
</div>
</div>
<div class="summary-card success-rate">
<div class="card-icon">
<i class="fas fa-percentage"></i>
</div>
<div class="card-content">
<h3>${summary.successRate}%</h3>
<p>Success Rate</p>
</div>
</div>
<div class="summary-card duration">
<div class="card-icon">
<i class="fas fa-stopwatch"></i>
</div>
<div class="card-content">
<h3>${formatTotalDuration(summary.totalDuration)}</h3>
<p>Total Duration</p>
</div>
</div>
</div>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-segment passed" style="width: ${(summary.passedTests / summary.totalTests) * 100}%"></div>
<div class="progress-segment failed" style="width: ${(summary.failedTests / summary.totalTests) * 100}%"></div>
<div class="progress-segment skipped" style="width: ${(summary.skippedTests / summary.totalTests) * 100}%"></div>
</div>
<div class="progress-labels">
<span class="label passed">${summary.passedTests} Passed</span>
<span class="label failed">${summary.failedTests} Failed</span>
<span class="label skipped">${summary.skippedTests} Skipped</span>
</div>
</div>
</div>
</div>
<div class="analytics-dashboard">
<nav class="tab-navigation">
<button class="tab-btn active" onclick="switchTab('overview')">
<i class="fas fa-chart-pie"></i> Overview
</button>
<button class="tab-btn" onclick="switchTab('performance')">
<i class="fas fa-tachometer-alt"></i> Performance
</button>
<button class="tab-btn" onclick="switchTab('flaky')">
<i class="fas fa-exclamation-triangle"></i> Flaky Tests
</button>
<button class="tab-btn" onclick="switchTab('coverage')">
<i class="fas fa-shield-alt"></i> Coverage
</button>
<button class="tab-btn" onclick="switchTab('memory')">
<i class="fas fa-memory"></i> Memory
</button>
<button class="tab-btn" onclick="switchTab('parallel')">
<i class="fas fa-sitemap"></i> Parallel
</button>
<button class="tab-btn" onclick="switchTab('results')">
<i class="fas fa-list"></i> All Results
</button>
</nav>
<!-- Tab Content -->
<div class="tab-content">
<!-- Overview Tab -->
<div id="overviewTab" class="tab-panel active">
<div class="test-results-overview">
<div class="container">
<div class="overview-header">
<h2>
<i class="fas fa-list-ul"></i>
Test Results Overview
</h2>
<div class="overview-controls">
<input type="text" id="testFileSearch" placeholder="Search test files..." onkeyup="filterTestFiles()">
<button class="control-btn" onclick="expandAllFiles()">
<i class="fas fa-expand"></i> Expand All
</button>
<button class="control-btn" onclick="collapseAllFiles()">
<i class="fas fa-compress"></i> Collapse All
</button>
</div>
</div>
<div class="test-files-overview">
<div class="test-files-container"></div>
</div>
</div>
</div>
</div>
<!-- Performance Tab -->
<div id="performanceTab" class="tab-panel">
<div id="performanceContent"></div>
</div>
<!-- Flaky Tab -->
<div id="flakyTab" class="tab-panel">
<div id="flakyContent"></div>
</div>
<!-- Coverage Tab -->
<div id="coverageTab" class="tab-panel">
<div id="coverageContent"></div>
</div>
<!-- Memory Tab -->
<div id="memoryTab" class="tab-panel">
<div id="memoryContent"></div>
</div>
<!-- Parallel Tab -->
<div id="parallelTab" class="tab-panel">
<div id="parallelContent"></div>
</div>
<!-- Results Tab -->
<div id="resultsTab" class="tab-panel">
<div id="resultsContent"></div>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-info">
<p><strong>🥞 Super Pancake Automation Framework v2.8.0</strong></p>
<p>Generated on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}</p>
</div>
<div class="footer-links">
<a href="https://github.com/your-repo/automation-framework" target="_blank">
<i class="fab fa-github"></i> GitHub
</a>
<a href="#" onclick="downloadReport()">
<i class="fas fa-download"></i> Download
</a>
</div>
</div>
</div>
</footer>
</div>
<!-- Scripts will be loaded here -->
<script src="./reporter/utils/dataProcessing.js"></script>
<script src="./reporter/utils/tabNavigation.js"></script>
<script src="./reporter/utils/filterUtils.js"></script>
<script src="./reporter/charts/chartUtils.js"></script>
<script src="./reporter/templates/overviewTab.js"></script>
<script src="./reporter/templates/performanceTab.js"></script>
<script src="./reporter/templates/flakyTab.js"></script>
<script src="./reporter/templates/coverageTab.js"></script>
<script src="./reporter/templates/memoryTab.js"></script>
<script src="./reporter/templates/parallelTab.js"></script>
<script>
// Initialize the report when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 Starting Super Pancake Test Report...');
initializeTheme();
loadTestData();
});
</script>
</body>
</html>`;
}
export default generateBaseTemplate;