super-pancake-automation
Version:
A lightweight DOM-based UI automation framework using Chrome DevTools Protocol
970 lines (875 loc) • 37.3 kB
JavaScript
/**
* Overview Tab Module
* Handles rendering and functionality for the overview tab
*/
/**
* Render test files in the overview tab
*/
function renderTestFiles() {
const container = document.querySelector('.test-files-container');
if (!container) {
console.error('❌ Test files container not found');
return;
}
const allTests = window.getAllTests();
if (!allTests || allTests.length === 0) {
container.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-secondary);">No test data available</div>';
return;
}
// Group tests by file
const testsByFile = window.groupTestsByFile(allTests);
let html = '';
Object.entries(testsByFile).forEach(([fileName, tests]) => {
const totalTests = tests.length;
const passedTests = tests.filter(t => t.status === 'passed').length;
const failedTests = tests.filter(t => t.status === 'failed').length;
const skippedTests = tests.filter(t => t.status === 'skipped').length;
// Determine file status class
let fileClass = 'file-success';
if (failedTests > 0) {
fileClass = 'file-error';
} else if (skippedTests > 0) {
fileClass = 'file-warning';
}
// Calculate pass rate
const passRate = totalTests > 0 ? Math.round((passedTests / totalTests) * 100) : 0;
let passRateClass = 'file-success';
if (passRate < 70) {
passRateClass = 'file-error';
} else if (passRate < 90) {
passRateClass = 'file-warning';
}
html += `
<div class="test-file-section ${fileClass}">
<div class="test-file-header" onclick="toggleFileSection(this)">
<div class="file-info">
<i class="file-icon fas fa-file-code"></i>
<span class="file-name">${fileName}</span>
</div>
<div class="file-stats">
<span class="test-count">${totalTests} tests</span>
<span class="pass-rate ${passRateClass}">${passRate}% pass</span>
</div>
<i class="file-toggle fas fa-chevron-down"></i>
</div>
<div class="test-file-content" style="display: none;">
<div class="test-cases-list">
${tests.map(test => renderTestCase(test)).join('')}
</div>
</div>
</div>
`;
});
container.innerHTML = html;
console.log('✅ Test files rendered successfully:', Object.keys(testsByFile).length, 'files');
}
/**
* Render individual test case
*/
function renderTestCase(test) {
const statusClass = `test-${test.status}`;
const statusIcon = getStatusIcon(test.status);
const duration = test.duration ? formatDuration(test.duration) : 'N/A';
const testName = test.testName || test.description || 'Unknown Test';
const testId = test.id || `test-${Math.random().toString(36).substr(2, 9)}`;
let errorHtml = '';
let logsHtml = '';
let screenshotsHtml = '';
let actionsHtml = '';
if (test.error && test.status === 'failed') {
const errorMessage = typeof test.error === 'string' ? test.error : JSON.stringify(test.error);
const truncatedError = errorMessage.length > 200 ? errorMessage.substring(0, 200) + '...' : errorMessage;
errorHtml = `
<div class="test-error">
<div class="error-preview">${escapeHtml(truncatedError)}</div>
</div>
`;
actionsHtml = `
<div class="test-actions">
<button class="btn-view-details" onclick="showErrorDetails('${testId}')" title="View full error details">
<i class="fas fa-info-circle"></i> View Details
</button>
${test.screenshots && test.screenshots.length > 0 ? `
<button class="btn-view-screenshot" onclick="showErrorScreenshot('${testId}')" title="View error screenshot">
<i class="fas fa-camera"></i> Screenshot
</button>
` : ''}
</div>
`;
}
// Show console logs for all tests (not just failed ones)
if (test.logs && test.logs.length > 0) {
const logPreview = test.logs.slice(0, 3).join('\n'); // Show first 3 log lines
const truncatedLogs = logPreview.length > 300 ? logPreview.substring(0, 300) + '...' : logPreview;
const hasMore = test.logs.length > 3 || logPreview.length > 300;
logsHtml =
'<div class="test-logs">' +
'<div class="logs-header">' +
'<div class="logs-title">' +
'<i class="fas fa-terminal"></i>' +
'<span>Console Logs (' + test.logs.length + ' entries)</span>' +
'</div>' +
(hasMore ? (
'<button class="btn-view-all-logs" onclick="window.showAllLogs(\'' + testId + '\'); event.preventDefault(); return false;" title="View all console logs for this test">' +
'<i class="fas fa-expand-alt"></i> View All Logs' +
'</button>'
) : '') +
'</div>' +
'<div class="logs-preview">' +
'<pre class="log-text-preview">' + escapeHtml(truncatedLogs) + '</pre>' +
'</div>' +
'</div>';
}
// Show screenshots if available
if (test.screenshots && test.screenshots.length > 0) {
const screenshotPreview = test.screenshots.slice(0, 2); // Show first 2 screenshots
const hasMoreScreenshots = test.screenshots.length > 2;
let screenshotThumbnails = '';
screenshotPreview.forEach((screenshot, index) => {
const screenshotPath = screenshot.path || screenshot.url || screenshot;
screenshotThumbnails += `
<div class="screenshot-thumbnail" onclick="window.showScreenshot('${testId}', ${index})" title="Click to view full screenshot">
<img src="${screenshotPath}" alt="Screenshot ${index + 1}" onerror="this.style.display='none'; this.nextSibling.style.display='block';">
<div class="screenshot-placeholder" style="display: none;">
<i class="fas fa-image"></i>
<span>Screenshot ${index + 1}</span>
</div>
</div>
`;
});
screenshotsHtml = `
<div class="test-screenshots">
<div class="screenshots-header">
<div class="screenshots-title">
<i class="fas fa-camera"></i>
<span>Screenshots (${test.screenshots.length} captured)</span>
</div>
${hasMoreScreenshots ? `
<button class="btn-view-all-screenshots" onclick="window.showAllScreenshots('${testId}'); event.preventDefault(); return false;" title="View all screenshots">
<i class="fas fa-images"></i> View All (${test.screenshots.length})
</button>
` : ''}
</div>
<div class="screenshots-preview">
${screenshotThumbnails}
</div>
</div>
`;
}
// Store test data for popup access
if (typeof window.testDetailsData === 'undefined') {
window.testDetailsData = {};
}
window.testDetailsData[testId] = test;
// Add actions to view screenshots if not already present (logs handled in preview section)
if (!actionsHtml) {
let actionButtons = '';
if (test.screenshots && test.screenshots.length > 0) {
actionButtons += `
<button class="btn-view-screenshots" onclick="window.showAllScreenshots('${testId}')" title="View all screenshots">
<i class="fas fa-images"></i> View Screenshots
</button>
`;
}
if (actionButtons) {
actionsHtml = `
<div class="test-actions">
${actionButtons}
</div>
`;
}
}
return `
<div class="test-case-item ${statusClass}" data-test-id="${testId}">
<div class="test-case-info">
<i class="test-status-icon ${statusIcon}"></i>
<span class="test-name">${escapeHtml(testName)}</span>
<span class="test-duration">${duration}</span>
</div>
${errorHtml}
${logsHtml}
${screenshotsHtml}
${actionsHtml}
</div>
`;
}
/**
* Get appropriate icon for test status
*/
function getStatusIcon(status) {
switch (status) {
case 'passed':
return 'fas fa-check-circle';
case 'failed':
return 'fas fa-times-circle';
case 'skipped':
return 'fas fa-exclamation-triangle';
default:
return 'fas fa-question-circle';
}
}
/**
* Escape HTML to prevent XSS
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Toggle file section expand/collapse
*/
function toggleFileSection(header) {
const testContent = header.nextElementSibling;
const chevron = header.querySelector('.file-toggle');
if (testContent.style.display === 'none') {
testContent.style.display = 'block';
chevron.style.transform = 'rotate(180deg)';
chevron.classList.add('expanded');
} else {
testContent.style.display = 'none';
chevron.style.transform = 'rotate(0deg)';
chevron.classList.remove('expanded');
}
}
/**
* Filter test files based on search input
*/
function filterTestFiles() {
const searchInput = document.getElementById('testFileSearch');
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
const fileSections = document.querySelectorAll('.test-file-section');
fileSections.forEach(section => {
const fileName = section.querySelector('.file-name');
const testNames = section.querySelectorAll('.test-name');
let shouldShow = false;
// Check file name
if (fileName && fileName.textContent.toLowerCase().includes(searchTerm)) {
shouldShow = true;
}
// Check test names
testNames.forEach(testName => {
if (testName.textContent.toLowerCase().includes(searchTerm)) {
shouldShow = true;
}
});
section.style.display = shouldShow ? 'block' : 'none';
});
}
/**
* Expand all file sections
*/
function expandAllFiles() {
const headers = document.querySelectorAll('.test-file-header');
headers.forEach(header => {
const testContent = header.nextElementSibling;
const chevron = header.querySelector('.file-toggle');
testContent.style.display = 'block';
chevron.style.transform = 'rotate(180deg)';
chevron.classList.add('expanded');
});
}
/**
* Collapse all file sections
*/
function collapseAllFiles() {
const headers = document.querySelectorAll('.test-file-header');
headers.forEach(header => {
const testContent = header.nextElementSibling;
const chevron = header.querySelector('.file-toggle');
testContent.style.display = 'none';
chevron.style.transform = 'rotate(0deg)';
chevron.classList.remove('expanded');
});
}
function formatDuration(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 >= 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';
}
/**
* Show detailed error information in a modal
*/
function showErrorDetails(testId) {
const test = window.testDetailsData[testId];
if (!test || !test.error) {
alert('No error details available');
return;
}
const errorMessage = typeof test.error === 'string' ? test.error : JSON.stringify(test.error, null, 2);
const testName = test.testName || test.description || 'Unknown Test';
// Create modal HTML
const modalHtml = `
<div class="error-modal-overlay" onclick="closeErrorModal()">
<div class="error-modal" onclick="event.stopPropagation()">
<div class="error-modal-header">
<h3><i class="fas fa-exclamation-triangle"></i> Test Error Details</h3>
<button class="close-btn" onclick="closeErrorModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="error-modal-content">
<div class="test-info">
<h4>Test: ${escapeHtml(testName)}</h4>
<p class="test-meta">
<span><i class="fas fa-clock"></i> Duration: ${formatDuration(test.duration)}</span>
<span><i class="fas fa-calendar"></i> ${new Date(test.timestamp).toLocaleString()}</span>
</p>
</div>
<div class="error-details">
<h4>Error Message:</h4>
<pre class="error-text">${escapeHtml(errorMessage)}</pre>
</div>
${test.screenshots && test.screenshots.length > 0 ? `
<div class="error-screenshots">
<h4>Screenshots:</h4>
<div class="screenshot-grid">
${test.screenshots.map(screenshot => `
<img src="${screenshot}" alt="Error Screenshot" class="error-screenshot-thumb"
onclick="showFullScreenshot('${screenshot}')" title="Click to view full size">
`).join('')}
</div>
</div>
` : ''}
${test.logs && test.logs.length > 0 ? `
<div class="error-logs">
<h4>Console Logs (${test.logs.length} entries):</h4>
<div class="logs-preview-modal">
<pre class="log-text">${escapeHtml(test.logs.slice(0, 10).join('\\n'))}${test.logs.length > 10 ? '\\n\\n... and ' + (test.logs.length - 10) + ' more entries' : ''}</pre>
</div>
${test.logs.length > 10 ? `
<button class="btn-view-all-logs-modal" onclick="showAllLogsInModal('${testId}')" style="margin-top: 8px;">
<i class="fas fa-terminal"></i> View All ${test.logs.length} Console Logs
</button>
` : ''}
</div>
` : ''}
</div>
<div class="error-modal-footer">
<button class="btn-copy-error" onclick="copyErrorToClipboard('${testId}')">
<i class="fas fa-copy"></i> Copy Error
</button>
<button class="btn-close" onclick="closeErrorModal()">Close</button>
</div>
</div>
</div>
`;
// Add modal to page
const existingModal = document.querySelector('.error-modal-overlay');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
/**
* Show error screenshot in a modal
*/
function showErrorScreenshot(testId) {
const test = window.testDetailsData[testId];
if (!test || !test.screenshots || test.screenshots.length === 0) {
alert('No screenshots available');
return;
}
const screenshot = test.screenshots[0]; // Show first screenshot
showFullScreenshot(screenshot);
}
/**
* Show full-size screenshot
*/
function showFullScreenshot(screenshotUrl) {
const modalHtml = `
<div class="screenshot-modal-overlay" onclick="closeScreenshotModal()">
<div class="screenshot-modal" onclick="event.stopPropagation()">
<div class="screenshot-modal-header">
<h3><i class="fas fa-camera"></i> Screenshot</h3>
<button class="close-btn" onclick="closeScreenshotModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="screenshot-modal-content">
<img src="${screenshotUrl}" alt="Test Screenshot" class="full-screenshot">
</div>
</div>
</div>
`;
// Add modal to page
const existingModal = document.querySelector('.screenshot-modal-overlay');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden';
}
/**
* Close error details modal
*/
function closeErrorModal() {
const modal = document.querySelector('.error-modal-overlay');
if (modal) {
modal.remove();
document.body.style.overflow = 'auto';
}
}
/**
* Close screenshot modal
*/
function closeScreenshotModal() {
const modal = document.querySelector('.screenshot-modal-overlay');
if (modal) {
modal.remove();
document.body.style.overflow = 'auto';
}
}
/**
* Copy error details to clipboard
*/
function copyErrorToClipboard(testId) {
const test = window.testDetailsData[testId];
if (!test || !test.error) {
return;
}
const errorText = `Test: ${test.testName || test.description}
Duration: ${formatDuration(test.duration)}
Timestamp: ${new Date(test.timestamp).toLocaleString()}
Error:
${typeof test.error === 'string' ? test.error : JSON.stringify(test.error, null, 2)}`;
navigator.clipboard.writeText(errorText).then(() => {
// Show temporary success message
const btn = document.querySelector('.btn-copy-error');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
btn.style.background = 'var(--success-color)';
setTimeout(() => {
btn.innerHTML = originalText;
btn.style.background = '';
}, 2000);
}).catch(() => {
alert('Failed to copy to clipboard');
});
}
/**
* Show all console logs in a modal
*/
function showAllLogs(testId) {
console.log('showAllLogs called with testId:', testId);
const test = window.testDetailsData && window.testDetailsData[testId];
if (!test || !test.logs || test.logs.length === 0) {
alert('No console logs available for this test');
return;
}
const testName = test.testName || test.description || 'Unknown Test';
const allLogs = test.logs.join('\n');
// Try modal first, fallback to simple popup
try {
// Create modal HTML
const modalHtml = `
<div class="logs-modal-overlay" onclick="window.closeLogsModal()">
<div class="logs-modal" onclick="event.stopPropagation()">
<div class="logs-modal-header">
<h3><i class="fas fa-terminal"></i> Console Logs</h3>
<button class="close-btn" onclick="window.closeLogsModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="logs-modal-content">
<div class="test-info">
<h4>Test: ${escapeHtml(testName)}</h4>
<p class="test-meta">
<span><i class="fas fa-clock"></i> Duration: ${formatDuration(test.duration)}</span>
<span><i class="fas fa-calendar"></i> ${new Date(test.timestamp).toLocaleString()}</span>
<span><i class="fas fa-list"></i> ${test.logs.length} log entries</span>
</p>
</div>
<div class="logs-content">
<h4>Complete Console Output:</h4>
<pre class="full-log-text">${escapeHtml(allLogs)}</pre>
</div>
</div>
<div class="logs-modal-footer">
<button class="btn-copy-logs" onclick="window.copyLogsToClipboard('${testId}')">
<i class="fas fa-copy"></i> Copy Logs
</button>
<button class="btn-close" onclick="window.closeLogsModal()">Close</button>
</div>
</div>
</div>
`;
// Remove existing modal
const existingModal = document.querySelector('.logs-modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Add modal to page
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden';
} catch (error) {
console.error('Modal creation failed:', error);
// Fallback to simple popup
showSimpleLogsPopup(testName, allLogs);
}
}
/**
* Simple logs popup fallback
*/
function showSimpleLogsPopup(testName, logs) {
const popup = window.open('', 'ConsoleLogsPopup', 'width=800,height=600,scrollbars=yes,resizable=yes');
if (popup) {
popup.document.write(`
<html>
<head>
<title>Console Logs - ${testName}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #f5f5f5;
}
.header {
background: #6366f1;
color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.logs {
background: white;
padding: 20px;
border-radius: 8px;
border: 1px solid #ddd;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 500px;
overflow-y: auto;
}
.close-btn {
margin-top: 15px;
padding: 10px 20px;
background: #6366f1;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="header">
<h2>🚀 Console Logs: ${testName}</h2>
<p>Total log entries: ${logs.split('\\n').length}</p>
</div>
<div class="logs">${logs}</div>
<button class="close-btn" onclick="window.close()">Close Window</button>
</body>
</html>
`);
popup.document.close();
} else {
// Final fallback - just alert with some of the logs
const truncatedLogs = logs.length > 1000 ? logs.substring(0, 1000) + '...\n\n[Logs truncated - open browser console for full output]' : logs;
alert(`Console Logs for: ${testName}\n\n${truncatedLogs}`);
console.log('Full console logs for', testName, ':\n', logs);
}
}
/**
* Close logs modal
*/
function closeLogsModal() {
const modal = document.querySelector('.logs-modal-overlay');
if (modal) {
modal.remove();
document.body.style.overflow = 'auto';
}
}
/**
* Copy all logs to clipboard
*/
function copyLogsToClipboard(testId) {
const test = window.testDetailsData[testId];
if (!test || !test.logs) {
return;
}
const logsText = `Test: ${test.testName || test.description}
Duration: ${formatDuration(test.duration)}
Timestamp: ${new Date(test.timestamp).toLocaleString()}
Log Entries: ${test.logs.length}
Console Logs:
${test.logs.join('\n')}`;
navigator.clipboard.writeText(logsText).then(() => {
// Show temporary success message
const btn = document.querySelector('.btn-copy-logs');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
btn.style.background = 'var(--success-color)';
setTimeout(() => {
btn.innerHTML = originalText;
btn.style.background = '';
}, 2000);
}).catch(() => {
alert('Failed to copy to clipboard');
});
}
/**
* Show all console logs from within error modal
*/
function showAllLogsInModal(testId) {
const test = window.testDetailsData[testId];
if (!test || !test.logs || test.logs.length === 0) {
alert('No console logs available');
return;
}
const testName = test.testName || test.description || 'Unknown Test';
const allLogs = test.logs.join('\n');
// Close existing error modal first
closeErrorModal();
// Create full logs modal HTML
const modalHtml = `
<div class="logs-modal-overlay" onclick="closeLogsModal()">
<div class="logs-modal" onclick="event.stopPropagation()">
<div class="logs-modal-header">
<h3><i class="fas fa-terminal"></i> Console Logs</h3>
<button class="close-btn" onclick="closeLogsModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="logs-modal-content">
<div class="test-info">
<h4>Test: ${escapeHtml(testName)}</h4>
<p class="test-meta">
<span><i class="fas fa-clock"></i> Duration: ${formatDuration(test.duration)}</span>
<span><i class="fas fa-calendar"></i> ${new Date(test.timestamp).toLocaleString()}</span>
<span><i class="fas fa-list"></i> ${test.logs.length} log entries</span>
</p>
</div>
<div class="logs-content">
<h4>Complete Console Output:</h4>
<pre class="full-log-text">${escapeHtml(allLogs)}</pre>
</div>
</div>
<div class="logs-modal-footer">
<button class="btn-copy-logs" onclick="copyLogsToClipboard('${testId}')">
<i class="fas fa-copy"></i> Copy Logs
</button>
<button class="btn-close" onclick="closeLogsModal()">Close</button>
</div>
</div>
</div>
`;
// Add modal to page
const existingModal = document.querySelector('.logs-modal-overlay');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
// Export functions globally
window.renderTestFiles = renderTestFiles;
window.renderTestCase = renderTestCase;
window.toggleFileSection = toggleFileSection;
window.filterTestFiles = filterTestFiles;
window.expandAllFiles = expandAllFiles;
window.collapseAllFiles = collapseAllFiles;
window.showErrorDetails = showErrorDetails;
window.showErrorScreenshot = showErrorScreenshot;
window.showFullScreenshot = showFullScreenshot;
window.closeErrorModal = closeErrorModal;
window.closeScreenshotModal = closeScreenshotModal;
window.copyErrorToClipboard = copyErrorToClipboard;
window.showAllLogs = showAllLogs;
window.closeLogsModal = closeLogsModal;
window.copyLogsToClipboard = copyLogsToClipboard;
window.showAllLogsInModal = showAllLogsInModal;
window.showSimpleLogsPopup = showSimpleLogsPopup;
/**
* Show individual screenshot in modal
*/
function showScreenshot(testId, screenshotIndex) {
const test = window.testDetailsData && window.testDetailsData[testId];
if (!test || !test.screenshots || !test.screenshots[screenshotIndex]) {
alert('Screenshot not available');
return;
}
const screenshot = test.screenshots[screenshotIndex];
const screenshotPath = screenshot.path || screenshot.url || screenshot;
const testName = test.testName || test.description || 'Unknown Test';
const modalHtml = `
<div class="screenshot-modal-overlay" onclick="window.closeScreenshotModal()">
<div class="screenshot-modal" onclick="event.stopPropagation()">
<div class="screenshot-modal-header">
<h3><i class="fas fa-image"></i> Screenshot ${screenshotIndex + 1}</h3>
<button class="close-btn" onclick="window.closeScreenshotModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="screenshot-modal-content">
<div class="test-info">
<h4>Test: ${escapeHtml(testName)}</h4>
<p class="test-meta">
<span><i class="fas fa-clock"></i> Duration: ${formatDuration(test.duration)}</span>
<span><i class="fas fa-calendar"></i> ${new Date(test.timestamp).toLocaleString()}</span>
</p>
</div>
<div class="screenshot-container">
<img src="${screenshotPath}" alt="Screenshot ${screenshotIndex + 1}" class="full-screenshot"
onerror="this.style.display='none'; this.nextSibling.style.display='flex';">
<div class="screenshot-error" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<span>Unable to load screenshot</span>
</div>
</div>
</div>
<div class="screenshot-modal-footer">
<button class="btn-download-screenshot" onclick="window.downloadScreenshot('${screenshotPath}')">
<i class="fas fa-download"></i> Download
</button>
<button class="btn-close" onclick="window.closeScreenshotModal()">Close</button>
</div>
</div>
</div>
`;
// Remove existing modal
const existingModal = document.querySelector('.screenshot-modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Add modal to page
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden';
}
/**
* Show all screenshots for a test
*/
function showAllScreenshots(testId) {
const test = window.testDetailsData && window.testDetailsData[testId];
if (!test || !test.screenshots || test.screenshots.length === 0) {
alert('No screenshots available for this test');
return;
}
const testName = test.testName || test.description || 'Unknown Test';
let screenshotsGrid = '';
test.screenshots.forEach((screenshot, index) => {
const screenshotPath = screenshot.path || screenshot.url || screenshot;
screenshotsGrid += `
<div class="screenshot-grid-item" onclick="window.showScreenshot('${testId}', ${index})">
<img src="${screenshotPath}" alt="Screenshot ${index + 1}"
onerror="this.style.display='none'; this.nextSibling.style.display='flex';">
<div class="screenshot-placeholder" style="display: none;">
<i class="fas fa-image"></i>
<span>Screenshot ${index + 1}</span>
</div>
<div class="screenshot-overlay">
<i class="fas fa-search-plus"></i>
</div>
</div>
`;
});
const modalHtml = `
<div class="screenshots-modal-overlay" onclick="window.closeScreenshotsModal()">
<div class="screenshots-modal" onclick="event.stopPropagation()">
<div class="screenshots-modal-header">
<h3><i class="fas fa-images"></i> All Screenshots (${test.screenshots.length})</h3>
<button class="close-btn" onclick="window.closeScreenshotsModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="screenshots-modal-content">
<div class="test-info">
<h4>Test: ${escapeHtml(testName)}</h4>
<p class="test-meta">
<span><i class="fas fa-clock"></i> Duration: ${formatDuration(test.duration)}</span>
<span><i class="fas fa-calendar"></i> ${new Date(test.timestamp).toLocaleString()}</span>
<span><i class="fas fa-camera"></i> ${test.screenshots.length} screenshots</span>
</p>
</div>
<div class="screenshots-grid">
${screenshotsGrid}
</div>
</div>
<div class="screenshots-modal-footer">
<button class="btn-download-all" onclick="window.downloadAllScreenshots('${testId}')">
<i class="fas fa-download"></i> Download All
</button>
<button class="btn-close" onclick="window.closeScreenshotsModal()">Close</button>
</div>
</div>
</div>
`;
// Remove existing modal
const existingModal = document.querySelector('.screenshots-modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Add modal to page
document.body.insertAdjacentHTML('beforeend', modalHtml);
document.body.style.overflow = 'hidden';
}
/**
* Close screenshot modal
*/
function closeScreenshotModal() {
const modal = document.querySelector('.screenshot-modal-overlay');
if (modal) {
modal.remove();
document.body.style.overflow = '';
}
}
/**
* Close screenshots modal
*/
function closeScreenshotsModal() {
const modal = document.querySelector('.screenshots-modal-overlay');
if (modal) {
modal.remove();
document.body.style.overflow = '';
}
}
/**
* Download individual screenshot
*/
function downloadScreenshot(screenshotPath) {
const link = document.createElement('a');
link.href = screenshotPath;
link.download = screenshotPath.split('/').pop() || 'screenshot.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* Download all screenshots for a test
*/
function downloadAllScreenshots(testId) {
const test = window.testDetailsData && window.testDetailsData[testId];
if (!test || !test.screenshots || test.screenshots.length === 0) {
alert('No screenshots to download');
return;
}
test.screenshots.forEach((screenshot, index) => {
const screenshotPath = screenshot.path || screenshot.url || screenshot;
setTimeout(() => {
downloadScreenshot(screenshotPath);
}, index * 100); // Stagger downloads
});
}
// Export screenshot functions globally
window.showScreenshot = showScreenshot;
window.showAllScreenshots = showAllScreenshots;
window.closeScreenshotModal = closeScreenshotModal;
window.closeScreenshotsModal = closeScreenshotsModal;
window.downloadScreenshot = downloadScreenshot;
window.downloadAllScreenshots = downloadAllScreenshots;