UNPKG

super-pancake-automation

Version:

A lightweight DOM-based UI automation framework using Chrome DevTools Protocol

2,435 lines 89.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🥞 Super Pancake Test Runner</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        :root {
            /* Light Theme */
            --primary-color: #7c3aed;
            --primary-light: #8b5cf6;
            --primary-dark: #6d28d9;
            --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            --success-color: #10b981;
            --success-light: #34d399;
            --warning-color: #f59e0b;
            --warning-light: #fbbf24;
            --danger-color: #ef4444;
            --danger-light: #f87171;
            --info-color: #3b82f6;
            --info-light: #60a5fa;
            
            /* Text Colors */
            --text-primary: #1f2937;
            --text-secondary: #6b7280;
            --text-muted: #9ca3af;
            --text-inverse: #ffffff;
            
            /* Background Colors */
            --bg-primary: #f8fafc;
            --bg-secondary: #ffffff;
            --bg-tertiary: #f1f5f9;
            --bg-dark: #0f172a;
            --bg-sidebar: #ffffff;
            --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            --bg-glass: rgba(255, 255, 255, 0.95);
            
            /* Border & Effects */
            --border-color: #e2e8f0;
            --border-light: #f1f5f9;
            --border-radius: 12px;
            --border-radius-lg: 20px;
            
            /* Shadows */
            --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
            --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
            --shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
            --glow: 0 0 20px rgba(139, 92, 246, 0.3);
            
            /* Animations */
            --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            --transition-slow: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        [data-theme="dark"] {
            --text-primary: #f8fafc;
            --text-secondary: #cbd5e1;
            --text-muted: #64748b;
            --bg-primary: #0f172a;
            --bg-secondary: #1e293b;
            --bg-tertiary: #334155;
            --bg-sidebar: #1e293b;
            --bg-glass: rgba(30, 41, 59, 0.95);
            --border-color: #334155;
            --border-light: #475569;
        }

        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: var(--bg-gradient);
            color: var(--text-primary);
            line-height: 1.6;
            min-height: 100vh;
            transition: var(--transition);
            overflow-x: hidden;
        }

        .app-container {
            display: flex;
            height: 100vh;
            gap: 24px;
            padding: 24px;
            max-width: 1800px;
            margin: 0 auto;
            position: relative;
        }

        .app-container::before {
            content: '';
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: radial-gradient(circle at 25% 25%, rgba(139, 92, 246, 0.1) 0%, transparent 50%),
                        radial-gradient(circle at 75% 75%, rgba(59, 130, 246, 0.1) 0%, transparent 50%);
            pointer-events: none;
            z-index: -1;
        }

        .card {
            background: var(--bg-glass);
            backdrop-filter: blur(20px);
            border: 1px solid var(--border-color);
            border-radius: var(--border-radius-lg);
            box-shadow: var(--shadow-xl);
            overflow: hidden;
            transition: var(--transition-slow);
            position: relative;
        }

        .card::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 1px;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent);
            z-index: 1;
        }

        .card:hover {
            transform: translateY(-2px);
            box-shadow: var(--shadow-xl), var(--glow);
        }

        /* Sidebar Styles */
        .sidebar {
            width: 20%;
            min-width: 320px;
            max-width: 50%;
            display: flex;
            flex-direction: column;
            height: 100%;
            position: relative;
            transition: var(--transition-slow);
            overflow: hidden;
        }

        .sidebar-resizer {
            position: absolute;
            top: 0;
            right: -12px;
            width: 24px;
            height: 100%;
            cursor: col-resize;
            z-index: 5;
            background: transparent;
            transition: var(--transition);
            pointer-events: auto;
        }

        .sidebar-resizer:hover {
            background: linear-gradient(90deg, transparent, rgba(124, 58, 237, 0.3), transparent);
        }

        .sidebar-resizer::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 3px;
            height: 40px;
            background: var(--border-color);
            border-radius: 2px;
            opacity: 0;
            transition: var(--transition);
        }

        .sidebar-resizer:hover::after {
            opacity: 1;
        }

        .app-container.resizing {
            user-select: none;
        }

        .app-container.resizing .sidebar-resizer {
            background: linear-gradient(90deg, transparent, rgba(124, 58, 237, 0.5), transparent);
        }

        .app-container.resizing .sidebar-resizer::after {
            opacity: 1;
        }

        .sidebar-header {
            background: var(--primary-gradient);
            color: var(--text-inverse);
            padding: 20px 16px;
            position: relative;
            overflow: hidden;
            z-index: 2;
        }

        .sidebar-header::before {
            content: '';
            position: absolute;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 70%);
            animation: float 8s ease-in-out infinite;
        }

        .sidebar-header::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 1px;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
        }

        @keyframes float {
            0%, 100% { transform: translateY(0px) rotate(0deg); }
            50% { transform: translateY(-12px) rotate(2deg); }
        }

        @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.05); }
        }

        @keyframes slideInDown {
            0% { opacity: 0; transform: translateY(-20px); }
            100% { opacity: 1; transform: translateY(0); }
        }

        @keyframes slideInUp {
            0% { opacity: 0; transform: translateY(20px); }
            100% { opacity: 1; transform: translateY(0); }
        }

        @keyframes fadeIn {
            0% { opacity: 0; }
            100% { opacity: 1; }
        }

        .header-content {
            position: relative;
            z-index: 1;
            animation: slideInDown 0.6s ease-out;
        }

        .header-top {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 16px;
            flex-wrap: nowrap;
        }

        .icon {
            font-size: 2.2rem;
            text-shadow: 0 4px 8px rgba(0,0,0,0.3);
            animation: pulse 4s ease-in-out infinite;
            filter: drop-shadow(0 0 10px rgba(255,255,255,0.5));
            flex-shrink: 0;
        }

        /* Pancake running animation */
        .icon.running {
            animation: pancakeFlip 1.5s ease-in-out infinite;
        }

        @keyframes pancakeFlip {
            0%, 100% { transform: rotateY(0deg) scale(1); }
            25% { transform: rotateY(90deg) scale(1.1); }
            50% { transform: rotateY(180deg) scale(1.2); }
            75% { transform: rotateY(270deg) scale(1.1); }
        }

        .sidebar-header h1 {
            font-size: 1.1rem;
            font-weight: 800;
            text-shadow: 0 2px 4px rgba(0,0,0,0.2);
            letter-spacing: -0.025em;
            background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
            background-clip: text;
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            min-width: 0;
            flex: 1;
        }
        
        /* Responsive title sizing */
        @media (max-width: 400px) {
            .sidebar-header h1 {
                font-size: 0.95rem;
            }
            
            .icon {
                font-size: 1.8rem;
            }
        }
        
        /* Sidebar at minimum width */
        .sidebar[style*="width: 15%"] h1,
        .sidebar[style*="width: 16%"] h1,
        .sidebar[style*="width: 17%"] h1,
        .sidebar[style*="width: 18%"] h1,
        .sidebar[style*="width: 19%"] h1,
        .sidebar[style*="width: 20%"] h1 {
            font-size: 0.95rem;
        }
        
        .sidebar[style*="width: 15%"] .icon,
        .sidebar[style*="width: 16%"] .icon,
        .sidebar[style*="width: 17%"] .icon,
        .sidebar[style*="width: 18%"] .icon,
        .sidebar[style*="width: 19%"] .icon,
        .sidebar[style*="width: 20%"] .icon {
            font-size: 1.8rem;
        }

        .header-stats {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 4px;
            animation: slideInUp 0.6s ease-out 0.2s both;
        }

        .header-stat-item {
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 6px 4px;
            border-radius: var(--border-radius);
            background: rgba(255, 255, 255, 0.2);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.3);
            transition: var(--transition);
            position: relative;
            overflow: hidden;
            min-width: 0;
        }

        .header-stat-item::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
            transition: left 0.5s ease;
        }

        .header-stat-item:hover::before {
            left: 100%;
        }

        .header-stat-item:hover {
            transform: translateY(-2px);
            background: rgba(255, 255, 255, 0.25);
            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
        }

        .header-stat-number {
            font-size: 0.9rem;
            font-weight: 700;
            line-height: 1;
            margin-bottom: 4px;
            transition: var(--transition);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .header-stat-label {
            font-size: 0.6rem;
            opacity: 0.95;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .header-stat-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--danger-color);
            margin-bottom: 8px;
            box-shadow: 0 0 0 3px rgba(255,255,255,0.4), 0 0 20px rgba(239, 68, 68, 0.5);
            animation: pulse 2s ease-in-out infinite;
        }

        .status.connected .header-stat-dot {
            background: var(--success-color);
            box-shadow: 0 0 0 3px rgba(255,255,255,0.4), 0 0 20px rgba(16, 185, 129, 0.6);
        }

        .controls {
            padding: 12px 20px;
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            display: flex;
            gap: 12px;
            animation: slideInUp 0.6s ease-out 0.4s both;
            justify-content: center;
            align-items: center;
            flex-shrink: 0;
        }

        .btn {
            padding: 10px 14px;
            border: none;
            border-radius: var(--border-radius);
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            font-size: 12px;
            text-decoration: none;
            font-family: 'Inter', sans-serif;
            position: relative;
            overflow: hidden;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            white-space: nowrap;
            min-width: 0;
        }

        .btn i {
            flex-shrink: 0;
        }

        .btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
            transition: left 0.4s ease;
        }

        .btn:hover::before {
            left: 100%;
        }

        .btn-primary {
            background: var(--primary-gradient);
            color: var(--text-inverse);
            min-width: 100px;
            box-shadow: var(--shadow-lg), 0 0 20px rgba(124, 58, 237, 0.3);
            border: 1px solid rgba(255, 255, 255, 0.2);
        }

        .btn-primary:hover:not(:disabled) {
            transform: translateY(-2px) scale(1.02);
            box-shadow: var(--shadow-xl), 0 0 30px rgba(124, 58, 237, 0.4);
        }

        .btn-primary:active:not(:disabled) {
            transform: translateY(0) scale(1);
        }

        .btn-primary:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none;
            box-shadow: var(--shadow);
        }

        .btn-primary.loading {
            background: linear-gradient(45deg, var(--primary-color), var(--primary-light), var(--primary-color));
            background-size: 200% 200%;
            animation: shimmer 1.5s ease-in-out infinite;
        }

        @keyframes shimmer {
            0% { background-position: -200% 0; }
            100% { background-position: 200% 0; }
        }

        .btn-secondary {
            background: rgba(255, 255, 255, 0.1);
            color: var(--text-primary);
            border: 1px solid var(--border-color);
            backdrop-filter: blur(10px);
        }

        .btn-secondary:hover {
            background: var(--primary-gradient);
            color: var(--text-inverse);
            border-color: transparent;
            transform: translateY(-1px);
            box-shadow: var(--shadow-lg);
        }

        .btn i {
            font-size: 16px;
            transition: var(--transition);
        }

        .btn:hover i {
            transform: scale(1.1);
        }

        .btn-icon {
            min-width: 60px;
            width: 60px;
            height: 50px;
            padding: 6px 4px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            border-radius: var(--border-radius);
            gap: 4px;
        }

        .btn-icon i {
            font-size: 16px;
            margin: 0;
            flex-shrink: 0;
        }

        .btn-text-small {
            font-size: 9px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            line-height: 1;
            white-space: nowrap;
            opacity: 0.9;
        }

        .btn-icon:hover {
            transform: translateY(-2px) scale(1.05);
        }

        .btn-icon:hover .btn-text-small {
            opacity: 1;
        }

        /* Search and Filter Section */
        .search-section {
            padding: 16px 20px;
            background: var(--bg-secondary);
            border-bottom: 1px solid var(--border-color);
            animation: slideInUp 0.6s ease-out 0.6s both;
            flex-shrink: 0;
        }

        .test-files-wrapper {
            flex: 1;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            min-height: 0;
        }

        .test-files {
            overflow-y: auto;
            flex: 1;
            min-height: 0;
            max-height: 100%;
            padding: 0; /* Remove any default padding */
        }

        .search-container {
            position: relative;
            margin-bottom: 16px;
        }

        .search-input {
            width: 100%;
            padding: 12px 16px 12px 44px;
            border: 2px solid var(--border-color);
            border-radius: var(--border-radius);
            font-size: 14px;
            font-family: 'Inter', sans-serif;
            background: var(--bg-primary);
            color: var(--text-primary);
            transition: var(--transition);
            outline: none;
        }

        .search-input:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
            background: var(--bg-secondary);
        }

        .search-icon {
            position: absolute;
            left: 16px;
            top: 50%;
            transform: translateY(-50%);
            color: var(--text-muted);
            font-size: 14px;
            transition: var(--transition);
        }

        .search-input:focus + .search-icon {
            color: var(--primary-color);
        }

        .search-clear {
            position: absolute;
            right: 12px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            color: var(--text-muted);
            cursor: pointer;
            padding: 4px;
            border-radius: 50%;
            transition: var(--transition);
            opacity: 0;
            visibility: hidden;
        }

        .search-input:not(:placeholder-shown) + .search-icon + .search-clear {
            opacity: 1;
            visibility: visible;
        }

        .search-clear:hover {
            background: var(--danger-color);
            color: white;
        }

        .filter-tabs {
            display: flex;
            gap: 8px;
        }

        .filter-tab {
            padding: 8px 16px;
            background: var(--bg-tertiary);
            border: 1px solid var(--border-color);
            border-radius: 20px;
            font-size: 12px;
            font-weight: 600;
            color: var(--text-secondary);
            cursor: pointer;
            transition: var(--transition);
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .filter-tab:hover {
            background: var(--primary-color);
            color: white;
            border-color: var(--primary-color);
            transform: translateY(-1px);
        }

        .filter-tab.active {
            background: var(--primary-gradient);
            color: white;
            border-color: transparent;
            box-shadow: var(--shadow);
        }

        .select-all-button {
            padding: 8px 16px;
            background: var(--success-color);
            border: 1px solid var(--success-color);
            border-radius: 20px;
            font-size: 12px;
            font-weight: 600;
            color: white;
            cursor: pointer;
            transition: var(--transition);
            text-transform: uppercase;
            letter-spacing: 0.5px;
            display: flex;
            align-items: center;
            gap: 6px;
            margin-right: 8px;
        }

        .select-all-button:hover {
            background: var(--success-light);
            border-color: var(--success-light);
            transform: translateY(-1px);
            box-shadow: var(--shadow);
        }

        .select-all-button.deselect-mode {
            background: var(--warning-color);
            border-color: var(--warning-color);
        }

        .select-all-button.deselect-mode:hover {
            background: var(--warning-light);
            border-color: var(--warning-light);
        }

        .select-all-button i {
            font-size: 10px;
        }

        .test-files {
            flex: 1;
            overflow-y: auto;
            background: var(--bg-secondary);
            position: relative;
            max-height: 100%;
        }

        #test-form {
            height: 100%;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            margin: 0;
            padding: 0;
        }

        #test-files-container {
            flex: 1;
            overflow-y: auto;
            min-height: 0;
            max-height: 100%;
            padding: 0;
            margin: 0;
        }

        .test-files::-webkit-scrollbar {
            width: 8px;
        }

        .test-files::-webkit-scrollbar-track {
            background: var(--bg-tertiary);
            border-radius: 4px;
        }

        .test-files::-webkit-scrollbar-thumb {
            background: var(--border-color);
            border-radius: 4px;
            transition: var(--transition);
        }

        .test-files::-webkit-scrollbar-thumb:hover {
            background: var(--primary-color);
        }

        .test-file {
            border-bottom: 1px solid var(--border-light);
        }

        .test-file:last-child {
            border-bottom: none;
        }

        .test-file-header {
            padding: 12px 16px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 12px;
            transition: all 0.2s ease;
            background: var(--bg-secondary);
        }

        .test-file-header:hover {
            background: var(--bg-primary);
        }

        .test-file.expanded .test-file-header {
            background: var(--bg-primary);
            border-bottom: 1px solid var(--border-color);
        }

        .test-file-header-left {
            flex: 1;
            min-width: 0;
        }

        .test-file-title {
            display: flex;
            align-items: center;
            gap: 10px;
            font-weight: 600;
            color: var(--text-primary);
        }


        .file-select-all {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-right: auto;
            padding-right: 16px;
            pointer-events: auto;
            z-index: 1000;
        }

        .file-select-all-checkbox {
            width: 18px;
            height: 18px;
            border-radius: 4px;
            border: 2px solid var(--border-color);
            background: var(--bg-secondary);
            cursor: pointer;
            transition: var(--transition);
            appearance: none;
            position: relative;
            flex-shrink: 0;
            z-index: 999;
            pointer-events: auto;
            outline: none;
        }

        .file-select-all-checkbox:checked {
            background: var(--primary-color);
            border-color: var(--primary-color);
            box-shadow: 0 0 10px rgba(124, 58, 237, 0.5);
        }
        
        .file-select-all-checkbox:hover {
            background: var(--primary-light);
            border-color: var(--primary-light);
            transform: scale(1.1);
        }

        .file-select-all-checkbox:checked::after {
            content: '✓';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 11px;
            font-weight: bold;
        }

        .file-select-all-checkbox:indeterminate {
            background: var(--warning-color);
            border-color: var(--warning-color);
        }

        .file-select-all-checkbox:indeterminate::after {
            content: '−';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 13px;
            font-weight: bold;
        }

        .file-select-all-label {
            font-size: 12px;
            color: var(--text-secondary);
            cursor: pointer;
            font-weight: 500;
        }

        .toggle-icon {
            color: var(--text-muted);
            transition: transform 0.2s ease;
            cursor: pointer;
            font-size: 14px;
        }

        .test-file.expanded .toggle-icon {
            transform: rotate(180deg);
        }

        .test-cases {
            display: none;
            max-height: 300px;
            overflow-y: auto;
            background: var(--bg-secondary);
            border-top: 1px solid var(--border-color);
        }

        .test-file.expanded .test-cases {
            display: block;
        }

        .test-case-item {
            padding: 10px 16px;
            display: flex;
            align-items: center;
            gap: 12px;
            transition: all 0.2s ease;
            border-bottom: 1px solid var(--border-light);
        }

        .test-case-checkbox {
            width: 20px;
            height: 20px;
            margin-right: 12px;
            border-radius: 4px;
            border: 2px solid var(--border-color);
            background: var(--bg-secondary);
            cursor: pointer;
            transition: var(--transition);
            appearance: none;
            position: relative;
            flex-shrink: 0;
            z-index: 999;
            pointer-events: auto;
            outline: none;
        }

        .test-case-checkbox:checked {
            background: var(--primary-color);
            border-color: var(--primary-color);
        }

        .test-case-checkbox:checked::after {
            content: '✓';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 12px;
            font-weight: bold;
        }

        .test-case-item:last-child {
            border-bottom: none;
        }

        .test-case-item:hover {
            background: rgba(22, 163, 74, 0.05);
        }

        .test-case-item.selected {
            background: rgba(22, 163, 74, 0.1);
            border-left: 3px solid var(--primary-color);
        }

        .test-case-checkbox {
            margin: 0;
            width: 16px;
            height: 16px;
            accent-color: var(--primary-color);
        }

        .test-case-label {
            flex: 1;
            cursor: pointer;
            font-size: 14px;
            color: var(--text-primary);
            line-height: 1.4;
        }

        /* Main Content Styles */
        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            min-width: 0;
            animation: slideInUp 0.6s ease-out 0.8s both;
        }

        .console-panel {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            position: relative;
        }

        .console-header {
            background: var(--primary-gradient);
            color: var(--text-inverse);
            padding: 28px 32px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            overflow: hidden;
        }

        .console-header::before {
            content: '';
            position: absolute;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
            animation: float 10s ease-in-out infinite;
        }

        .console-header h3 {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 1.5rem;
            font-weight: 700;
            position: relative;
            z-index: 1;
            text-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }

        .console-header h3 i {
            font-size: 1.75rem;
            filter: drop-shadow(0 0 8px rgba(255,255,255,0.3));
        }

        .console-controls {
            display: flex;
            align-items: center;
            gap: 12px;
            position: relative;
            z-index: 1;
        }

        .console-btn {
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(10px);
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.2);
            padding: 10px 16px;
            border-radius: var(--border-radius);
            cursor: pointer;
            transition: var(--transition);
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            font-weight: 600;
            font-family: 'Inter', sans-serif;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .console-btn:hover {
            background: rgba(255, 255, 255, 0.25);
            transform: translateY(-2px);
            box-shadow: 0 8px 32px rgba(0,0,0,0.2);
        }
        
        .console-btn.btn-primary {
            background: var(--primary-gradient);
            color: var(--text-inverse);
            border-color: var(--primary-color);
        }
        
        .console-btn.btn-primary:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: var(--glow), 0 8px 32px rgba(0,0,0,0.2);
        }
        
        .console-btn.btn-danger {
            background: linear-gradient(135deg, var(--danger-color), var(--danger-light));
            color: var(--text-inverse);
            border-color: var(--danger-color);
        }
        
        .console-btn.btn-danger:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(239, 68, 68, 0.3), 0 8px 32px rgba(0,0,0,0.2);
        }
        
        .console-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none !important;
        }

        .console-btn i {
            font-size: 14px;
        }

        .status-bar {
            padding: 16px 32px;
            border-bottom: 1px solid var(--border-color);
            display: flex;
            align-items: center;
            justify-content: space-between;
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            position: relative;
        }

        .status-bar::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 1px;
            background: linear-gradient(90deg, transparent, rgba(124, 58, 237, 0.3), transparent);
        }

        .status-indicator {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
            font-weight: 500;
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--danger-color);
        }

        .status-indicator.status-connected .status-dot {
            background: var(--success-color);
            box-shadow: 0 0 6px rgba(16, 185, 129, 0.4);
        }

        .log-container {
            flex: 1;
            padding: 24px 32px;
            overflow-y: auto;
            background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
            color: #e2e8f0;
            font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.8;
            position: relative;
        }

        .log-container::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.1) 0%, transparent 50%);
            pointer-events: none;
        }

        .log-container::-webkit-scrollbar {
            width: 12px;
        }

        .log-container::-webkit-scrollbar-track {
            background: rgba(0, 0, 0, 0.2);
            border-radius: 6px;
        }

        .log-container::-webkit-scrollbar-thumb {
            background: linear-gradient(135deg, #7c3aed, #8b5cf6);
            border-radius: 6px;
            border: 2px solid transparent;
            background-clip: content-box;
        }

        .log-container::-webkit-scrollbar-thumb:hover {
            background: linear-gradient(135deg, #8b5cf6, #a78bfa);
            background-clip: content-box;
        }

        .log-info {
            color: #38bdf8;
            margin-bottom: 6px;
        }

        .log-success {
            color: #4ade80;
            margin-bottom: 6px;
        }

        .log-warning {
            color: #facc15;
            margin-bottom: 6px;
        }

        .log-fail {
            color: #f87171;
            margin-bottom: 6px;
        }

        .log-error {
            color: #ef4444;
            margin-bottom: 6px;
        }

        /* Loading animation */
        .loading::after {
            content: '';
            display: inline-block;
            width: 0;
            animation: loading 1.5s infinite;
        }

        @keyframes loading {
            0% { content: ''; }
            25% { content: '.'; }
            50% { content: '..'; }
            75% { content: '...'; }
            100% { content: ''; }
        }

        /* Scrollbar styling */
        .test-files::-webkit-scrollbar,
        .test-cases::-webkit-scrollbar,
        .log-container::-webkit-scrollbar {
            width: 6px;
        }

        .test-files::-webkit-scrollbar-track,
        .test-cases::-webkit-scrollbar-track {
            background: var(--bg-primary);
        }

        .test-files::-webkit-scrollbar-thumb,
        .test-cases::-webkit-scrollbar-thumb {
            background: var(--border-color);
            border-radius: 3px;
        }

        .log-container::-webkit-scrollbar-track {
            background: #1e293b;
        }

        .log-container::-webkit-scrollbar-thumb {
            background: #475569;
            border-radius: 3px;
        }

        /* Responsive design */
        @media (max-width: 1200px) {
            .sidebar {
                width: 25%;
                min-width: 300px;
            }
        }

        @media (max-width: 768px) {
            .app-container {
                flex-direction: column;
                height: auto;
                padding: 10px;
                gap: 10px;
            }
            
            .sidebar {
                width: 100%;
                max-height: 50vh;
            }
            
            .main-content {
                height: 50vh;
            }
        }
    </style>
</head>
<body>
    <div class="app-container">
        <!-- Left Sidebar - Test Selection -->
        <div class="sidebar card">
            <div class="sidebar-resizer"></div>
            <div class="sidebar-header">
                <div class="header-content">
                    <div class="header-top">
                        <div class="icon">🥞</div>
                        <h1>Super Pancake Test Runner</h1>
                    </div>
                    <div class="header-stats">
                        <div class="header-stat-item selected" id="selected-stat">
                            <span class="header-stat-number" id="selected-count-number">0</span>
                            <span class="header-stat-label">Selected</span>
                        </div>
                        <div class="header-stat-item files" id="files-stat">
                            <span class="header-stat-number" id="files-count">0</span>
                            <span class="header-stat-label">Files</span>
                        </div>
                        <div class="header-stat-item status disconnected" id="status-stat">
                            <span class="header-stat-dot"></span>
                            <span class="header-stat-number" id="status-text-main">Disconnected</span>
                        </div>
                    </div>
                </div>
            </div>

            <div class="controls">
                <button class="btn btn-icon" onclick="location.reload()" title="Refresh test files">
                    <i class="fas fa-sync-alt"></i>
                    <span class="btn-text-small">Refresh</span>
                </button>
                <button class="btn btn-icon" onclick="openTestReport()" title="Open test report">
                    <i class="fas fa-file-alt"></i>
                    <span class="btn-text-small">Report</span>
                </button>
                <button class="btn btn-icon" onclick="toggleHeadlessMode()" title="Toggle headless/headed mode" id="headless-btn">
                    <i class="fas fa-eye" id="headless-icon"></i>
                    <span class="btn-text-small" id="headless-text">Headed</span>
                </button>
                <button class="btn btn-icon" onclick="toggleBrowserType()" title="Toggle browser (Chrome/Firefox)" id="browser-btn">
                    <i class="fab fa-chrome" id="browser-icon"></i>
                    <span class="btn-text-small" id="browser-text">Chrome</span>
                </button>
                <button class="btn btn-icon" onclick="toggleTheme()" title="Toggle dark/light mode">
                    <i class="fas fa-moon" id="theme-icon"></i>
                    <span class="btn-text-small">Theme</span>
                </button>
            </div>

            <!-- Search and Filter Section -->
            <div class="search-section">
                <div class="search-container">
                    <i class="fas fa-search search-icon"></i>
                    <input type="text" class="search-input" placeholder="Search test files..." id="search-input">
                    <button class="search-clear" onclick="clearSearch()" title="Clear search">
                        <i class="fas fa-times"></i>
                    </button>
                </div>
                <div class="filter-tabs">
                    <button class="select-all-button" id="global-select-all" onclick="toggleAllTests()" title="Select/Deselect all tests">
                        <i class="fas fa-check-double"></i>
                        <span>Select All</span>
                    </button>
                    <button class="filter-tab active" data-filter="all">All</button>
                    <button class="filter-tab" data-filter="unit">Unit</button>
                    <button class="filter-tab" data-filter="integration">Integration</button>
                    <button class="filter-tab" data-filter="e2e">E2E</button>
                </div>
            </div>

            <div class="test-files-wrapper">
                <form id="test-form">
                    <div class="test-files" id="test-files-container">
                        <div style="padding: 60px 20px; text-align: center; color: var(--text-muted);">
                            <i class="fas fa-spinner fa-spin" style="font-size: 2rem; margin-bottom: 16px; color: var(--primary-color);"></i>
                            <div style="font-weight: 600;">Loading test files...</div>
                            <div style="font-size: 12px; margin-top: 8px;">Please wait while we discover your tests</div>
                        </div>
                    </div>
                </form>
            </div>
        </div>

        <!-- Right Side - Console -->
        <div class="main-content">
            <div class="console-panel card">
                <div class="console-header">
                    <h3>
                        <i class="fas fa-terminal"></i>
                        Test Console & Live Reports
                    </h3>
                    <div class="console-controls">
                        <button class="console-btn" onclick="clearLogs()">
                            <i class="fas fa-trash-alt"></i>
                            Clear
                        </button>
                        <button class="console-btn" onclick="copyLogs()">
                            <i class="fas fa-copy"></i>
                            Copy
                        </button>
                        <button type="submit" class="console-btn btn-primary" id="run-btn" disabled>
                            <i class="fas fa-play"></i>
                            Run Tests
                        </button>
                        <button class="console-btn btn-success" id="live-btn" onclick="toggleLiveMode()" title="Toggle live monitoring mode">
                            <i class="fas fa-video"></i>
                            <span id="live-btn-text">Live Mode</span>
                        </button>
                        <button class="console-btn btn-danger" id="stop-btn" onclick="stopTests()" disabled>
                            <i class="fas fa-stop"></i>
                            Stop Tests
                        </button>
                    </div>
                </div>
                <div class="status-bar">
                    <div class="status-indicator status-error" id="status-indicator">
                        <div class="status-dot"></div>
                        <span id="status-text">Disconnected</span>
                    </div>
                    <div class="status-indicator">
                        <i class="fas fa-clock"></i>
                        <span id="execution-time">00:00</span>
                    </div>
                </div>
                <div class="log-container" id="log">
                    <div class="log-info">🎯 Welcome to the Super Pancake Test Runner!</div>
                    <div class="log-info">✨ Select tests from the left panel and click "Run Tests" to begin</div>
                    <div class="log-info">📊 Real-time results and reports will appear here</div>
                    <div class="log-info">🔌 Connecting to test server...</div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // WebSocket connection
        let ws;
        let liveWs;  // Separate WebSocket for live monitoring
        let isConnected = false;
        let isLiveModeEnabled = false;
        let selectedTests = new Set();
        let executionStartTime = null;
        let executionTimer = null;
        let reconnectAttempts = 0;
        const maxReconnectAttempts = 5;

        // Initialize connection with better retry logic
        function connectWebSocket() {
            if (reconnectAttempts >= maxReconnectAttempts) {
                logMessage('❌ Max reconnection attempts reached. Please refresh the page.', 'error');
                return;
            }

            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsUrl = `${protocol}//${window.location.host}`;
            
            try {
                // Close existing connection if any
                if (ws && ws.readyState !== WebSocket.CLOSED) {
                    ws.close();
                }
                
                ws = new WebSocket(wsUrl);
                
                // Set a connection timeout
                const connectionTimeout = setTimeout(() => {
                    if (ws.readyState === WebSocket.CONNECTING) {
                        ws.close();
                        logMessage('⚠️ Connection timeout, retrying...', 'warning');
                        reconnectAttempts++;
                        setTimeout(connectWebSocket, 2000);
                    }
                }, 10000); // 10 second timeout
                
                ws.onopen = function() {
                    clearTimeout(connectionTimeout);
                    isConnected = true;
                    reconnectAttempts = 0;
                    updateConnectionStatus();
                    logMessage('🔌 Connected to test runner successfully', 'success');
                };
                
                ws.onmessage = function(event) {
                    try {
                        const message = event.data.toString();
                        logMessage(message, 'info');
                        
                        // Check for test completion
                        if (message.includes('All tests finished') || 
                            message.includes('HTML report generated') ||
                            message.includes('🎉 All tests completed successfully')) {
                            
                            stopTestExecution();
                            logMessage('✅ Test execution completed', 'success');
                        }
                    } catch (error) {
                        console.error('Error processing WebSocket message:', error);
                    }
                };
                
                ws.onclose = function(event) {
                    clearTimeout(connectionTimeout);
                    isConnected = false;
                    updateConnectionStatus();
                    
                    // Only reconnect if it wasn't a clean close and we haven't exceeded attempts
                    if (event.code !== 1000 && reconnectAttempts < maxReconnectAttempts) {
                        reconnectAttempts++;
                        const delay = Math.min(2000 * reconnectAttempts, 10000); // Max 10s delay
                        logMessage(`❌ Connection lost. Attempting to reconnect... (${reconnectAttempts}/${maxReconnectAttempts})`, 'warning');
                        setTimeout(connectWebSocket, delay);
                    } else if (reconnectAttempts >= maxReconnectAttempts) {
                        logMessage('❌ Max reconnection attempts reached. Please refresh the page.', 'error');
                    }
                };
                
                ws.onerror = function(error) {
                    clearTimeout(connectionTimeout);
                    logMessage('⚠️ WebSocket connection error', 'warning');
                    console.error('WebSocket error:', error);
                };
                
            } catch (error) {
                logMessage('❌ Failed to create WebSocket connection', 'error');
                console.error('WebSocket creation error:', error);
                reconnectAttempts++;
                if (reconnectAttempts < maxReconnectAttempts) {
                    setTimeout(connectWebSocket, 3000);
                }
            }
        }

        function updateConnectionStatus() {
            const statusEl = document.getElementById('status-indicator');
            const statusTextEl = document.getElementById('status-text');
            const statusStatEl = document.getElementById('status-stat');
            const statusTextMainEl = document.getElementById('status-text-main');
            
            if (isConnected) {
                statusEl.className = 'status-indicator status-connected';
                statusTextEl.textContent = 'Connected';
                statusStatEl.className = 'header-stat-item status connected';
                statusTextMainEl.textContent = 'Connected';
            } else {
                statusEl.className = 'status-indicator status-error';
                statusTextEl.textContent = 'Disconnected';
                statusStatEl.className = 'header-stat-item status disconnected';
                statusTextMainEl.textContent = 'Disconnected';
            }
        }

        function logMessage(message, type = 'info') {
            const logContainer = document.getElementById('log');
            const logDiv = document.createElement('div');
            logDiv.className = `log-${type}`;
            logDiv.textContent = message;
            logContainer.appendChild(logDiv);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        function clearLogs() {
            const logContainer = document.getElementById('log');
            logContainer.innerHTML = `
                <div class="log-info">🎯 Welcome to the Super Pancake Test Runner!</div>
                <div class="log-info">✨ Select tests from the left panel and click "Run Tests" to begin</div>
                <div class="log-info">📊 Real-time results and reports will appear here</div>
            `;
        }

        function copyLogs() {
            const logContainer = document.getElementById('log');
            const logs = Array.from(logContainer.children).map(div => div.textContent).join('\n');
            navigator.clipboard.writeText(logs).then(() => {
                logMessage('📋 Logs copied to clipboard!', 'success');
            });
        }

        // Function to open the test report
        function openTestReport() {
            try {
                // Try to open the report in a new tab
                const reportUrl = '/automationTestReport.html';
                const newWindow = window.open(reportUrl, '_blank');
                
                if (newWindow) {
                    logMessage('📊 Opening test report in new tab...', 'success');
                } else {
                    // If popup blocked, try to navigate
                    logMessage('⚠️ Popup blocked. Trying to navigate to report...', 'warning');
                    setTimeout(() => {
                        window.location.href = reportUrl;
                    }, 1000);
                }
            } catch (error) {
                logMessage('❌ Failed to open test report: ' + error.message, 'error');
                console.error('Error opening test report:', error);
            }
        }

        // Test file management
        async function loadTestFiles() {
            try {
                const response = await fetch('/api/test-files');
                const testFiles = await response.json();
                
                const container = document.getElementById('test-files-container');
                container.innerHTML = '';
                
                document.getElementById('files-count').textContent = testFiles.length;
                
                for (const file of testFiles) {
                    await createTestFileElement(file, container);
                }
                
                // Update the select all button after files are loaded
                setTimeout(() => {
                    updateSelectAllButton();
                }, 100);
                
                logMessage(`📁 Loaded ${testFiles.length} test files successfully`, 'success');
            } catch (error) {
                logMessage(`❌ Failed to load test files: ${error.message}`, 'error');
                const container = document.getElementById('test-files-container');
                container.innerHTML = `
                    <div style="padding: 40px 20px; text-align: center; color: var(--danger-color);">
                        <i class="fas fa-exclamation-triangle" style="font-size: 2rem; margin-bottom: 16px;"></i>
                        <div style="font-weight: 600;">Failed to load test files</div>
                        <div style="font-size: 12px; margin-top: 8px;">${error.message}</div>
                    </div>
                `;
            }
        }

        async function createTestFileElement(file, container) {
            const groupId = file.replace(/[\/\.]/g, '_');
            
            const fileDiv = document.createElement('div');
            fileDiv.className = 'test-file';
            fileDiv.id = `group-${groupId}`;
            
            // Load test cases
            let testCases = [];
            try {
                // Normalize file path for Windows compatibility
                const normalizedFile = file.replace(/\\/g, '/');
                const casesResponse = await fetch('/api/test-cases', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ filePath: normalizedFile })
                });
                testCases = await casesResponse.json();
            } catch (error) {
                console.error(`Failed to load test cases for ${file}:`, error);
            }
            
            fileDiv.innerHTML = `
                <div class="test-file-header">
                    <div class="file-select-all">
                        <input type="checkbox" class="file-select-all-checkbox" data-group="${groupId}" id="select-all-${groupId}">
                        <label for="select-all-${groupId}" class="file-select-all-label">All</label>
                    </div>
                    <div class="test-file-header-left">
                        <div class="test-file-title">
                            ${file}
                        </div>
                    </div>
                    <i class="fas fa-chevron-down toggle-icon" data-group="${groupId}" id="toggle-${groupId}"></i>
                </div>
                <div class="test-cases" id="cases-${groupId}">
                    ${testCases.map(testCase => `
                        <div class="test-case-item" data-test-id="${file}::${testCase}">
                            <input type="checkbox" name="tests" value="${file}::${testCase}" class="test-case-checkbox" id="${file}::${testCase}">
                            <label for="${file}::${testCase}" class="test-case-label">${testCase}</label>
                        </div>
                    `).join('')}
                </div>
            `;
            
            container.appendChild(fileDiv);
        }

        function toggleFile(groupId) {
            console.log('toggleFile called with groupId:', groupId);
            
            // Escape backslashes in the group ID for CSS selector
            const escapedGroupId = groupId.replace(/\\/g, '\\\\');
            console.log('Escaped groupId for toggleFile:', escapedGroupId);
            
            const fileElement = document.getElementById(`group-${escapedGroupId}`);
            console.log('Found file element:', fileElement);
            
            if (fileElement) {
                fileElement.classList.toggle('expanded');
            } else {
                console.error('File element not found for groupId:', groupId);
            }
        }

        function toggleGroup(groupId, checked) {
            console.log('toggleGroup called with groupId:', groupId, 'checked:', checked);
            
            // Escape backslashes in the group ID for CSS selector
            const escapedGroupId = groupId.replace(/\\/g, '\\\\');
            console.log('Escaped groupId:', escapedGroupId);
            
            const selector = `#cases-${escapedGroupId} input[type="checkbox"]`;
            console.log('Using selector:', selector);
            
            const checkboxes = document.querySelectorAll(selector);
            console.log('Found checkboxes:', checkboxes.length);
            
            checkboxes.forEach(cb => {
                cb.checked = checked;
                const item = cb.closest('.test-case-item');
                if (checked) {
                    item.classList.add('selected');
                } else {
                    item.classList.remove('selected');
                }
            });
            updateSelectedCount();
        }

        function updateSelectedCount() {
            const checkboxes = document.querySelectorAll('.test-case-checkbox:checked');
            const count = checkboxes.length;
            
            document.getElementById('selected-count-number').textContent = count;
            document.getElementById('run-btn').disabled = count === 0;
            
            // Update visual selection
            document.querySelectorAll('.test-case-checkbox').forEach(cb => {
                const item = cb.closest('.test-case-item');
                if (cb.checked) {
                    item.classList.add('selected');
                } else {
                    item.classList.remove('selected');
                }
            });
            
            // Update group checkboxes
            document.querySelectorAll('.test-file').forEach(file => {
                const groupCheckbox = file.querySelector('.file-select-all-checkbox');
                const testCheckboxes = file.querySelectorAll('.test-case-checkbox');
                const checkedTestBoxes = file.querySelectorAll('.test-case-checkbox:checked');
                
                console.log('Updating group checkbox for file:', file.id, 'checked:', checkedTestBoxes.length, 'total:', testCheckboxes.length);
                
                if (checkedTestBoxes.length === 0) {
                    groupCheckbox.checked = false;
                    groupCheckbox.indeterminate = false;
                } else if (checkedTestBoxes.length === testCheckboxes.length) {
                    groupCheckbox.checked = true;
                    groupCheckbox.indeterminate = false;
                } else {
                    groupCheckbox.checked = false;
                    groupCheckbox.indeterminate = true;
                }
            });
        }

        async function runTests() {
            if (!isConnected) {
                logMessage('⚠️ Not connected to test runner. Please wait for connection.', 'warning');
                return;
            }
            
            const checkboxes = document.querySelectorAll('.test-case-checkbox:checked');
            const tests = Array.from(checkboxes).map(cb => cb.value);
            
            if (tests.length === 0) {
                logMessage('⚠️ Please select at least one test to run.', 'warning');
                return;
            }
            
            // Update button states
            document.getElementById('run-btn').disabled = true;
            document.getElementById('run-btn').classList.add('loading');
            document.getElementById('stop-btn').disabled = false;
            
            // Start pancake animation
            const icon = document.querySelector('.icon');
            icon.classList.add('running');
            
            // Start execution timer
            executionStartTime = Date.now();
            executionTimer = setInterval(updateExecutionTime, 1000);
            
            clearLogs();
            logMessage(`🚀 Starting execution of ${tests.length} selected tests...`, 'info');
            
            try {
                const response = await fetch('/run', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ 
                        tests,
                        headless: isHeadlessMode,
                        browser: selectedBrowser
                    })
                });
                
                if (!response.ok) {
                    throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
                }
                
                const modeText = isHeadlessMode ? 'headless' : 'headed';
                const browserName = selectedBrowser === 'chrome' ? 'Chrome' : 'Firefox';
                logMessage(`📤 Test execution request sent successfully (${browserName} ${modeText} mode)`, 'success');
            } catch (error) {
                logMessage(`❌ Failed to start test execution: ${error.message}`, 'error');
                stopTestExecution();
            }
        }
        
        async function stopTests() {
            try {
                // Stop via live WebSocket if in live mode
                if (isLiveModeEnabled && liveWs && liveWs.readyState === WebSocket.OPEN) {
                    liveWs.send(JSON.stringify({
                        type: 'stop_tests'
                    }));
                    logMessage('🔴 [LIVE] Test stop requested', 'warning');
                } else {
                    // Regular stop via HTTP
                    const response = await fetch('/stop', {
                        method: 'POST'
                    });
                    
                    if (response.ok) {
                        logMessage('🛑 Test execution stop requested', 'warning');
                    } else {
                        logMessage('⚠️ Failed to stop tests - they may complete naturally', 'warning');
                    }
                }
            } catch (error) {
                logMessage(`❌ Error stopping tests: ${error.message}`, 'error');
            }
            
            stopTestExecution();
        }
        
        function stopTestExecution() {
            // Update button states
            document.getElementById('run-btn').disabled = false;
            document.getElementById('run-btn').classList.remove('loading');
            document.getElementById('stop-btn').disabled = true;
            
            // Stop pancake animation
            const icon = document.querySelector('.icon');
            icon.classList.remove('running');
            
            // Stop execution timer
            if (executionTimer) {
                clearInterval(executionTimer);
                executionTimer = null;
            }
        }

        function updateExecutionTime() {
            if (!executionStartTime) return;
            
            const elapsed = Math.floor((Date.now() - executionStartTime) / 1000);
            const minutes = Math.floor(elapsed / 60);
            const seconds = elapsed % 60;
            
            document.getElementById('execution-time').textContent = 
                `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        }

        // Live Mode Functionality
        function toggleLiveMode() {
            isLiveModeEnabled = !isLiveModeEnabled;
            
            const liveBtn = document.getElementById('live-btn');
            const liveBtnText = document.getElementById('live-btn-text');
            const liveIcon = liveBtn.querySelector('i');
            
            if (isLiveModeEnabled) {
                // Enable live mode
                liveBtn.style.background = 'linear-gradient(135deg, #10b981, #34d399)';
                liveBtn.style.borderColor = '#10b981';
                liveIcon.className = 'fas fa-video';
                liveBtnText.textContent = 'Live ON';
                liveBtn.title = 'Live monitoring enabled - Click to disable';
                
                // Connect to live WebSocket server
                connectLiveWebSocket();
                
                logMessage('🔴 Live monitoring mode enabled - Real-time test streaming active', 'success');
                localStorage.setItem('liveModeEnabled', 'true');
            } else {
                // Disable live mode
                liveBtn.style.background = '';
                liveBtn.style.borderColor = '';
                liveIcon.className = 'fas fa-video';
                liveBtnText.textContent = 'Live Mode';
                liveBtn.title = 'Toggle live monitoring mode';
                
                // Disconnect live WebSocket
                if (liveWs) {
                    liveWs.close();
                    liveWs = null;
                }
                
                logMessage('⚪ Live monitoring mode disabled', 'info');
                localStorage.setItem('liveModeEnabled', 'false');
            }
        }

        // Connect to Live WebSocket Server for real-time monitoring
        async function connectLiveWebSocket() {
            try {
                // Try to discover live server port
                let livePort = 3002; // fallback
                try {
                    const response = await fetch('/api/config');
                    const config = await response.json();
                    livePort = config.wsPort || 3002;
                } catch (error) {
                    console.warn('Could not fetch live server config, using default port');
                }

                const liveWsUrl = `ws://${window.location.hostname}:${livePort}`;
                liveWs = new WebSocket(liveWsUrl);
                
                liveWs.onopen = () => {
                    logMessage('🔴 Connected to live monitoring server', 'success');
                };
                
                liveWs.onmessage = (event) => {
                    try {
                        const message = JSON.parse(event.data);
                        handleLiveMessage(message);
                    } catch (error) {
                        // Handle plain text messages from live server
                        const plainMessage = event.data.toString();
                        logMessage(`[LIVE] ${plainMessage}`, 'info');
                    }
                };
                
                liveWs.onclose = () => {
                    logMessage('🔴 Live monitoring disconnected', 'warning');
                    if (isLiveModeEnabled) {
                        // Try to reconnect after 3 seconds if live mode is still enabled
                        setTimeout(() => {
                            if (isLiveModeEnabled) {
                                connectLiveWebSocket();
                            }
                        }, 3000);
                    }
                };
                
                liveWs.onerror = (error) => {
                    console.error('Live WebSocket error:', error);
                    logMessage('⚠️ Live monitoring connection error', 'warning');
                };
                
            } catch (error) {
                console.error('Failed to create live WebSocket:', error);
                logMessage('❌ Failed to connect to live monitoring server', 'error');
            }
        }

        // Handle live monitoring messages
        function handleLiveMessage(message) {
            switch (message.type) {
                case 'test_started':
                    logMessage(`🔴 [LIVE] Test execution started - ${message.data.totalTests || 'unknown'} tests`, 'success');
                    break;
                    
                case 'console_output':
                    const outputData = message.data;
                    const outputClass = outputData.type === 'stderr' ? 'error' : 'info';
                    logMessage(`🔴 [LIVE] ${outputData.output}`, outputClass);
                    break;
                    
                case 'test_completed':
                    const status = message.data.exitCode === 0 ? 'passed' : 'failed';
                    const statusClass = status === 'passed' ? 'success' : 'error';
                    logMessage(`🔴 [LIVE] Tests completed - Exit code: ${message.data.exitCode}`, statusClass);
                    break;
                    
                case 'test_stopped':
                    logMessage('🔴 [LIVE] Test execution stopped', 'warning');
                    break;
                    
                case 'state_update':
                    // Update progress indicators based on live state
                    updateLiveProgress(message.data);
                    break;
                    
                default:
                    logMessage(`🔴 [LIVE] ${message.data?.output || JSON.stringify(message)}`, 'info');
                    break;
            }
        }

        // Update progress indicators in live mode
        function updateLiveProgress(liveState) {
            if (!isLiveModeEnabled) return;
            
            // Update execution time for live mode
            if (liveState.startTime && liveState.status === 'running') {
                const startTime = new Date(liveState.startTime).getTime();
                const elapsed = Math.floor((Date.now() - startTime) / 1000);
                const minutes = Math.floor(elapsed / 60);
                const seconds = elapsed % 60;
                
                document.getElementById('execution-time').textContent = 
                    `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
            }
            
            // Show current test in status
            if (liveState.currentTest) {
                const statusText = document.getElementById('status-text');
                statusText.textContent = `Running: ${liveState.currentTest}`;
            }
        }

        // Enhanced run tests function with live mode support
        async function runTestsWithLiveMode() {
            if (isLiveModeEnabled && liveWs && liveWs.readyState === WebSocket.OPEN) {
                // Use live WebSocket to run tests
                const checkboxes = document.querySelectorAll('.test-case-checkbox:checked');
                const tests = Array.from(checkboxes).map(cb => cb.value);
                
                if (tests.length === 0) {
                    logMessage('⚠️ Please select at least one test to run.', 'warning');
                    return;
                }
                
                liveWs.send(JSON.stringify({
                    type: 'run_tests',
                    testFiles: tests
                }));
                
                logMessage(`🔴 [LIVE] Starting ${tests.length} tests in live monitoring mode...`, 'success');
                
                // Update UI state
                document.getElementById('run-btn').disabled = true;
                document.getElementById('stop-btn').disabled = false;
                
                // Start pancake animation
                const icon = document.querySelector('.icon');
                icon.classList.add('running');
                
            } else {
                // Fall back to regular execution
                await runTests();
            }
        }

        // Event listeners
        document.getElementById('run-btn').addEventListener('click', () => {
            if (isLiveModeEnabled) {
                runTestsWithLiveMode();
            } else {
                runTests();
            }
        });

        // Event delegation for dynamically created checkboxes
        document.addEventListener('change', function(e) {
            console.log('Change event triggered on:', e.target);
            if (e.target.classList.contains('file-select-all-checkbox')) {
                console.log('File select all checkbox changed to:', e.target.checked);
                const groupId = e.target.dataset.group;
                if (groupId) {
                    toggleGroup(groupId, e.target.checked);
                } else {
                    console.error('No group ID found for checkbox:', e.target);
                }
            } else if (e.target.classList.contains('test-case-checkbox')) {
                console.log('Test case checkbox changed to:', e.target.checked);
                updateSelectedCount();
            }
        });

        // Event delegation for toggle icons and non-checkbox elements
        document.addEventListener('click', function(e) {
            console.log('Click event triggered on:', e.target);
            
            // Handle toggle icons
            if (e.target.classList.contains('toggle-icon')) {
                const groupId = e.target.dataset.group;
                toggleFile(groupId);
                return;
            }
            
            // Handle checkbox labels
            if (e.target.classList.contains('file-select-all-label')) {
                console.log('Checkbox label clicked');
                // Let the browser handle the label click naturally
                return;
            }
            
            // Handle test case labels
            if (e.target.classList.contains('test-case-label')) {
                console.log('Test case label clicked');
                // Let the browser handle the label click naturally
                return;
            }
            
            // Handle header clicks (but not if clicking on checkbox area)
            if (e.target.classList.contains('test-file-header')) {
                if (!e.target.closest('.file-select-all')) {
                    const toggleIcon = e.target.querySelector('.toggle-icon');
                    if (toggleIcon) {
                        const groupId = toggleIcon.dataset.group;
                        toggleFile(groupId);
                    }
                }
                return;
            }
            
            // Stop propagation for checkbox area to prevent header clicks
            if (e.target.closest('.file-select-all')) {
                e.stopPropagation();
                console.log('Stopped propagation for checkbox area');
                return;
            }
        });

        // Initialize everything
        document.addEventListener('DOMContentLoaded', () => {
            // Load saved theme first
            loadSavedTheme();
            
            // Load saved headless mode
            loadSavedHeadlessMode();
            
            // Load saved browser type
            loadSavedBrowserType();
            
            // Load saved live mode state
            loadSavedLiveModeState();
            
            // Setup search and filter functionality
            setupSearchAndFilter();
            
            // Setup sidebar resizer
            setupSidebarResizer();
            
            // Start connecting immediately
            connectWebSocket();
            
            // Load test files after a short delay
            setTimeout(() => {
                loadTestFiles();
            }, 500);
        });

        // Search and filter functionality
        function setupSearchAndFilter() {
            const searchInput = document.getElementById('search-input');
            const filterTabs = document.querySelectorAll('.filter-tab');
            
            searchInput.addEventListener('input', filterTestFiles);
            
            filterTabs.forEach(tab => {
                tab.addEventListener('click', () => {
                    // Update active tab
                    filterTabs.forEach(t => t.classList.remove('active'));
                    tab.classList.add('active');
                    
                    // Apply filter
                    filterTestFiles();
                });
            });
        }
        
        function filterTestFiles() {
            const searchTerm = document.getElementById('search-input').value.toLowerCase();
            const activeFilter = document.querySelector('.filter-tab.active').dataset.filter;
            const testFiles = document.querySelectorAll('.test-file');
            
            testFiles.forEach(file => {
                const fileName = file.querySelector('.test-file-title').textContent.toLowerCase();
                
                // Check search match
                const searchMatch = searchTerm === '' || fileName.includes(searchTerm);
                
                // Check filter match
                let filterMatch = true;
                if (activeFilter !== 'all') {
                    filterMatch = fileName.includes(activeFilter);
                }
                
                // Show/hide file
                file.style.display = (searchMatch && filterMatch) ? 'block' : 'none';
            });
        }
        
        function clearSearch() {
            document.getElementById('search-input').value = '';
            filterTestFiles();
        }

        // Global select all functionality
        function toggleAllTests() {
            const button = document.getElementById('global-select-all');
            const allTestCheckboxes = document.querySelectorAll('.test-case-checkbox');
            const allFileCheckboxes = document.querySelectorAll('.file-select-all-checkbox');
            
            // Determine if we should select all or deselect all
            const checkedTestBoxes = document.querySelectorAll('.test-case-checkbox:checked');
            const shouldSelectAll = checkedTestBoxes.length === 0 || checkedTestBoxes.length < allTestCheckboxes.length;
            
            // Update all test checkboxes
            allTestCheckboxes.forEach(checkbox => {
                const wasChecked = checkbox.checked;
                checkbox.checked = shouldSelectAll;
                
                // Trigger change event to update selectedTests set and visual state
                if (wasChecked !== shouldSelectAll) {
                    const event = new Event('change', { bubbles: true });
                    checkbox.dispatchEvent(event);
                }
            });
            
            // Update all file checkboxes to match
            allFileCheckboxes.forEach(fileCheckbox => {
                fileCheckbox.checked = shouldSelectAll;
                fileCheckbox.indeterminate = false;
            });
            
            // Update button appearance and text
            updateSelectAllButton();
            
            // Log the action
            const action = shouldSelectAll ? 'selected' : 'deselected';
            const count = allTestCheckboxes.length;
            logMessage(`🔘 ${action} all ${count} tests`, shouldSelectAll ? 'success' : 'info');
        }

        // Update the select all button appearance based on current selection state
        function updateSelectAllButton() {
            const button = document.getElementById('global-select-all');
            const allTestCheckboxes = document.querySelectorAll('.test-case-checkbox');
            const checkedTestBoxes = document.querySelectorAll('.test-case-checkbox:checked');
            
            if (!button || allTestCheckboxes.length === 0) return;
            
            const isAllSelected = checkedTestBoxes.length === allTestCheckboxes.length;
            const isNoneSelected = checkedTestBoxes.length === 0;
            const icon = button.querySelector('i');
            const text = button.querySelector('span');
            
            if (isAllSelected) {
                // All selected - show deselect mode
                button.classList.add('deselect-mode');
                icon.className = 'fas fa-minus-circle';
                text.textContent = 'Deselect All';
                button.title = 'Deselect all tests';
            } else {
                // Some or none selected - show select mode
                button.classList.remove('deselect-mode');
                icon.className = 'fas fa-check-double';
                text.textContent = 'Select All';
                button.title = 'Select all tests';
            }
        }
        
        // Headless mode toggle functionality
        let isHeadlessMode = false;
        
        function toggleHeadlessMode() {
            const headlessIcon = document.getElementById('headless-icon');
            const headlessText = document.getElementById('headless-text');
            const headlessBtn = document.getElementById('headless-btn');
            
            isHeadlessMode = !isHeadlessMode;
            
            if (isHeadlessMode) {
                // Switch to headless mode
                headlessIcon.className = 'fas fa-eye-slash';
                headlessText.textContent = 'Headless';
                headlessBtn.style.background = 'linear-gradient(135deg, #f59e0b, #fbbf24)';
                headlessBtn.style.color = 'white';
                headlessBtn.style.borderColor = '#f59e0b';
                headlessBtn.title = 'Toggle headless/headed mode (Currently: Headless)';
                localStorage.setItem('headlessMode', 'true');
                logMessage('⚡ Switched to headless mode - tests will run faster without GUI', 'info');
            } else {
                // Switch to headed mode
                headlessIcon.className = 'fas fa-eye';
                headlessText.textContent = 'Headed';
                headlessBtn.style.background = '';
                headlessBtn.style.color = '';
                headlessBtn.style.borderColor = '';
                headlessBtn.title = 'Toggle headless/headed mode (Currently: Headed)';
                localStorage.setItem('headlessMode', 'false');
                logMessage('👀 Switched to headed mode - browser windows will be visible', 'info');
            }
        }
        
        // Browser type toggle functionality
        let selectedBrowser = 'chrome';
        
        function toggleBrowserType() {
            const browserIcon = document.getElementById('browser-icon');
            const browserText = document.getElementById('browser-text');
            const browserBtn = document.getElementById('browser-btn');
            
            selectedBrowser = selectedBrowser === 'chrome' ? 'firefox' : 'chrome';
            
            if (selectedBrowser === 'firefox') {
                // Switch to Firefox
                browserIcon.className = 'fab fa-firefox-browser';
                browserText.textContent = 'Firefox';
                browserBtn.style.background = 'linear-gradient(135deg, #ff6600, #ff9500)';
                browserBtn.style.color = 'white';
                browserBtn.style.borderColor = '#ff6600';
                browserBtn.title = 'Toggle browser (Chrome/Firefox) - Currently: Firefox';
                localStorage.setItem('selectedBrowser', 'firefox');
                logMessage('🦊 Switched to Firefox browser - tests will run in Firefox', 'info');
            } else {
                // Switch to Chrome
                browserIcon.className = 'fab fa-chrome';
                browserText.textContent = 'Chrome';
                browserBtn.style.background = '';
                browserBtn.style.color = '';
                browserBtn.style.borderColor = '';
                browserBtn.title = 'Toggle browser (Chrome/Firefox) - Currently: Chrome';
                localStorage.setItem('selectedBrowser', 'chrome');
                logMessage('🔶 Switched to Chrome browser - tests will run in Chrome', 'info');
            }
        }
        
        // Load saved browser type
        function loadSavedBrowserType() {
            const savedBrowser = localStorage.getItem('selectedBrowser');
            const browserIcon = document.getElementById('browser-icon');
            const browserText = document.getElementById('browser-text');
            const browserBtn = document.getElementById('browser-btn');
            
            if (savedBrowser === 'firefox') {
                selectedBrowser = 'firefox';
                browserIcon.className = 'fab fa-firefox-browser';
                browserText.textContent = 'Firefox';
                browserBtn.style.background = 'linear-gradient(135deg, #ff6600, #ff9500)';
                browserBtn.style.color = 'white';
                browserBtn.style.borderColor = '#ff6600';
                browserBtn.title = 'Toggle browser (Chrome/Firefox) - Currently: Firefox';
            } else {
                selectedBrowser = 'chrome';
                browserIcon.className = 'fab fa-chrome';
                browserText.textContent = 'Chrome';
                browserBtn.title = 'Toggle browser (Chrome/Firefox) - Currently: Chrome';
            }
        }

        // Load saved live mode state
        function loadSavedLiveModeState() {
            const savedLiveMode = localStorage.getItem('liveModeEnabled');
            if (savedLiveMode === 'true') {
                // Auto-enable live mode on page load
                toggleLiveMode();
            }
        }
        
        // Load saved headless mode
        function loadSavedHeadlessMode() {
            const savedHeadlessMode = localStorage.getItem('headlessMode');
            const headlessIcon = document.getElementById('headless-icon');
            const headlessText = document.getElementById('headless-text');
            const headlessBtn = document.getElementById('headless-btn');
            
            if (savedHeadlessMode === 'true') {
                isHeadlessMode = true;
                headlessIcon.className = 'fas fa-eye-slash';
                headlessText.textContent = 'Headless';
                headlessBtn.style.background = 'linear-gradient(135deg, #f59e0b, #fbbf24)';
                headlessBtn.style.color = 'white';
                headlessBtn.style.borderColor = '#f59e0b';
                headlessBtn.title = 'Toggle headless/headed mode (Currently: Headless)';
            } else {
                isHeadlessMode = false;
                headlessIcon.className = 'fas fa-eye';
                headlessText.textContent = 'Headed';
                headlessBtn.title = 'Toggle headless/headed mode (Currently: Headed)';
            }
        }
        
        // Theme toggle functionality
        function toggleTheme() {
            const body = document.body;
            const themeIcon = document.getElementById('theme-icon');
            
            if (body.hasAttribute('data-theme')) {
                // Switch to light mode
                body.removeAttribute('data-theme');
                themeIcon.className = 'fas fa-moon';
                localStorage.setItem('theme', 'light');
            } else {
                // Switch to dark mode
                body.setAttribute('data-theme', 'dark');
                themeIcon.className = 'fas fa-sun';
                localStorage.setItem('theme', 'dark');
            }
        }
        
        // Load saved theme
        function loadSavedTheme() {
            const savedTheme = localStorage.getItem('theme');
            const themeIcon = document.getElementById('theme-icon');
            
            if (savedTheme === 'dark') {
                document.body.setAttribute('data-theme', 'dark');
                themeIcon.className = 'fas fa-sun';
            } else {
                themeIcon.className = 'fas fa-moon';
            }
        }
        
        
        // Sidebar resizer functionality
        function setupSidebarResizer() {
            const resizer = document.querySelector('.sidebar-resizer');
            const sidebar = document.querySelector('.sidebar');
            const container = document.querySelector('.app-container');
            let isResizing = false;
            
            resizer.addEventListener('mousedown', (e) => {
                isResizing = true;
                container.classList.add('resizing');
                document.addEventListener('mousemove', handleResize);
                document.addEventListener('mouseup', stopResize);
                e.preventDefault();
            });
            
            function handleResize(e) {
                if (!isResizing) return;
                
                const containerRect = container.getBoundingClientRect();
                const newWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100;
                
                // Constrain between 15% and 60%
                const constrainedWidth = Math.max(15, Math.min(60, newWidth));
                sidebar.style.width = constrainedWidth + '%';
            }
            
            function stopResize() {
                isResizing = false;
                container.classList.remove('resizing');
                document.removeEventListener('mousemove', handleResize);
                document.removeEventListener('mouseup', stopResize);
            }
        }

        // Cleanup on page unload
        window.addEventListener('beforeunload', () => {
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.close(1000, 'Page unloading');
            }
        });
    </script>
</body>
</html>