@arghajit/playwright-pulse-report 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,12 +98,12 @@ export function ansiToHtml(text) {
98
98
  if (codes[code]) {
99
99
  if (code === "39") {
100
100
  currentStylesArray = currentStylesArray.filter(
101
- (s) => !s.startsWith("color:")
101
+ (s) => !s.startsWith("color:"),
102
102
  );
103
103
  currentStylesArray.push("color:inherit");
104
104
  } else if (code === "49") {
105
105
  currentStylesArray = currentStylesArray.filter(
106
- (s) => !s.startsWith("background-color:")
106
+ (s) => !s.startsWith("background-color:"),
107
107
  );
108
108
  currentStylesArray.push("background-color:inherit");
109
109
  } else {
@@ -114,10 +114,10 @@ export function ansiToHtml(text) {
114
114
  const type = parts[0] === "38" ? "color" : "background-color";
115
115
  if (parts.length === 5) {
116
116
  currentStylesArray = currentStylesArray.filter(
117
- (s) => !s.startsWith(type + ":")
117
+ (s) => !s.startsWith(type + ":"),
118
118
  );
119
119
  currentStylesArray.push(
120
- `${type}:rgb(${parts[2]},${parts[3]},${parts[4]})`
120
+ `${type}:rgb(${parts[2]},${parts[3]},${parts[4]})`,
121
121
  );
122
122
  }
123
123
  }
@@ -175,7 +175,7 @@ function convertPlaywrightErrorToHTML(str) {
175
175
  if (!str) return "";
176
176
  return str
177
177
  .replace(/^(\s+)/gm, (match) =>
178
- match.replace(/ /g, " ").replace(/\t/g, " ")
178
+ match.replace(/ /g, " ").replace(/\t/g, " "),
179
179
  )
180
180
  .replace(/<red>/g, '<span style="color: red;">')
181
181
  .replace(/<green>/g, '<span style="color: green;">')
@@ -265,7 +265,7 @@ function generateTestTrendsChart(trendData) {
265
265
  .substring(2, 7)}`;
266
266
  const renderFunctionName = `renderTestTrendsChart_${chartId.replace(
267
267
  /-/g,
268
- "_"
268
+ "_",
269
269
  )}`;
270
270
  const runs = trendData.overall;
271
271
 
@@ -360,7 +360,7 @@ function generateDurationTrendChart(trendData) {
360
360
  .substring(2, 7)}`;
361
361
  const renderFunctionName = `renderDurationTrendChart_${chartId.replace(
362
362
  /-/g,
363
- "_"
363
+ "_",
364
364
  )}`;
365
365
  const runs = trendData.overall;
366
366
 
@@ -455,7 +455,7 @@ function generateTestHistoryChart(history) {
455
455
  if (!history || history.length === 0)
456
456
  return '<div class="no-data-chart">No data for chart</div>';
457
457
  const validHistory = history.filter(
458
- (h) => h && typeof h.duration === "number" && h.duration >= 0
458
+ (h) => h && typeof h.duration === "number" && h.duration >= 0,
459
459
  );
460
460
  if (validHistory.length === 0)
461
461
  return '<div class="no-data-chart">No valid data for chart</div>';
@@ -465,7 +465,7 @@ function generateTestHistoryChart(history) {
465
465
  .substring(2, 7)}`;
466
466
  const renderFunctionName = `renderTestHistoryChart_${chartId.replace(
467
467
  /-/g,
468
- "_"
468
+ "_",
469
469
  )}`;
470
470
 
471
471
  const seriesDataPoints = validHistory.map((run) => {
@@ -499,12 +499,12 @@ function generateTestHistoryChart(history) {
499
499
  const accentColorRGB = "103, 58, 183"; // Assuming var(--accent-color) is Deep Purple #673ab7
500
500
 
501
501
  const categoriesString = JSON.stringify(
502
- validHistory.map((_, i) => `R${i + 1}`)
502
+ validHistory.map((_, i) => `R${i + 1}`),
503
503
  );
504
504
  const seriesDataPointsString = JSON.stringify(seriesDataPoints);
505
505
 
506
506
  return `
507
- <div id="${chartId}" style="width: 320px; height: 100px;" class="lazy-load-chart" data-render-function-name="${renderFunctionName}">
507
+ <div id="${chartId}" style="width: 100%; max-width: 320px; height: 100px;" class="lazy-load-chart" data-render-function-name="${renderFunctionName}">
508
508
  <div class="no-data-chart">Loading History...</div>
509
509
  </div>
510
510
  <script>
@@ -568,7 +568,7 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
568
568
  }
569
569
  const passedEntry = data.find((d) => d.label === "Passed");
570
570
  const passedPercentage = Math.round(
571
- ((passedEntry ? passedEntry.value : 0) / total) * 100
571
+ ((passedEntry ? passedEntry.value : 0) / total) * 100,
572
572
  );
573
573
 
574
574
  const chartId = `pieChart-${Date.now()}-${Math.random()
@@ -614,13 +614,11 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
614
614
  {
615
615
  chart: {
616
616
  type: 'pie',
617
- width: ${chartWidth},
618
- height: ${
619
- chartHeight - 40
620
- }, // Adjusted height to make space for legend if chartHeight is for the whole wrapper
617
+ width: null,
618
+ height: ${chartHeight - 40},
621
619
  backgroundColor: 'transparent',
622
620
  plotShadow: false,
623
- spacingBottom: 40 // Ensure space for legend
621
+ spacingBottom: 40
624
622
  },
625
623
  title: {
626
624
  text: '${passedPercentage}%',
@@ -670,8 +668,8 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
670
668
  <div class="pie-chart-wrapper" style="align-items: center; max-height: 450px">
671
669
  <div style="display: flex; align-items: start; width: 100%;"><h3>Test Distribution</h3></div>
672
670
  <div id="${chartId}" style="width: ${chartWidth}px; height: ${
673
- chartHeight - 40
674
- }px;"></div>
671
+ chartHeight - 40
672
+ }px;"></div>
675
673
  <script>
676
674
  document.addEventListener('DOMContentLoaded', function() {
677
675
  if (typeof Highcharts !== 'undefined') {
@@ -690,7 +688,7 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
690
688
  </div>
691
689
  `;
692
690
  }
693
- function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
691
+ function generateEnvironmentDashboard(environment) {
694
692
  // Format memory for display
695
693
  const formattedMemory = environment.memory.replace(/(\d+\.\d{2})GB/, "$1 GB");
696
694
 
@@ -699,9 +697,6 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
699
697
  .toString(36)
700
698
  .substring(2, 7)}`;
701
699
 
702
- const cardHeight = Math.floor(dashboardHeight * 0.44);
703
- const cardContentPadding = 16; // px
704
-
705
700
  // Logic for Run Context
706
701
  const runContext = process.env.CI ? "CI" : "Local Test";
707
702
 
@@ -715,166 +710,145 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
715
710
  }
716
711
 
717
712
  .environment-dashboard-wrapper {
718
- --primary-color: #007bff;
719
- --primary-light-color: #e6f2ff;
720
- --secondary-color: #6c757d;
721
- --success-color: #28a745;
722
- --success-light-color: #eaf6ec;
723
- --warning-color: #ffc107;
724
- --warning-light-color: #fff9e6;
725
- --danger-color: #dc3545;
713
+ --primary-color: #6366f1;
714
+ --success-color: #10b981;
715
+ --warning-color: #f59e0b;
726
716
 
727
- --background-color: #ffffff;
728
- --card-background-color: #ffffff;
729
- --text-color: #212529;
730
- --text-color-secondary: #6c757d;
731
- --border-color: #dee2e6;
732
- --border-light-color: #f1f3f5;
733
- --icon-color: #495057;
734
- --chip-background: #e9ecef;
735
- --chip-text: #495057;
736
- --shadow-color: rgba(0, 0, 0, 0.075);
737
-
738
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
739
- background-color: var(--background-color);
740
- border-radius: 12px;
741
- box-shadow: 0 6px 12px var(--shadow-color);
742
- padding: 24px;
743
- color: var(--text-color);
717
+ background-color: white;
718
+ padding: 48px;
719
+ border-bottom: 1px solid #e2e8f0;
720
+ font-family: var(--font-family);
721
+ color: #0f172a;
744
722
  display: grid;
745
- grid-template-columns: 1fr 1fr;
746
- grid-template-rows: auto 1fr;
747
- gap: 20px;
748
- font-size: 14px;
723
+ grid-template-columns: repeat(2, 1fr);
724
+ gap: 32px;
725
+ font-size: 15px;
726
+ transform: translateZ(0);
749
727
  }
750
728
 
751
- /* Mobile Responsiveness */
752
729
  @media (max-width: 768px) {
753
730
  .environment-dashboard-wrapper {
754
- grid-template-columns: 1fr; /* Stack columns on mobile */
755
- grid-template-rows: auto;
756
- padding: 16px;
757
- height: auto !important; /* Allow height to grow */
731
+ grid-template-columns: 1fr;
732
+ padding: 32px 24px;
758
733
  }
759
- .env-card {
760
- height: auto !important; /* Allow cards to grow based on content */
761
- min-height: 200px;
734
+ }
735
+ @media (max-width: 480px) {
736
+ .environment-dashboard-wrapper {
737
+ padding: 24px;
762
738
  }
763
739
  }
764
740
 
765
741
  .env-dashboard-header {
766
742
  grid-column: 1 / -1;
767
- display: flex;
768
- justify-content: space-between;
769
- align-items: center;
770
- border-bottom: 1px solid var(--border-color);
771
- padding-bottom: 16px;
772
- margin-bottom: 8px;
773
- flex-wrap: wrap; /* Allow wrapping header items */
774
- gap: 10px;
743
+ margin-bottom: 24px;
775
744
  }
776
745
 
777
746
  .env-dashboard-title {
778
- font-size: 1.5rem;
779
- font-weight: 600;
780
- color: var(--text-color);
781
- margin: 0;
747
+ font-size: 2em;
748
+ font-weight: 900;
749
+ color: #0f172a;
750
+ letter-spacing: -0.02em;
751
+ margin: 0 0 8px 0;
782
752
  }
783
753
 
784
754
  .env-dashboard-subtitle {
785
- font-size: 0.875rem;
786
- color: var(--text-color-secondary);
787
- margin-top: 4px;
755
+ font-size: 1.05em;
756
+ color: #64748b;
757
+ margin: 0;
758
+ font-weight: 400;
788
759
  }
789
760
 
790
761
  .env-card {
791
- background-color: var(--card-background-color);
792
- border-radius: 8px;
793
- padding: ${cardContentPadding}px;
794
- box-shadow: 0 3px 6px var(--shadow-color);
795
- height: ${cardHeight}px;
762
+ background: white;
763
+ border: none;
764
+ border-left: 4px solid #e2e8f0;
765
+ padding: 28px;
796
766
  display: flex;
797
767
  flex-direction: column;
798
- overflow: hidden;
768
+ gap: 20px;
769
+ transition: all 0.12s ease;
770
+ transform: translateZ(0);
771
+ }
772
+
773
+ .env-card:hover {
774
+ border-left-color: var(--primary-color);
775
+ background: #fafbfc;
799
776
  }
800
777
 
801
778
  .env-card-header {
802
- font-weight: 600;
803
- font-size: 1rem;
804
- margin-bottom: 12px;
805
- color: var(--text-color);
779
+ font-weight: 700;
780
+ font-size: 1.05em;
781
+ color: #0f172a;
806
782
  display: flex;
807
783
  align-items: center;
808
- padding-bottom: 8px;
809
- border-bottom: 1px solid var(--border-light-color);
784
+ gap: 10px;
785
+ text-transform: uppercase;
786
+ letter-spacing: 0.5px;
810
787
  }
811
788
 
812
789
  .env-card-header svg {
813
- margin-right: 10px;
814
- width: 18px;
790
+ width: 18px;
815
791
  height: 18px;
816
- fill: var(--icon-color);
792
+ fill: #6366f1;
817
793
  }
818
794
 
819
795
  .env-card-content {
820
- flex-grow: 1;
821
- overflow-y: auto;
822
- padding-right: 5px;
796
+ display: flex;
797
+ flex-direction: column;
798
+ gap: 16px;
823
799
  }
824
800
 
825
801
  .env-detail-row {
826
802
  display: flex;
827
803
  justify-content: space-between;
828
- align-items: center;
829
- padding: 10px 0;
830
- border-bottom: 1px solid var(--border-light-color);
831
- font-size: 0.875rem;
832
- flex-wrap: wrap; /* Allow details to wrap on very small screens */
833
- gap: 8px;
834
- }
835
-
836
- .env-detail-row:last-child {
837
- border-bottom: none;
804
+ align-items: flex-start;
805
+ gap: 16px;
806
+ font-size: 1em;
807
+ padding: 8px 0;
838
808
  }
839
809
 
840
810
  .env-detail-label {
841
- color: var(--text-color-secondary);
842
- font-weight: 500;
843
- margin-right: 10px;
811
+ color: #64748b;
812
+ font-weight: 600;
813
+ font-size: 0.9em;
814
+ text-transform: uppercase;
815
+ letter-spacing: 0.3px;
816
+ flex-shrink: 0;
844
817
  }
845
818
 
846
819
  .env-detail-value {
847
- color: var(--text-color);
820
+ color: #0f172a;
848
821
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
822
+ font-size: 0.95em;
849
823
  text-align: right;
850
- word-break: break-all;
851
- margin-left: auto; /* Push to right */
824
+ word-break: break-word;
825
+ margin-left: auto;
852
826
  }
853
827
 
854
828
  .env-chip {
855
- display: inline-block;
856
- padding: 4px 10px;
857
- border-radius: 16px;
858
- font-size: 0.75rem;
859
- font-weight: 500;
860
- line-height: 1.2;
861
- background-color: var(--chip-background);
862
- color: var(--chip-text);
829
+ display: inline-flex;
830
+ align-items: center;
831
+ padding: 6px 14px;
832
+ border-radius: 6px;
833
+ font-size: 0.85em;
834
+ font-weight: 700;
835
+ text-transform: uppercase;
836
+ letter-spacing: 0.5px;
863
837
  }
864
838
 
865
839
  .env-chip-primary {
866
- background-color: var(--primary-light-color);
867
- color: var(--primary-color);
840
+ background-color: #ede9fe;
841
+ color: #6366f1;
868
842
  }
869
843
 
870
844
  .env-chip-success {
871
- background-color: var(--success-light-color);
872
- color: var(--success-color);
845
+ background-color: #d1fae5;
846
+ color: #10b981;
873
847
  }
874
848
 
875
849
  .env-chip-warning {
876
- background-color: var(--warning-light-color);
877
- color: var(--warning-color);
850
+ background-color: #fef3c7;
851
+ color: #f59e0b;
878
852
  }
879
853
 
880
854
  .env-cpu-cores {
@@ -920,17 +894,7 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
920
894
  <span class="env-detail-label">CPU Cores</span>
921
895
  <span class="env-detail-value">
922
896
  <div class="env-cpu-cores">
923
- ${Array.from(
924
- { length: Math.max(0, environment.cpu.cores || 0) },
925
- (_, i) =>
926
- `<div class="env-core-indicator ${
927
- i >=
928
- (environment.cpu.cores >= 8 ? 8 : environment.cpu.cores)
929
- ? "inactive"
930
- : ""
931
- }" title="Core ${i + 1}"></div>`
932
- ).join("")}
933
- <span>${environment.cpu.cores || "N/A"} cores</span>
897
+ <span>${environment.cpu.cores || "N/A"} core${environment.cpu.cores !== 1 ? "s" : ""}</span>
934
898
  </div>
935
899
  </span>
936
900
  </div>
@@ -964,8 +928,8 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
964
928
  <div class="env-detail-row">
965
929
  <span class="env-detail-label">Hostname</span>
966
930
  <span class="env-detail-value" title="${environment.host}">${
967
- environment.host
968
- }</span>
931
+ environment.host
932
+ }</span>
969
933
  </div>
970
934
  </div>
971
935
  </div>
@@ -987,10 +951,10 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
987
951
  <div class="env-detail-row">
988
952
  <span class="env-detail-label">Working Dir</span>
989
953
  <span class="env-detail-value" title="${environment.cwd}">${
990
- environment.cwd.length > 25
991
- ? "..." + environment.cwd.slice(-22)
992
- : environment.cwd
993
- }</span>
954
+ environment.cwd.length > 25
955
+ ? "..." + environment.cwd.slice(-22)
956
+ : environment.cwd
957
+ }</span>
994
958
  </div>
995
959
  </div>
996
960
  </div>
@@ -1015,9 +979,9 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
1015
979
  environment.cpu.model.toLowerCase().includes("apple")
1016
980
  ? "Apple Silicon"
1017
981
  : environment.cpu.model.toLowerCase().includes("arm") ||
1018
- environment.cpu.model.toLowerCase().includes("aarch64")
1019
- ? "ARM-based"
1020
- : "x86/Other"
982
+ environment.cpu.model.toLowerCase().includes("aarch64")
983
+ ? "ARM-based"
984
+ : "x86/Other"
1021
985
  }
1022
986
  </span>
1023
987
  </span>
@@ -1089,7 +1053,7 @@ function generateWorkerDistributionChart(results) {
1089
1053
  .substring(2, 7)}`;
1090
1054
  const renderFunctionName = `renderWorkerDistChart_${chartId.replace(
1091
1055
  /-/g,
1092
- "_"
1056
+ "_",
1093
1057
  )}`;
1094
1058
  const modalJsNamespace = `modal_funcs_${chartId.replace(/-/g, "_")}`;
1095
1059
 
@@ -1131,12 +1095,22 @@ function generateWorkerDistributionChart(results) {
1131
1095
  position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
1132
1096
  }
1133
1097
  .worker-modal-close {
1134
- position: absolute; top: 10px; right: 20px;
1135
- font-size: 28px; font-weight: bold; cursor: pointer;
1098
+ position: absolute;
1099
+ top: 15px;
1100
+ right: 25px;
1101
+ font-size: 32px;
1102
+ font-weight: bold;
1103
+ cursor: pointer;
1136
1104
  line-height: 1;
1105
+ z-index: 10;
1106
+ color: #fff;
1107
+ transition: color 0.2s ease;
1108
+ user-select: none;
1109
+ -webkit-user-select: none;
1137
1110
  }
1138
1111
  .worker-modal-close:hover, .worker-modal-close:focus {
1139
- color: var(--text-color, #000);
1112
+ color: #ef4444;
1113
+ transform: scale(1.1);
1140
1114
  }
1141
1115
  #worker-modal-body-${chartId} ul {
1142
1116
  list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
@@ -1163,7 +1137,7 @@ function generateWorkerDistributionChart(results) {
1163
1137
 
1164
1138
  <div id="worker-modal-${chartId}" class="worker-modal-overlay">
1165
1139
  <div class="worker-modal-content">
1166
- <span class="worker-modal-close">×</span>
1140
+ <span class="worker-modal-close" onclick="window.${modalJsNamespace}.close?.()">×</span>
1167
1141
  <h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: #fff"></h3>
1168
1142
  <div id="worker-modal-body-${chartId}"></div>
1169
1143
  </div>
@@ -1210,10 +1184,14 @@ function generateWorkerDistributionChart(results) {
1210
1184
  const closeModal = function() {
1211
1185
  modal.style.display = 'none';
1212
1186
  };
1187
+
1188
+ window.${modalJsNamespace}.close = closeModal;
1213
1189
 
1214
- closeModalBtn.onclick = closeModal;
1190
+ if (closeModalBtn) {
1191
+ closeModalBtn.onclick = closeModal;
1192
+ }
1193
+
1215
1194
  modal.onclick = function(event) {
1216
- // Close if clicked on the dark overlay background
1217
1195
  if (event.target == modal) {
1218
1196
  closeModal();
1219
1197
  }
@@ -1329,7 +1307,7 @@ function generateTestHistoryContent(trendData) {
1329
1307
  ? `test run ${overallRun.runId}`
1330
1308
  : `test run ${index + 1}`;
1331
1309
  const testRunForThisOverallRun = trendData.testRuns[runKey]?.find(
1332
- (t) => t && t.testName === fullTestName
1310
+ (t) => t && t.testName === fullTestName,
1333
1311
  );
1334
1312
  if (testRunForThisOverallRun) {
1335
1313
  history.push({
@@ -1369,12 +1347,12 @@ function generateTestHistoryContent(trendData) {
1369
1347
  : { status: "unknown" };
1370
1348
  return `
1371
1349
  <div class="test-history-card" data-test-name="${sanitizeHTML(
1372
- test.testTitle.toLowerCase()
1350
+ test.testTitle.toLowerCase(),
1373
1351
  )}" data-latest-status="${latestRun.status}">
1374
1352
  <div class="test-history-header">
1375
1353
  <p title="${sanitizeHTML(test.testTitle)}">${capitalize(
1376
- sanitizeHTML(test.testTitle)
1377
- )}</p>
1354
+ sanitizeHTML(test.testTitle),
1355
+ )}</p>
1378
1356
  <span class="status-badge ${getStatusClass(latestRun.status)}">
1379
1357
  ${String(latestRun.status).toUpperCase()}
1380
1358
  </span>
@@ -1396,11 +1374,11 @@ function generateTestHistoryContent(trendData) {
1396
1374
  <tr>
1397
1375
  <td>${run.runId}</td>
1398
1376
  <td><span class="status-badge-small ${getStatusClass(
1399
- run.status
1377
+ run.status,
1400
1378
  )}">${String(run.status).toUpperCase()}</span></td>
1401
1379
  <td>${formatDuration(run.duration)}</td>
1402
1380
  <td>${formatDate(run.timestamp)}</td>
1403
- </tr>`
1381
+ </tr>`,
1404
1382
  )
1405
1383
  .join("")}
1406
1384
  </tbody>
@@ -1500,9 +1478,9 @@ function generateSuitesWidget(suitesData) {
1500
1478
  <span class="summary-badge">${
1501
1479
  suitesData.length
1502
1480
  } suites • ${suitesData.reduce(
1503
- (sum, suite) => sum + suite.count,
1504
- 0
1505
- )} tests</span>
1481
+ (sum, suite) => sum + suite.count,
1482
+ 0,
1483
+ )} tests</span>
1506
1484
  </div>
1507
1485
 
1508
1486
  <div class="suites-grid-container" style="flex-grow: 1; overflow-y: auto; padding-right: 5px;">
@@ -1512,16 +1490,14 @@ function generateSuitesWidget(suitesData) {
1512
1490
  (suite) => `
1513
1491
  <div class="suite-card status-${suite.statusOverall}">
1514
1492
  <div class="suite-card-header">
1515
- <h3 class="suite-name" title="${sanitizeHTML(
1516
- suite.name
1517
- )} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(
1518
- suite.name
1519
- )}</h3>
1520
- </div>
1521
- <div>🖥️ <span class="browser-tag">${sanitizeHTML(
1522
- suite.browser
1523
- )}</span></div>
1524
- <div class="suite-card-body">
1493
+ <h3 class="suite-name" title="${sanitizeHTML(
1494
+ suite.name,
1495
+ )} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
1496
+ </div>
1497
+ <div style="margin-bottom: 12px;"><span class="browser-tag" title="🌐 ${sanitizeHTML(suite.browser)}">🌐 ${sanitizeHTML(
1498
+ suite.browser,
1499
+ )}</span></div>
1500
+ <div class="suite-card-body">
1525
1501
  <span class="test-count">${suite.count} test${
1526
1502
  suite.count !== 1 ? "s" : ""
1527
1503
  }</span>
@@ -1543,7 +1519,7 @@ function generateSuitesWidget(suitesData) {
1543
1519
  }
1544
1520
  </div>
1545
1521
  </div>
1546
- </div>`
1522
+ </div>`,
1547
1523
  )
1548
1524
  .join("")}
1549
1525
  </div>
@@ -1565,12 +1541,11 @@ function getAttachmentIcon(contentType) {
1565
1541
  }
1566
1542
  function generateAIFailureAnalyzerTab(results) {
1567
1543
  const failedTests = (results || []).filter(
1568
- (test) => test.status === "failed"
1544
+ (test) => test.status === "failed",
1569
1545
  );
1570
1546
 
1571
1547
  if (failedTests.length === 0) {
1572
1548
  return `
1573
- <h2 class="tab-main-title">AI Failure Analysis</h2>
1574
1549
  <div class="no-data">Congratulations! No failed tests in this run.</div>
1575
1550
  `;
1576
1551
  }
@@ -1579,7 +1554,6 @@ function generateAIFailureAnalyzerTab(results) {
1579
1554
  const btoa = (str) => Buffer.from(str).toString("base64");
1580
1555
 
1581
1556
  return `
1582
- <h2 class="tab-main-title">AI Failure Analysis</h2>
1583
1557
  <div class="ai-analyzer-stats">
1584
1558
  <div class="stat-item">
1585
1559
  <span class="stat-number">${failedTests.length}</span>
@@ -1594,7 +1568,7 @@ function generateAIFailureAnalyzerTab(results) {
1594
1568
  <div class="stat-item">
1595
1569
  <span class="stat-number">${Math.round(
1596
1570
  failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) /
1597
- 1000
1571
+ 1000,
1598
1572
  )}s</span>
1599
1573
  <span class="stat-label">Total Duration</span>
1600
1574
  </div>
@@ -1617,14 +1591,14 @@ function generateAIFailureAnalyzerTab(results) {
1617
1591
  <div class="failure-header">
1618
1592
  <div class="failure-main-info">
1619
1593
  <h3 class="failure-title" title="${sanitizeHTML(
1620
- test.name
1594
+ test.name,
1621
1595
  )}">${sanitizeHTML(testTitle)}</h3>
1622
1596
  <div class="failure-meta">
1623
1597
  <span class="browser-indicator">${sanitizeHTML(
1624
- test.browser || "unknown"
1598
+ test.browser || "unknown",
1625
1599
  )}</span>
1626
1600
  <span class="duration-indicator">${formatDuration(
1627
- test.duration
1601
+ test.duration,
1628
1602
  )}</span>
1629
1603
  </div>
1630
1604
  </div>
@@ -1639,7 +1613,7 @@ function generateAIFailureAnalyzerTab(results) {
1639
1613
  </div>
1640
1614
  <div class="failure-error-preview">
1641
1615
  <div class="error-snippet">${formatPlaywrightError(
1642
- truncatedError
1616
+ truncatedError,
1643
1617
  )}</div>
1644
1618
  <button class="expand-error-btn" onclick="toggleErrorDetails(this)">
1645
1619
  <span class="expand-text">Show Full Error</span>
@@ -1649,10 +1623,16 @@ function generateAIFailureAnalyzerTab(results) {
1649
1623
  <div class="full-error-details" style="display: none;">
1650
1624
  <div class="full-error-content">
1651
1625
  ${formatPlaywrightError(
1652
- test.errorMessage || "No detailed error message available"
1626
+ test.errorMessage ||
1627
+ "No detailed error message available",
1653
1628
  )}
1654
1629
  </div>
1655
1630
  </div>
1631
+ <div class="ai-suggestion-container" style="display: none;">
1632
+ <div class="ai-suggestion-content">
1633
+ <!-- AI suggestion will be injected here -->
1634
+ </div>
1635
+ </div>
1656
1636
  </div>
1657
1637
  `;
1658
1638
  })
@@ -1895,7 +1875,25 @@ function generateDescribeDurationChart(results) {
1895
1875
  series: [{
1896
1876
  name: 'Duration',
1897
1877
  data: ${dataStr},
1898
- color: 'var(--accent-color-alt)',
1878
+ colorByPoint: true,
1879
+ colors: [
1880
+ '#9333ea',
1881
+ '#6366f1',
1882
+ '#0ea5e9',
1883
+ '#10b981',
1884
+ '#84cc16',
1885
+ '#eab308',
1886
+ '#f97316',
1887
+ '#ef4444',
1888
+ '#ec4899',
1889
+ '#8b5cf6',
1890
+ '#06b6d4',
1891
+ '#14b8a6',
1892
+ '#a3e635',
1893
+ '#fbbf24',
1894
+ '#fb923c',
1895
+ '#f87171'
1896
+ ],
1899
1897
  }],
1900
1898
  credits: { enabled: false }
1901
1899
  });
@@ -2067,12 +2065,41 @@ function generateHTML(reportData, trendData = null) {
2067
2065
  const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
2068
2066
  const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
2069
2067
  const skipPercentage = Math.round(
2070
- ((runSummary.skipped || 0) / totalTestsOr1) * 100
2068
+ ((runSummary.skipped || 0) / totalTestsOr1) * 100,
2071
2069
  );
2072
2070
  const avgTestDuration =
2073
2071
  runSummary.totalTests > 0
2074
2072
  ? formatDuration(runSummary.duration / runSummary.totalTests)
2075
2073
  : "0.0s";
2074
+
2075
+ // Calculate retry statistics
2076
+ const totalRetried = (results || []).reduce((acc, test) => {
2077
+ if (test.retries && test.retries > 0) {
2078
+ return acc + 1;
2079
+ }
2080
+ return acc;
2081
+ }, 0);
2082
+
2083
+ // Calculate browser distribution
2084
+ const browserStats = (results || []).reduce((acc, test) => {
2085
+ let browserName = "unknown";
2086
+ if (test.browser) {
2087
+ // Extract browser name from strings like "Chrome v143 on Windows 10"
2088
+ const match = test.browser.match(/^(\w+)/);
2089
+ browserName = match ? match[1] : test.browser;
2090
+ }
2091
+ acc[browserName] = (acc[browserName] || 0) + 1;
2092
+ return acc;
2093
+ }, {});
2094
+
2095
+ const totalTests = runSummary.totalTests || 1;
2096
+ const browserBreakdown = Object.entries(browserStats)
2097
+ .map(([browser, count]) => ({
2098
+ browser,
2099
+ count,
2100
+ percentage: Math.round((count / totalTests) * 100),
2101
+ }))
2102
+ .sort((a, b) => b.count - a.count);
2076
2103
  function generateTestCasesHTML() {
2077
2104
  if (!results || results.length === 0)
2078
2105
  return '<div class="no-tests">No test results found in this run.</div>';
@@ -2082,28 +2109,9 @@ function generateHTML(reportData, trendData = null) {
2082
2109
  const testFileParts = test.name.split(" > ");
2083
2110
  const testTitle =
2084
2111
  testFileParts[testFileParts.length - 1] || "Unnamed Test";
2085
- // --- NEW: Severity Logic ---
2112
+ // --- Simplified Severity Badge ---
2086
2113
  const severity = test.severity || "Medium";
2087
- const getSeverityColor = (level) => {
2088
- switch (level) {
2089
- case "Minor":
2090
- return "#006064";
2091
- case "Low":
2092
- return "#FFA07A";
2093
- case "Medium":
2094
- return "#577A11";
2095
- case "High":
2096
- return "#B71C1C";
2097
- case "Critical":
2098
- return "#64158A";
2099
- default:
2100
- return "#577A11";
2101
- }
2102
- };
2103
- const severityColor = getSeverityColor(severity);
2104
- // We reuse 'status-badge' class for size/font consistency, but override background color
2105
- const severityBadge = `<span class="status-badge" style="background-color: ${severityColor}; margin-right: 8px;">${severity}</span>`;
2106
- // ---------------------------
2114
+ const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
2107
2115
  const generateStepsHTML = (steps, depth = 0) => {
2108
2116
  if (!steps || steps.length === 0)
2109
2117
  return "<div class='no-steps'>No steps recorded for this test.</div>";
@@ -2120,17 +2128,17 @@ function generateHTML(reportData, trendData = null) {
2120
2128
  <div class="step-header ${stepClass}" role="button" aria-expanded="false">
2121
2129
  <span class="step-icon">${getStatusIcon(step.status)}</span>
2122
2130
  <span class="step-title">${sanitizeHTML(
2123
- step.title
2131
+ step.title,
2124
2132
  )}${hookIndicator}</span>
2125
2133
  <span class="step-duration">${formatDuration(
2126
- step.duration
2134
+ step.duration,
2127
2135
  )}</span>
2128
2136
  </div>
2129
2137
  <div class="step-details" style="display: none;">
2130
2138
  ${
2131
2139
  step.codeLocation
2132
2140
  ? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
2133
- step.codeLocation
2141
+ step.codeLocation,
2134
2142
  )}</div>`
2135
2143
  : ""
2136
2144
  }
@@ -2140,7 +2148,7 @@ function generateHTML(reportData, trendData = null) {
2140
2148
  ${
2141
2149
  step.stackTrace
2142
2150
  ? `<div class="stack-trace">${formatPlaywrightError(
2143
- step.stackTrace
2151
+ step.stackTrace,
2144
2152
  )}</div>`
2145
2153
  : ""
2146
2154
  }
@@ -2170,7 +2178,7 @@ function generateHTML(reportData, trendData = null) {
2170
2178
  hasNestedSteps
2171
2179
  ? `<div class="nested-steps">${generateStepsHTML(
2172
2180
  step.steps,
2173
- depth + 1
2181
+ depth + 1,
2174
2182
  )}</div>`
2175
2183
  : ""
2176
2184
  }
@@ -2184,15 +2192,12 @@ function generateHTML(reportData, trendData = null) {
2184
2192
  <div class="test-case" data-status="${
2185
2193
  test.status
2186
2194
  }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
2187
- .join(",")
2188
- .toLowerCase()}">
2195
+ .join(",")
2196
+ .toLowerCase()}">
2189
2197
  <div class="test-case-header" role="button" aria-expanded="false">
2190
2198
  <div class="test-case-summary">
2191
- <span class="status-badge ${getStatusClass(test.status)}">${String(
2192
- test.status
2193
- ).toUpperCase()}</span>
2194
2199
  <span class="test-case-title" title="${sanitizeHTML(
2195
- test.name
2200
+ test.name,
2196
2201
  )}">${sanitizeHTML(testTitle)}</span>
2197
2202
  <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2198
2203
  </div>
@@ -2205,6 +2210,11 @@ function generateHTML(reportData, trendData = null) {
2205
2210
  .join(" ")
2206
2211
  : ""
2207
2212
  }
2213
+ </div>
2214
+ <div class="test-case-status-duration">
2215
+ <span class="status-badge ${getStatusClass(test.status)}">${String(
2216
+ test.status,
2217
+ ).toUpperCase()}</span>
2208
2218
  <span class="test-duration">${formatDuration(test.duration)}</span>
2209
2219
  </div>
2210
2220
  </div>
@@ -2224,14 +2234,14 @@ function generateHTML(reportData, trendData = null) {
2224
2234
  const descriptionHtml =
2225
2235
  isIssueOrBug && descriptionText.match(/^[A-Z]+-\d+$/)
2226
2236
  ? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
2227
- descriptionText
2237
+ descriptionText,
2228
2238
  )}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
2229
- descriptionText
2239
+ descriptionText,
2230
2240
  )}</a>`
2231
2241
  : sanitizeHTML(descriptionText);
2232
2242
  const locationText = annotation.location
2233
2243
  ? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
2234
- annotation.location.file
2244
+ annotation.location.file,
2235
2245
  )}:${annotation.location.line}:${
2236
2246
  annotation.location.column
2237
2247
  }</div>`
@@ -2253,14 +2263,14 @@ function generateHTML(reportData, trendData = null) {
2253
2263
  : ""
2254
2264
  }
2255
2265
  <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
2256
- test.workerId
2266
+ test.workerId,
2257
2267
  )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
2258
- test.totalWorkers
2259
- )}]</p>
2268
+ test.totalWorkers,
2269
+ )}]</p>
2260
2270
  ${
2261
2271
  test.errorMessage
2262
2272
  ? `<div class="test-error-summary">${formatPlaywrightError(
2263
- test.errorMessage
2273
+ test.errorMessage,
2264
2274
  )}
2265
2275
  <button
2266
2276
  class="copy-error-btn"
@@ -2287,7 +2297,7 @@ function generateHTML(reportData, trendData = null) {
2287
2297
  ${
2288
2298
  test.snippet
2289
2299
  ? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
2290
- test.snippet
2300
+ test.snippet,
2291
2301
  )}</code></pre></div>`
2292
2302
  : ""
2293
2303
  }
@@ -2299,19 +2309,21 @@ function generateHTML(reportData, trendData = null) {
2299
2309
  const logId = `stdout-log-${test.id || index}`;
2300
2310
  return `<div class="console-output-section">
2301
2311
  <h4>Console Output (stdout)
2302
- <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy Console</button>
2312
+ <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button>
2303
2313
  </h4>
2304
2314
  <div class="log-wrapper">
2305
2315
  <pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
2306
- test.stdout.map((line) => sanitizeHTML(line)).join("\n")
2307
- )}</pre>
2316
+ test.stdout
2317
+ .map((line) => sanitizeHTML(line))
2318
+ .join("\n"),
2319
+ )}</pre>
2308
2320
  </div>
2309
2321
  </div>`;
2310
2322
  })()}
2311
2323
  ${
2312
2324
  test.stderr && test.stderr.length > 0
2313
2325
  ? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
2314
- test.stderr.map((line) => sanitizeHTML(line)).join("\n")
2326
+ test.stderr.map((line) => sanitizeHTML(line)).join("\n"),
2315
2327
  )}</pre></div>`
2316
2328
  : ""
2317
2329
  }
@@ -2331,15 +2343,15 @@ function generateHTML(reportData, trendData = null) {
2331
2343
  <div class="attachment-info">
2332
2344
  <div class="trace-actions">
2333
2345
  <a href="${fixPath(
2334
- screenshot
2346
+ screenshot,
2335
2347
  )}" target="_blank" class="view-full">View Full Image</a>
2336
2348
  <a href="${fixPath(
2337
- screenshot
2349
+ screenshot,
2338
2350
  )}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
2339
2351
  </div>
2340
2352
  </div>
2341
2353
  </div>
2342
- `
2354
+ `,
2343
2355
  )
2344
2356
  .join("")}
2345
2357
  </div>
@@ -2369,14 +2381,14 @@ function generateHTML(reportData, trendData = null) {
2369
2381
  index + 1
2370
2382
  }">
2371
2383
  <source src="${sanitizeHTML(
2372
- fixedVideoUrl
2384
+ fixedVideoUrl,
2373
2385
  )}" type="${mimeType}">
2374
2386
  Your browser does not support the video tag.
2375
2387
  </video>
2376
2388
  <div class="attachment-info">
2377
2389
  <div class="trace-actions">
2378
2390
  <a href="${sanitizeHTML(
2379
- fixedVideoUrl
2391
+ fixedVideoUrl,
2380
2392
  )}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
2381
2393
  </div>
2382
2394
  </div>
@@ -2395,16 +2407,16 @@ function generateHTML(reportData, trendData = null) {
2395
2407
  <div class="trace-preview">
2396
2408
  <span class="trace-icon">📄</span>
2397
2409
  <span class="trace-name">${sanitizeHTML(
2398
- path.basename(test.tracePath)
2410
+ path.basename(test.tracePath),
2399
2411
  )}</span>
2400
2412
  </div>
2401
2413
  <div class="attachment-info">
2402
2414
  <div class="trace-actions">
2403
2415
  <a href="${sanitizeHTML(
2404
- fixPath(test.tracePath)
2416
+ fixPath(test.tracePath),
2405
2417
  )}" target="_blank" download="${sanitizeHTML(
2406
- path.basename(test.tracePath)
2407
- )}" class="download-trace">Download Trace</a>
2418
+ path.basename(test.tracePath),
2419
+ )}" class="download-trace">Download Trace</a>
2408
2420
  </div>
2409
2421
  </div>
2410
2422
  </div>
@@ -2424,30 +2436,30 @@ function generateHTML(reportData, trendData = null) {
2424
2436
  (attachment) => `
2425
2437
  <div class="attachment-item generic-attachment">
2426
2438
  <div class="attachment-icon">${getAttachmentIcon(
2427
- attachment.contentType
2439
+ attachment.contentType,
2428
2440
  )}</div>
2429
2441
  <div class="attachment-caption">
2430
2442
  <span class="attachment-name" title="${sanitizeHTML(
2431
- attachment.name
2443
+ attachment.name,
2432
2444
  )}">${sanitizeHTML(attachment.name)}</span>
2433
2445
  <span class="attachment-type">${sanitizeHTML(
2434
- attachment.contentType
2446
+ attachment.contentType,
2435
2447
  )}</span>
2436
2448
  </div>
2437
2449
  <div class="attachment-info">
2438
2450
  <div class="trace-actions">
2439
2451
  <a href="${sanitizeHTML(
2440
- fixPath(attachment.path)
2452
+ fixPath(attachment.path),
2441
2453
  )}" target="_blank" class="view-full">View</a>
2442
2454
  <a href="${sanitizeHTML(
2443
- fixPath(attachment.path)
2455
+ fixPath(attachment.path),
2444
2456
  )}" target="_blank" download="${sanitizeHTML(
2445
- attachment.name
2446
- )}" class="download-trace">Download</a>
2457
+ attachment.name,
2458
+ )}" class="download-trace">Download</a>
2447
2459
  </div>
2448
2460
  </div>
2449
2461
  </div>
2450
- `
2462
+ `,
2451
2463
  )
2452
2464
  .join("")}
2453
2465
  </div>
@@ -2458,7 +2470,7 @@ function generateHTML(reportData, trendData = null) {
2458
2470
  ${
2459
2471
  test.codeSnippet
2460
2472
  ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
2461
- sanitizeHTML(test.codeSnippet)
2473
+ sanitizeHTML(test.codeSnippet),
2462
2474
  )}</code></pre></div>`
2463
2475
  : ""
2464
2476
  }
@@ -2475,17 +2487,51 @@ function generateHTML(reportData, trendData = null) {
2475
2487
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2476
2488
  <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2477
2489
  <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2490
+ <!-- Preconnect to external domains -->
2491
+ <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
2492
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2493
+ <link rel="preconnect" href="https://code.highcharts.com">
2494
+
2495
+ <!-- Preload critical font -->
2496
+ <link rel="preload" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
2497
+ <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
2498
+
2478
2499
  <script src="https://code.highcharts.com/highcharts.js" defer></script>
2479
2500
  <title>Pulse Report</title>
2480
2501
  <style>
2481
2502
  :root {
2482
- --primary-color: #3f51b5; --secondary-color: #ff4081; --accent-color: #673ab7; --accent-color-alt: #FF9800;
2483
- --success-color: #4CAF50; --danger-color: #F44336; --warning-color: #FFC107; --info-color: #2196F3;
2484
- --light-gray-color: #f5f5f5; --medium-gray-color: #e0e0e0; --dark-gray-color: #757575;
2485
- --text-color: #333; --text-color-secondary: #555; --border-color: #ddd; --background-color: #f8f9fa;
2486
- --card-background-color: #fff; --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
2487
- --border-radius: 8px; --box-shadow: 0 5px 15px rgba(0,0,0,0.08); --box-shadow-light: 0 3px 8px rgba(0,0,0,0.05); --box-shadow-inset: inset 0 1px 3px rgba(0,0,0,0.07);
2503
+ --primary-color: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
2504
+ --secondary-color: #8b5cf6; --secondary-dark: #7c3aed; --secondary-light: #a78bfa;
2505
+ --accent-color: #ec4899; --accent-alt: #06b6d4;
2506
+ --success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
2507
+ --danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
2508
+ --warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
2509
+ --info-color: #3b82f6;
2510
+ --neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
2511
+ --neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
2512
+ --neutral-800: #262626; --neutral-900: #171717;
2513
+ --text-primary: #0f172a; --text-secondary: #475569; --text-tertiary: #94a3b8;
2514
+ --bg-primary: #ffffff; --bg-secondary: #f8fafc; --bg-tertiary: #f1f5f9;
2515
+ --border-light: #e2e8f0; --border-medium: #cbd5e1; --border-dark: #94a3b8;
2516
+ --font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2517
+ --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-2xl: 24px;
2518
+ --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
2519
+ --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
2520
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
2521
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
2522
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
2523
+ --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
2524
+ --glow-primary: 0 0 20px rgba(99, 102, 241, 0.4), 0 0 40px rgba(99, 102, 241, 0.2);
2525
+ --glow-success: 0 0 20px rgba(16, 185, 129, 0.4), 0 0 40px rgba(16, 185, 129, 0.2);
2526
+ --glow-danger: 0 0 20px rgba(239, 68, 68, 0.4), 0 0 40px rgba(239, 68, 68, 0.2);
2488
2527
  }
2528
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2529
+ ::selection { background: var(--primary-color); color: white; }
2530
+ ::-webkit-scrollbar { width: 0; height: 0; display: none; }
2531
+ ::-webkit-scrollbar-track { display: none; }
2532
+ ::-webkit-scrollbar-thumb { display: none; }
2533
+ ::-webkit-scrollbar-thumb:hover { display: none; }
2534
+ * { scrollbar-width: none; -ms-overflow-style: none; }
2489
2535
  .trend-chart-container, .test-history-trend div[id^="testHistoryChart-"] { min-height: 100px; }
2490
2536
  .lazy-load-chart .no-data, .lazy-load-chart .no-data-chart { display: flex; align-items: center; justify-content: center; height: 100%; font-style: italic; color: var(--dark-gray-color); }
2491
2537
  .highcharts-background { fill: transparent; }
@@ -2493,34 +2539,659 @@ function generateHTML(reportData, trendData = null) {
2493
2539
  .highcharts-axis-labels text, .highcharts-legend-item text { fill: var(--text-color-secondary) !important; font-size: 12px !important; }
2494
2540
  .highcharts-axis-title { fill: var(--text-color) !important; }
2495
2541
  .highcharts-tooltip > span { background-color: rgba(10,10,10,0.92) !important; border-color: rgba(10,10,10,0.92) !important; color: #f5f5f5 !important; padding: 10px !important; border-radius: 6px !important; }
2496
- body { font-family: var(--font-family); margin: 0; background-color: var(--background-color); color: var(--text-color); line-height: 1.65; font-size: 16px; }
2497
- .container { padding: 30px; border-radius: var(--border-radius); box-shadow: var(--box-shadow); background: repeating-linear-gradient(#f1f8e9, #f9fbe7, #fce4ec); }
2498
- .header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; padding-bottom: 25px; border-bottom: 1px solid var(--border-color); margin-bottom: 25px; }
2499
- .header-title { display: flex; align-items: center; gap: 15px; }
2500
- .header h1 { margin: 0; font-size: 1.85em; font-weight: 600; color: var(--primary-color); }
2501
- #report-logo { height: 40px; width: 55px; }
2502
- .run-info { font-size: 0.9em; text-align: right; color: var(--text-color-secondary); line-height:1.5;}
2503
- .run-info strong { color: var(--text-color); }
2504
- .tabs { display: flex; border-bottom: 2px solid var(--border-color); margin-bottom: 30px; overflow-x: auto; }
2505
- .tab-button { padding: 15px 25px; background: none; border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 1.1em; font-weight: 600; color: black; transition: color 0.2s ease, border-color 0.2s ease; white-space: nowrap; }
2506
- .tab-button:hover { color: var(--accent-color); }
2507
- .tab-button.active { color: var(--primary-color); border-bottom-color: var(--primary-color); }
2508
- .tab-content { display: none; animation: fadeIn 0.4s ease-out; }
2509
- .tab-content.active { display: block; }
2542
+ html {
2543
+ overflow-x: hidden;
2544
+ }
2545
+ body {
2546
+ font-family: var(--font-family);
2547
+ margin: 0;
2548
+ background: #fafbfc;
2549
+ color: var(--text-primary);
2550
+ line-height: 1.6;
2551
+ font-size: 15px;
2552
+ min-height: 100vh;
2553
+ overflow-x: hidden;
2554
+ -webkit-font-smoothing: antialiased;
2555
+ -moz-osx-font-smoothing: grayscale;
2556
+ text-rendering: optimizeLegibility;
2557
+ }
2558
+ * {
2559
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2560
+ will-change: transform, opacity;
2561
+ }
2562
+ *:not(input):not(select):not(textarea):not(button) {
2563
+ transition-duration: 0.15s;
2564
+ }
2565
+ .container {
2566
+ padding: 0;
2567
+ margin: 0;
2568
+ max-width: 100%;
2569
+ overflow-x: hidden;
2570
+ }
2571
+ .header {
2572
+ display: flex;
2573
+ justify-content: space-between;
2574
+ align-items: center;
2575
+ padding: 32px 40px 28px 40px;
2576
+ border-bottom: 1px solid #e2e8f0;
2577
+ background: rgba(255, 255, 255, 0.95);
2578
+ }
2579
+ .header-title {
2580
+ display: flex;
2581
+ align-items: center;
2582
+ gap: 20px;
2583
+ }
2584
+ .header h1 {
2585
+ margin: 0;
2586
+ font-size: 2.5em;
2587
+ font-weight: 900;
2588
+ color: #0f172a;
2589
+ line-height: 1;
2590
+ letter-spacing: -0.03em;
2591
+ }
2592
+ #report-logo {
2593
+ height: 60px;
2594
+ }
2595
+ .run-info {
2596
+ display: flex;
2597
+ gap: 16px;
2598
+ align-items: stretch;
2599
+ background: #ffffff;
2600
+ border-radius: 12px;
2601
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
2602
+ border: 1px solid #e2e8f0;
2603
+ overflow: hidden;
2604
+ }
2605
+ .run-info-item {
2606
+ display: flex;
2607
+ flex-direction: column;
2608
+ gap: 8px;
2609
+ padding: 16px 28px;
2610
+ position: relative;
2611
+ flex: 1;
2612
+ min-width: 0;
2613
+ max-width: 100%;
2614
+ overflow-wrap: break-word;
2615
+ word-break: break-word;
2616
+ }
2617
+ .run-info-item:not(:last-child)::after {
2618
+ content: '';
2619
+ position: absolute;
2620
+ right: 0;
2621
+ top: 20%;
2622
+ bottom: 20%;
2623
+ width: 1px;
2624
+ background: linear-gradient(to bottom, transparent, #e2e8f0 20%, #e2e8f0 80%, transparent);
2625
+ }
2626
+ .run-info-item:first-child {
2627
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
2628
+ }
2629
+ .run-info-item:last-child {
2630
+ background: linear-gradient(135deg, #ddd6fe 0%, #c4b5fd 100%);
2631
+ }
2632
+ .run-info strong {
2633
+ display: flex;
2634
+ align-items: center;
2635
+ gap: 6px;
2636
+ font-size: 0.7em;
2637
+ text-transform: uppercase;
2638
+ letter-spacing: 1px;
2639
+ color: #64748b;
2640
+ margin: 0;
2641
+ font-weight: 700;
2642
+ }
2643
+ .run-info strong::before {
2644
+ content: '';
2645
+ width: 8px;
2646
+ height: 8px;
2647
+ border-radius: 50%;
2648
+ background: currentColor;
2649
+ opacity: 0.6;
2650
+ }
2651
+ .run-info-item:first-child strong {
2652
+ color: #92400e;
2653
+ }
2654
+ .run-info-item:last-child strong {
2655
+ color: #5b21b6;
2656
+ }
2657
+ .run-info span {
2658
+ font-weight: 800;
2659
+ color: #0f172a;
2660
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
2661
+ letter-spacing: -0.02em;
2662
+ line-height: 1.2;
2663
+ white-space: nowrap;
2664
+ }
2665
+ .tabs {
2666
+ display: flex;
2667
+ background: #0f172a;
2668
+ padding: 0;
2669
+ margin: 0;
2670
+ position: sticky;
2671
+ top: 0;
2672
+ z-index: 100;
2673
+ overflow-x: auto;
2674
+ -webkit-overflow-scrolling: touch;
2675
+ max-width: 100vw;
2676
+ width: 100%;
2677
+ }
2678
+ .tab-button {
2679
+ flex: 1 1 auto;
2680
+ padding: 24px 20px;
2681
+ background: transparent;
2682
+ border: none;
2683
+ cursor: pointer;
2684
+ font-size: 0.85em;
2685
+ font-weight: 700;
2686
+ color: #64748b;
2687
+ transition: all 0.2s ease;
2688
+ white-space: nowrap;
2689
+ text-transform: uppercase;
2690
+ letter-spacing: 1.2px;
2691
+ border-right: 1px solid #1e293b;
2692
+ min-width: 0;
2693
+ }
2694
+ .tab-button:last-child { border-right: none; }
2695
+ .tab-button:hover {
2696
+ background: #1e293b;
2697
+ color: #ffffff;
2698
+ }
2699
+ .tab-button.active {
2700
+ background: #6366f1;
2701
+ color: #ffffff;
2702
+ }
2703
+ .tab-content {
2704
+ display: none;
2705
+ animation: fadeIn 0.4s ease-out;
2706
+ overflow-x: hidden;
2707
+ max-width: 100%;
2708
+ }
2709
+ .tab-content.active {
2710
+ display: block;
2711
+ }
2510
2712
  @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
2511
- .dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 22px; margin-bottom: 35px; }
2512
- .summary-card { background-color: var(--card-background-color); border: 1px solid var(--border-color); border-radius: var(--border-radius); padding: 22px; text-align: center; box-shadow: var(--box-shadow-light); transition: transform 0.2s ease, box-shadow 0.2s ease; }
2513
- .summary-card:hover { transform: translateY(-5px); box-shadow: var(--box-shadow); }
2514
- .summary-card h3 { margin: 0 0 10px; font-size: 1.05em; font-weight: 500; color: var(--text-color-secondary); }
2515
- .summary-card .value { font-size: 2.4em; font-weight: 600; margin-bottom: 8px; }
2516
- .summary-card .trend-percentage { font-size: 1em; color: var(--dark-gray-color); }
2517
- .status-passed .value, .stat-passed svg { color: var(--success-color); }
2518
- .status-failed .value, .stat-failed svg { color: var(--danger-color); }
2519
- .status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
2713
+
2714
+ @media (max-width: 1200px) {
2715
+ .trend-charts-row {
2716
+ grid-template-columns: 1fr;
2717
+ }
2718
+ .dashboard-bottom-row {
2719
+ grid-template-columns: 1fr;
2720
+ }
2721
+ }
2722
+
2723
+ .dashboard-grid {
2724
+ display: grid;
2725
+ grid-template-columns: repeat(4, 1fr);
2726
+ gap: 0;
2727
+ margin: 0 0 40px 0;
2728
+ }
2729
+ .browser-breakdown {
2730
+ display: flex;
2731
+ flex-direction: column;
2732
+ gap: 4px;
2733
+ margin-top: 6px;
2734
+ max-height: 150px;
2735
+ overflow-y: auto;
2736
+ padding-right: 4px;
2737
+ scrollbar-width: thin;
2738
+ scrollbar-color: #cbd5e1 #f1f5f9;
2739
+ }
2740
+ .browser-breakdown::-webkit-scrollbar {
2741
+ width: 6px;
2742
+ display: block;
2743
+ }
2744
+ .browser-breakdown::-webkit-scrollbar-track {
2745
+ background: #f1f5f9;
2746
+ border-radius: 3px;
2747
+ display: block;
2748
+ }
2749
+ .browser-breakdown::-webkit-scrollbar-thumb {
2750
+ background: #cbd5e1;
2751
+ border-radius: 3px;
2752
+ display: block;
2753
+ }
2754
+ .browser-breakdown::-webkit-scrollbar-thumb:hover {
2755
+ background: #94a3b8;
2756
+ display: block;
2757
+ }
2758
+ .browser-item {
2759
+ display: flex;
2760
+ justify-content: space-between;
2761
+ align-items: center;
2762
+ font-size: 0.95em;
2763
+ }
2764
+ .browser-name {
2765
+ font-weight: 700;
2766
+ color: #0f172a;
2767
+ text-transform: capitalize;
2768
+ font-size: 1.05em;
2769
+ }
2770
+ .browser-stats {
2771
+ color: #64748b;
2772
+ font-weight: 700;
2773
+ font-size: 0.95em;
2774
+ }
2775
+ .summary-card {
2776
+ padding: 36px 32px;
2777
+ text-align: left;
2778
+ background: rgba(255, 255, 255, 0.95);
2779
+ border: 1px solid #e2e8f0;
2780
+ transition: background 0.2s ease;
2781
+ border-right: 1px solid #e2e8f0;
2782
+ border-bottom: 1px solid #e2e8f0;
2783
+ }
2784
+ .summary-card:nth-child(4n) { border-right: none; }
2785
+ .summary-card h3 {
2786
+ margin: 0 0 12px;
2787
+ font-size: 0.7em;
2788
+ font-weight: 700;
2789
+ color: #94a3b8;
2790
+ text-transform: uppercase;
2791
+ letter-spacing: 1.2px;
2792
+ }
2793
+ .summary-card .value {
2794
+ font-size: 2.8em;
2795
+ font-weight: 900;
2796
+ margin: 0;
2797
+ line-height: 1;
2798
+ letter-spacing: -0.03em;
2799
+ }
2800
+ .summary-card .trend-percentage {
2801
+ font-size: 0.9em;
2802
+ color: #64748b;
2803
+ margin-top: 8px;
2804
+ font-weight: 600;
2805
+ }
2806
+
2807
+ @media (max-width: 1024px) {
2808
+ .header {
2809
+ padding: 32px 24px;
2810
+ flex-direction: column;
2811
+ gap: 24px;
2812
+ align-items: flex-start;
2813
+ }
2814
+ .run-info {
2815
+ width: 100%;
2816
+ justify-content: flex-start;
2817
+ gap: 24px;
2818
+ }
2819
+ .dashboard-grid {
2820
+ grid-template-columns: repeat(2, 1fr);
2821
+ }
2822
+ .summary-card:nth-child(2n) { border-right: none; }
2823
+ .summary-card:nth-child(n+7) { border-bottom: none; }
2824
+ .filters {
2825
+ padding: 24px;
2826
+ flex-direction: column;
2827
+ }
2828
+ .filters input { min-width: 100%; }
2829
+ .filters select { min-width: 100%; }
2830
+ .filters button { width: 100%; }
2831
+ .copy-btn {
2832
+ font-size: 0.75em;
2833
+ padding: 8px 16px;
2834
+ margin-left: 0;
2835
+ }
2836
+ .console-output-section h4 {
2837
+ flex-direction: column;
2838
+ align-items: flex-start;
2839
+ gap: 8px;
2840
+ }
2841
+ .log-wrapper {
2842
+ max-height: 300px;
2843
+ }
2844
+ .tabs {
2845
+ overflow-x: auto;
2846
+ }
2847
+ .tab-button {
2848
+ padding: 20px 24px;
2849
+ font-size: 0.75em;
2850
+ white-space: nowrap;
2851
+ }
2852
+ .tag {
2853
+ font-size: 0.65em;
2854
+ padding: 4px 10px;
2855
+ margin-right: 4px;
2856
+ margin-bottom: 4px;
2857
+ letter-spacing: 0.3px;
2858
+ }
2859
+ .test-case-header {
2860
+ grid-template-columns: 1fr;
2861
+ grid-template-rows: auto auto auto;
2862
+ gap: 12px;
2863
+ padding: 16px 20px;
2864
+ }
2865
+ .test-case-summary {
2866
+ grid-column: 1;
2867
+ grid-row: 1;
2868
+ flex-direction: column;
2869
+ align-items: flex-start;
2870
+ gap: 8px;
2871
+ width: 100%;
2872
+ max-width: 100%;
2873
+ overflow: hidden;
2874
+ }
2875
+ .test-case-title {
2876
+ width: 100%;
2877
+ max-width: 100%;
2878
+ }
2879
+ .test-case-browser {
2880
+ width: 100%;
2881
+ max-width: 100%;
2882
+ white-space: normal;
2883
+ }
2884
+ .test-case-meta {
2885
+ grid-column: 1;
2886
+ grid-row: 2;
2887
+ width: 100%;
2888
+ gap: 6px;
2889
+ }
2890
+ .test-case-status-duration {
2891
+ grid-column: 1;
2892
+ grid-row: 3;
2893
+ align-items: flex-start;
2894
+ }
2895
+ .test-case {
2896
+ margin: 0 0 12px 0;
2897
+ border-radius: 8px;
2898
+ }
2899
+ .test-case-content {
2900
+ padding: 20px;
2901
+ }
2902
+ .pie-chart-wrapper, .suites-widget, .trend-chart {
2903
+ padding: 32px 24px;
2904
+ }
2905
+ .test-history-grid {
2906
+ grid-template-columns: 1fr;
2907
+ }
2908
+ .ai-failure-cards-grid {
2909
+ grid-template-columns: 1fr;
2910
+ }
2911
+ }
2912
+
2913
+ @media (max-width: 768px) {
2914
+ .header h1 { font-size: 1.8em; }
2915
+ #report-logo { height: 48px; }
2916
+ .tabs {
2917
+ flex-wrap: nowrap;
2918
+ gap: 0;
2919
+ overflow-x: auto;
2920
+ }
2921
+ .tab-button {
2922
+ padding: 16px 20px;
2923
+ font-size: 0.7em;
2924
+ flex: 1 1 auto;
2925
+ min-width: 100px;
2926
+ }
2927
+ .dashboard-grid {
2928
+ grid-template-columns: 1fr;
2929
+ }
2930
+ .summary-card {
2931
+ padding: 32px 24px !important;
2932
+ border-right: none !important;
2933
+ }
2934
+ .summary-card .value { font-size: 2.5em !important; }
2935
+ .dashboard-bottom-row {
2936
+ grid-template-columns: 1fr;
2937
+ gap: 0;
2938
+ }
2939
+ .dashboard-column {
2940
+ gap: 0;
2941
+ }
2942
+ .pie-chart-wrapper, .suites-widget, .trend-chart {
2943
+ padding: 28px 20px;
2944
+ }
2945
+ .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
2946
+ font-size: 1.2em;
2947
+ margin-bottom: 20px;
2948
+ }
2949
+ .pie-chart-wrapper div[id^="pieChart-"] {
2950
+ width: 100% !important;
2951
+ max-width: 100% !important;
2952
+ min-height: 280px;
2953
+ overflow: visible !important;
2954
+ }
2955
+ .pie-chart-wrapper {
2956
+ overflow: visible !important;
2957
+ }
2958
+ .trend-chart-container {
2959
+ min-height: 280px;
2960
+ }
2961
+ .suites-grid {
2962
+ grid-template-columns: 1fr;
2963
+ }
2964
+ .test-case-summary {
2965
+ flex-direction: column;
2966
+ align-items: flex-start;
2967
+ gap: 8px;
2968
+ }
2969
+ .test-case-title {
2970
+ width: 100%;
2971
+ }
2972
+ .test-case-browser {
2973
+ width: 100%;
2974
+ }
2975
+ .test-case-meta {
2976
+ flex-wrap: wrap;
2977
+ gap: 6px;
2978
+ width: 100%;
2979
+ }
2980
+ .test-history-trend-section {
2981
+ padding: 0px 20px !important;
2982
+ }
2983
+ .ai-failure-cards-grid {
2984
+ grid-template-columns: 1fr;
2985
+ }
2986
+ .ai-analyzer-stats {
2987
+ flex-direction: column;
2988
+ gap: 15px;
2989
+ text-align: center;
2990
+ }
2991
+ .failure-header {
2992
+ flex-direction: column;
2993
+ align-items: stretch;
2994
+ gap: 15px;
2995
+ }
2996
+ .failure-main-info {
2997
+ text-align: center;
2998
+ }
2999
+ .failure-meta {
3000
+ justify-content: center;
3001
+ }
3002
+ .ai-buttons-group {
3003
+ flex-direction: column;
3004
+ width: 100%;
3005
+ }
3006
+ .compact-ai-btn, .copy-prompt-btn {
3007
+ justify-content: center;
3008
+ padding: 12px 20px;
3009
+ width: 100%;
3010
+ }
3011
+ }
3012
+
3013
+ @media (max-width: 480px) {
3014
+ .header { padding: 24px 16px; }
3015
+ .header h1 { font-size: 1.5em; }
3016
+ #report-logo { height: 42px; }
3017
+ .run-info {
3018
+ flex-direction: column;
3019
+ gap: 12px;
3020
+ width: 100%;
3021
+ }
3022
+ .run-info-item {
3023
+ padding: 14px 20px;
3024
+ }
3025
+ .run-info-item:not(:last-child)::after {
3026
+ display: none;
3027
+ }
3028
+ .run-info-item:not(:last-child) {
3029
+ border-bottom: 1px solid var(--light-gray-color);
3030
+ }
3031
+ .run-info strong {
3032
+ font-size: 0.65em;
3033
+ }
3034
+ .run-info span {
3035
+ font-size: 1.1em;
3036
+ white-space: normal;
3037
+ word-break: break-word;
3038
+ overflow-wrap: break-word;
3039
+ }
3040
+ .tabs {
3041
+ flex-wrap: wrap;
3042
+ gap: 4px;
3043
+ padding: 8px;
3044
+ }
3045
+ .tab-button {
3046
+ padding: 14px 10px;
3047
+ font-size: 0.6em;
3048
+ letter-spacing: 0.3px;
3049
+ flex: 1 1 calc(50% - 4px);
3050
+ min-width: 0;
3051
+ text-align: center;
3052
+ }
3053
+ .dashboard-grid { gap: 0; }
3054
+ .summary-card { padding: 28px 16px !important; }
3055
+ .summary-card h3 { font-size: 0.65em; }
3056
+ .summary-card .value { font-size: 2em !important; }
3057
+ .dashboard-bottom-row { gap: 0; }
3058
+ .dashboard-column {
3059
+ gap: 0;
3060
+ }
3061
+ .pie-chart-wrapper, .suites-widget, .trend-chart {
3062
+ padding: 20px 16px;
3063
+ }
3064
+ .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
3065
+ font-size: 1em;
3066
+ margin-bottom: 16px;
3067
+ font-weight: 800;
3068
+ }
3069
+ .env-dashboard-title {
3070
+ font-size: 1em;
3071
+ margin-bottom: 6px;
3072
+ }
3073
+ .env-dashboard-subtitle {
3074
+ font-size: 0.85em;
3075
+ }
3076
+ .env-card-header {
3077
+ font-size: 0.85em;
3078
+ }
3079
+ .pie-chart-wrapper div[id^="pieChart-"] {
3080
+ width: 100% !important;
3081
+ max-width: 100% !important;
3082
+ min-height: 250px;
3083
+ overflow: visible !important;
3084
+ }
3085
+ .pie-chart-wrapper {
3086
+ overflow: visible !important;
3087
+ padding: 20px 12px;
3088
+ }
3089
+ .trend-chart-container {
3090
+ min-height: 250px;
3091
+ }
3092
+ .suites-grid {
3093
+ grid-template-columns: 1fr;
3094
+ gap: 16px;
3095
+ }
3096
+ .suite-card {
3097
+ padding: 16px;
3098
+ }
3099
+ .filters {
3100
+ padding: 16px;
3101
+ gap: 8px;
3102
+ }
3103
+ .test-history-trend-section {
3104
+ padding: 0px 16px !important;
3105
+ }
3106
+ .test-case {
3107
+ margin: 0 0 10px 0;
3108
+ border-radius: 6px;
3109
+ }
3110
+ .test-case-header {
3111
+ padding: 14px 16px;
3112
+ }
3113
+ .test-case-content {
3114
+ padding: 16px;
3115
+ }
3116
+ .stat-item .stat-number {
3117
+ font-size: 1.5em;
3118
+ }
3119
+ .failure-header {
3120
+ padding: 15px;
3121
+ }
3122
+ .failure-error-preview, .full-error-details {
3123
+ padding-left: 15px;
3124
+ padding-right: 15px;
3125
+ }
3126
+ .header h1 {
3127
+ word-break: break-word;
3128
+ overflow-wrap: break-word;
3129
+ }
3130
+ h2, h3, h4 {
3131
+ word-break: break-word;
3132
+ overflow-wrap: break-word;
3133
+ }
3134
+ .environment-dashboard-wrapper {
3135
+ padding: 24px 16px;
3136
+ gap: 24px;
3137
+ }
3138
+ .env-card {
3139
+ padding: 20px;
3140
+ }
3141
+ }
3142
+ .summary-card.status-passed { background: rgba(16, 185, 129, 0.02); }
3143
+ .summary-card.status-passed:hover {
3144
+ background: rgba(16, 185, 129, 0.15);
3145
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
3146
+ }
3147
+ .summary-card.status-passed .value { color: #10b981; }
3148
+ .summary-card.status-failed { background: rgba(239, 68, 68, 0.02); }
3149
+ .summary-card.status-failed:hover {
3150
+ background: rgba(239, 68, 68, 0.15);
3151
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
3152
+ }
3153
+ .summary-card.status-failed .value { color: #ef4444; }
3154
+ .summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
3155
+ .summary-card.status-skipped:hover {
3156
+ background: rgba(245, 158, 11, 0.15);
3157
+ box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
3158
+ }
3159
+ .summary-card.status-skipped .value { color: #f59e0b; }
3160
+ .summary-card:not([class*='status-']) .value { color: #0f172a; }
2520
3161
  .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
2521
- .pie-chart-wrapper, .suites-widget, .trend-chart { background-color: var(--card-background-color); padding: 28px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
2522
- .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3 { text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color); }
2523
- .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
3162
+ .dashboard-column {
3163
+ display: flex;
3164
+ flex-direction: column;
3165
+ gap: 28px;
3166
+ }
3167
+ .pie-chart-wrapper, .suites-widget, .trend-chart {
3168
+ background: rgba(255, 255, 255, 0.95);
3169
+ padding: 48px;
3170
+ border: 1px solid #e2e8f0;
3171
+ border-radius: 16px;
3172
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
3173
+ display: flex;
3174
+ flex-direction: column;
3175
+ overflow: visible;
3176
+ margin-bottom: 24px;
3177
+ }
3178
+ .pie-chart-wrapper {
3179
+ position: relative;
3180
+ }
3181
+ .pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
3182
+ text-align: left;
3183
+ margin: 0 0 40px 0;
3184
+ font-size: 1.8em;
3185
+ font-weight: 900;
3186
+ color: #0f172a;
3187
+ letter-spacing: -0.02em;
3188
+ }
3189
+ .trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] {
3190
+ flex-grow: 1;
3191
+ min-height: 250px;
3192
+ width: 100%;
3193
+ overflow: visible;
3194
+ }
2524
3195
  .status-badge-small-tooltip { padding: 2px 5px; border-radius: 3px; font-size: 0.9em; font-weight: 600; color: white; text-transform: uppercase; }
2525
3196
  .status-badge-small-tooltip.status-passed { background-color: var(--success-color); }
2526
3197
  .status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
@@ -2529,40 +3200,307 @@ function generateHTML(reportData, trendData = null) {
2529
3200
  .suites-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
2530
3201
  .summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
2531
3202
  .suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
2532
- .suite-card { border: 1px solid var(--border-color); border-left-width: 5px; border-radius: calc(var(--border-radius) / 1.5); padding: 20px; background-color: var(--card-background-color); transition: box-shadow 0.2s ease, border-left-color 0.2s ease; }
2533
- .suite-card:hover { box-shadow: var(--box-shadow); }
2534
- .suite-card.status-passed { border-left-color: var(--success-color); }
2535
- .suite-card.status-failed { border-left-color: var(--danger-color); }
2536
- .suite-card.status-skipped { border-left-color: var(--warning-color); }
3203
+ .suite-card {
3204
+ border: none;
3205
+ border-left: 4px solid #e2e8f0;
3206
+ padding: 24px;
3207
+ background: white;
3208
+ transition: all 0.15s ease;
3209
+ }
3210
+ .suite-card:hover {
3211
+ background: #fafbfc;
3212
+ border-left-color: #6366f1;
3213
+ }
3214
+ .suite-card.status-passed { border-left-color: #10b981; }
3215
+ .suite-card.status-passed:hover { background: rgba(16, 185, 129, 0.02); }
3216
+ .suite-card.status-failed { border-left-color: #ef4444; }
3217
+ .suite-card.status-failed:hover { background: rgba(239, 68, 68, 0.02); }
3218
+ .suite-card.status-skipped { border-left-color: #f59e0b; }
3219
+ .suite-card.status-skipped:hover { background: rgba(245, 158, 11, 0.02); }
2537
3220
  .suite-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
2538
3221
  .suite-name { font-weight: 600; font-size: 1.05em; color: var(--text-color); margin-right: 10px; word-break: break-word;}
2539
- .browser-tag { font-size: 0.8em; background-color: var(--medium-gray-color); color: var(--text-color-secondary); padding: 3px 8px; border-radius: 4px; white-space: nowrap;}
3222
+ .browser-tag {
3223
+ font-size: 0.85em;
3224
+ font-weight: 600;
3225
+ background: linear-gradient(135deg, rgba(96, 165, 250, 0.2) 0%, rgba(59, 130, 246, 0.15) 100%);
3226
+ padding: 6px 12px;
3227
+ border-radius: var(--radius-sm);
3228
+ border: 1px solid rgba(96, 165, 250, 0.3);
3229
+ display: inline-block;
3230
+ box-shadow: 0 2px 8px rgba(96, 165, 250, 0.15), inset 0 1px 0 rgba(96, 165, 250, 0.2);
3231
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
3232
+ letter-spacing: 0.3px;
3233
+ max-width: 200px;
3234
+ overflow: hidden;
3235
+ text-overflow: ellipsis;
3236
+ white-space: nowrap;
3237
+ vertical-align: middle;
3238
+ cursor: help;
3239
+ transition: all 0.2s ease;
3240
+ }
3241
+ .browser-tag:hover {
3242
+ background: linear-gradient(135deg, rgba(96, 165, 250, 0.3) 0%, rgba(59, 130, 246, 0.25) 100%);
3243
+ border-color: rgba(96, 165, 250, 0.5);
3244
+ }
2540
3245
  .suite-card-body .test-count { font-size: 0.95em; color: var(--text-color-secondary); display: block; margin-bottom: 10px; }
2541
3246
  .suite-stats { display: flex; gap: 14px; font-size: 0.95em; align-items: center; }
2542
3247
  .suite-stats span { display: flex; align-items: center; gap: 6px; }
2543
3248
  .suite-stats svg { vertical-align: middle; font-size: 1.15em; }
2544
- .filters { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 28px; padding: 20px; background-color: var(--light-gray-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-inset); border-color: black; border-style: groove; }
2545
- .filters input, .filters select, .filters button { padding: 11px 15px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 1em; }
2546
- .filters input { flex-grow: 1; min-width: 240px;}
2547
- .filters select {min-width: 180px;}
2548
- .filters button { background-color: var(--primary-color); color: white; cursor: pointer; transition: background-color 0.2s ease, box-shadow 0.2s ease; border: none; }
2549
- .filters button:hover { background-color: var(--accent-color); box-shadow: 0 2px 5px rgba(0,0,0,0.15);}
2550
- .test-case { margin-bottom: 15px; border: 1px solid var(--border-color); border-radius: var(--border-radius); background-color: var(--card-background-color); box-shadow: var(--box-shadow-light); overflow: hidden; }
2551
- .test-case-header { padding: 10px 15px; background-color: #fff; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid transparent; transition: background-color 0.2s ease; }
2552
- .test-case-header:hover { background-color: #f4f6f8; }
2553
- .test-case-header[aria-expanded="true"] { border-bottom-color: var(--border-color); background-color: #f9fafb; }
2554
- .test-case-summary { display: flex; align-items: center; gap: 14px; flex-grow: 1; flex-wrap: wrap;}
2555
- .test-case-title { font-weight: 600; color: var(--text-color); font-size: 1em; }
2556
- .test-case-browser { font-size: 0.9em; color: var(--text-color-secondary); }
2557
- .test-case-meta { display: flex; align-items: center; gap: 12px; font-size: 0.9em; color: var(--text-color-secondary); flex-shrink: 0; }
2558
- .test-duration { background-color: var(--light-gray-color); padding: 4px 10px; border-radius: 12px; font-size: 0.9em;}
2559
- .status-badge { padding: 5px; border-radius: 6px; font-size: 0.8em; font-weight: 600; color: white; text-transform: uppercase; min-width: 70px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
2560
- .status-badge.status-passed { background-color: var(--success-color); }
2561
- .status-badge.status-failed { background-color: var(--danger-color); }
2562
- .status-badge.status-skipped { background-color: var(--warning-color); }
2563
- .status-badge.status-unknown { background-color: var(--dark-gray-color); }
2564
- .tag { display: inline-block; background: linear-gradient( #fff, #333, #000); color: #fff; padding: 3px 10px; border-radius: 12px; font-size: 0.85em; margin-right: 6px; font-weight: 400; }
2565
- .test-case-content { display: none; padding: 20px; border-top: 1px solid var(--border-color); background-color: #fcfdff; }
3249
+ .suite-stats .stat-passed { color: #10b981; }
3250
+ .suite-stats .stat-failed { color: #ef4444; }
3251
+ .suite-stats .stat-skipped { color: #f59e0b; }
3252
+ .filters {
3253
+ display: flex;
3254
+ flex-wrap: wrap;
3255
+ gap: 12px;
3256
+ margin: 0;
3257
+ padding: 24px 32px;
3258
+ background: #f8fafc;
3259
+ border-bottom: 1px solid #e2e8f0;
3260
+ }
3261
+ .filters input, .filters select, .filters button {
3262
+ padding: 14px 18px;
3263
+ border: 2px solid #e2e8f0;
3264
+ font-size: 0.9em;
3265
+ font-family: var(--font-family);
3266
+ font-weight: 600;
3267
+ transition: all 0.15s ease;
3268
+ }
3269
+ .filters input {
3270
+ flex: 1 1 300px;
3271
+ min-width: 0;
3272
+ background: white;
3273
+ }
3274
+ .filters input:focus {
3275
+ outline: none;
3276
+ border-color: #6366f1;
3277
+ }
3278
+ .filters select {
3279
+ flex: 0 0 auto;
3280
+ min-width: 180px;
3281
+ background: white;
3282
+ cursor: pointer;
3283
+ }
3284
+ .filters select:focus {
3285
+ outline: none;
3286
+ border-color: #6366f1;
3287
+ }
3288
+ .filters button {
3289
+ background: #0f172a;
3290
+ color: white;
3291
+ cursor: pointer;
3292
+ border: none;
3293
+ font-weight: 700;
3294
+ padding: 14px 32px;
3295
+ text-transform: uppercase;
3296
+ letter-spacing: 0.5px;
3297
+ font-size: 0.8em;
3298
+ flex: 0 0 auto;
3299
+ }
3300
+ .filters button:hover {
3301
+ background: #1e293b;
3302
+ color: white;
3303
+ }
3304
+ .test-case {
3305
+ margin: 0 0 16px 0;
3306
+ padding: 0;
3307
+ border: 1px solid #e2e8f0;
3308
+ border-radius: 12px;
3309
+ background: rgba(255, 255, 255, 0.95);
3310
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
3311
+ transition: transform 0.2s ease;
3312
+ overflow: hidden;
3313
+ }
3314
+ .test-case:hover {
3315
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
3316
+ transform: translateY(-2px);
3317
+ border-color: #cbd5e1;
3318
+ }
3319
+ .test-case:last-child {
3320
+ margin-bottom: 0;
3321
+ }
3322
+ .test-case-header {
3323
+ padding: 20px 24px;
3324
+ background: linear-gradient(to right, #ffffff 0%, #f8fafc 100%);
3325
+ cursor: pointer;
3326
+ display: grid;
3327
+ grid-template-columns: 1fr auto;
3328
+ grid-template-rows: auto auto;
3329
+ gap: 12px 20px;
3330
+ transition: all 0.2s ease;
3331
+ border-bottom: 2px solid #f1f5f9;
3332
+ position: relative;
3333
+ }
3334
+ .test-case-header::before {
3335
+ content: '';
3336
+ position: absolute;
3337
+ left: 0;
3338
+ top: 0;
3339
+ bottom: 0;
3340
+ width: 4px;
3341
+ background: transparent;
3342
+ transition: background 0.2s ease;
3343
+ }
3344
+ .test-case-header:hover::before {
3345
+ background: linear-gradient(to bottom, #6366f1 0%, #8b5cf6 100%);
3346
+ }
3347
+ .test-case-header[aria-expanded="true"] {
3348
+ background: linear-gradient(to right, #f8fafc 0%, #f1f5f9 100%);
3349
+ border-bottom-color: #e2e8f0;
3350
+ }
3351
+ .test-case-header[aria-expanded="true"]::before {
3352
+ background: linear-gradient(to bottom, #6366f1 0%, #8b5cf6 100%);
3353
+ }
3354
+ .test-case-summary {
3355
+ display: flex;
3356
+ align-items: center;
3357
+ gap: 14px;
3358
+ flex-wrap: wrap;
3359
+ min-width: 0;
3360
+ grid-column: 1;
3361
+ grid-row: 1;
3362
+ }
3363
+ .test-case-title {
3364
+ font-weight: 600;
3365
+ color: var(--text-color);
3366
+ font-size: 1em;
3367
+ word-break: break-word;
3368
+ overflow-wrap: break-word;
3369
+ flex: 1 1 auto;
3370
+ min-width: 0;
3371
+ }
3372
+ .test-case-browser {
3373
+ font-size: 0.9em;
3374
+ color: var(--text-color-secondary);
3375
+ word-break: break-word;
3376
+ overflow-wrap: break-word;
3377
+ max-width: 100%;
3378
+ }
3379
+ .test-case-meta {
3380
+ display: flex;
3381
+ align-items: center;
3382
+ gap: 8px;
3383
+ font-size: 0.9em;
3384
+ color: var(--text-color-secondary);
3385
+ flex-wrap: wrap;
3386
+ min-width: 0;
3387
+ grid-column: 1;
3388
+ grid-row: 2;
3389
+ }
3390
+ .test-case-status-duration {
3391
+ display: flex;
3392
+ flex-direction: column;
3393
+ align-items: flex-end;
3394
+ gap: 8px;
3395
+ grid-column: 2;
3396
+ grid-row: 1 / 3;
3397
+ align-self: center;
3398
+ }
3399
+ .test-duration {
3400
+ background-color: var(--light-gray-color);
3401
+ padding: 6px 12px;
3402
+ border-radius: 8px;
3403
+ font-size: 0.9em;
3404
+ white-space: nowrap;
3405
+ flex-shrink: 0;
3406
+ font-weight: 700;
3407
+ color: #0f172a;
3408
+ }
3409
+ .status-badge {
3410
+ padding: 8px 20px;
3411
+ border-radius: 0;
3412
+ font-size: 0.7em;
3413
+ font-weight: 800;
3414
+ color: white;
3415
+ text-transform: uppercase;
3416
+ min-width: 100px;
3417
+ text-align: center;
3418
+ letter-spacing: 1px;
3419
+ }
3420
+ .status-badge.status-passed { background: #10b981; }
3421
+ .status-badge.status-failed { background: #ef4444; }
3422
+ .status-badge.status-skipped { background: #f59e0b; }
3423
+ .status-badge.status-unknown { background: #64748b; }
3424
+
3425
+ /* --- NEON GLASS SEVERITY BADGES --- */
3426
+ .severity-badge {
3427
+ display: inline-flex;
3428
+ align-items: center;
3429
+ gap: 6px;
3430
+ padding: 5px 12px;
3431
+ border-radius: 99px;
3432
+ font-size: 0.75em;
3433
+ font-weight: 700;
3434
+ text-transform: uppercase;
3435
+ letter-spacing: 0.05em;
3436
+ border: 1px solid;
3437
+ backdrop-filter: blur(4px);
3438
+ -webkit-backdrop-filter: blur(4px);
3439
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
3440
+ }
3441
+ .severity-badge::before {
3442
+ content: '';
3443
+ width: 6px;
3444
+ height: 6px;
3445
+ border-radius: 50%;
3446
+ background-color: currentColor;
3447
+ box-shadow: 0 0 6px currentColor;
3448
+ }
3449
+ /* Auto-map colors based on data-severity attribute */
3450
+ .severity-badge[data-severity="critical"] {
3451
+ color: #ff4d4d;
3452
+ background-color: rgba(255, 77, 77, 0.1);
3453
+ border-color: rgba(255, 77, 77, 0.25);
3454
+ }
3455
+ .severity-badge[data-severity="high"] {
3456
+ color: #fb923c;
3457
+ background-color: rgba(251, 146, 60, 0.1);
3458
+ border-color: rgba(251, 146, 60, 0.25);
3459
+ }
3460
+ .severity-badge[data-severity="medium"] {
3461
+ color: #facc15;
3462
+ background-color: rgba(250, 204, 21, 0.1);
3463
+ border-color: rgba(250, 204, 21, 0.25);
3464
+ }
3465
+ .severity-badge[data-severity="low"] {
3466
+ color: #4ade80;
3467
+ background-color: rgba(74, 222, 128, 0.1);
3468
+ border-color: rgba(74, 222, 128, 0.25);
3469
+ }
3470
+ .severity-badge[data-severity="minor"] {
3471
+ color: #94a3b8;
3472
+ background-color: rgba(148, 163, 184, 0.1);
3473
+ border-color: rgba(148, 163, 184, 0.25);
3474
+ }
3475
+
3476
+ .tag {
3477
+ display: inline-flex;
3478
+ align-items: center;
3479
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
3480
+ color: #ffffff;
3481
+ padding: 6px 14px;
3482
+ border-radius: 6px;
3483
+ font-size: 0.8em;
3484
+ margin-right: 8px;
3485
+ margin-bottom: 4px;
3486
+ font-weight: 600;
3487
+ text-transform: uppercase;
3488
+ letter-spacing: 0.5px;
3489
+ box-shadow: 0 2px 6px rgba(99, 102, 241, 0.25);
3490
+ transition: all 0.2s ease;
3491
+ flex-shrink: 0;
3492
+ white-space: nowrap;
3493
+ }
3494
+ .tag:hover {
3495
+ box-shadow: 0 4px 10px rgba(99, 102, 241, 0.35);
3496
+ transform: translateY(-1px);
3497
+ }
3498
+ .test-case-content {
3499
+ display: none;
3500
+ padding: 24px;
3501
+ background: linear-gradient(to bottom, #ffffff 0%, #f9fafb 100%);
3502
+ border-top: 1px solid #e2e8f0;
3503
+ }
2566
3504
  .test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
2567
3505
  .test-case-content p { margin-bottom: 10px; font-size: 1em; }
2568
3506
  .test-error-summary { margin-bottom: 20px; padding: 14px; background-color: rgba(244,67,54,0.05); border: 1px solid rgba(244,67,54,0.2); border-left: 4px solid var(--danger-color); border-radius: 4px; }
@@ -2609,10 +3547,24 @@ function generateHTML(reportData, trendData = null) {
2609
3547
  .test-history-header h3 { margin: 0; font-size: 1.15em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* This was h3, changed to p for consistency with user file */
2610
3548
  .test-history-header p { font-weight: 500 } /* Added this */
2611
3549
  .test-history-trend { margin-bottom: 20px; min-height: 110px; }
3550
+ .test-history-trend-section {
3551
+ padding: 0px 48px !important;
3552
+ }
3553
+ .test-history-trend-section .chart-title-header {
3554
+ margin: 0 0 20px 0 !important;
3555
+ }
2612
3556
  .test-history-trend div[id^="testHistoryChart-"] { display: block; margin: 0 auto; max-width:100%; height: 100px; width: 320px; }
2613
3557
  .test-history-details-collapsible summary { cursor: pointer; font-size: 1em; color: var(--primary-color); margin-bottom: 10px; font-weight:500; }
2614
3558
  .test-history-details-collapsible summary:hover {text-decoration: underline;}
2615
- .test-history-details table { width: 100%; border-collapse: collapse; font-size: 0.95em; }
3559
+ .test-history-details {
3560
+ overflow-x: auto;
3561
+ max-width: 100%;
3562
+ }
3563
+ .test-history-details table {
3564
+ width: 100%;
3565
+ border-collapse: collapse;
3566
+ font-size: 0.95em;
3567
+ }
2616
3568
  .test-history-details th, .test-history-details td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--light-gray-color); }
2617
3569
  .test-history-details th { background-color: var(--light-gray-color); font-weight: 600; }
2618
3570
  .status-badge-small { padding: 3px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 600; color: white; text-transform: uppercase; display: inline-block; }
@@ -2630,13 +3582,57 @@ function generateHTML(reportData, trendData = null) {
2630
3582
  .ai-failure-card-body { padding: 20px; }
2631
3583
  .ai-fix-btn { background-color: var(--primary-color); color: white; border: none; padding: 10px 18px; font-size: 1em; font-weight: 600; border-radius: 6px; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; display: inline-flex; align-items: center; gap: 8px; }
2632
3584
  .ai-fix-btn:hover { background-color: var(--accent-color); transform: translateY(-2px); }
2633
- .ai-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.65); display: none; align-items: center; justify-content: center; z-index: 1050; animation: fadeIn 0.3s; }
2634
- .ai-modal-content { background-color: var(--card-background-color); color: var(--text-color); border-radius: var(--border-radius); width: 90%; max-width: 800px; max-height: 90vh; box-shadow: 0 10px 30px rgba(0,0,0,0.2); display: flex; flex-direction: column; overflow: hidden; }
2635
- .ai-modal-header { padding: 18px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
2636
- .ai-modal-header h3 { margin: 0; font-size: 1.25em; }
2637
- .ai-modal-close { font-size: 2rem; font-weight: 300; cursor: pointer; color: var(--dark-gray-color); line-height: 1; transition: color 0.2s; }
2638
- .ai-modal-close:hover { color: var(--danger-color); }
2639
- .ai-modal-body { padding: 25px; overflow-y: auto; }
3585
+ .ai-modal-overlay {
3586
+ position: fixed;
3587
+ top: 0;
3588
+ left: 0;
3589
+ width: 100%;
3590
+ height: 100%;
3591
+ background-color: rgba(0,0,0,0.8);
3592
+ display: none;
3593
+ align-items: center;
3594
+ justify-content: center;
3595
+ z-index: 1050;
3596
+ animation: fadeIn 0.3s;
3597
+ }
3598
+ .ai-modal-content {
3599
+ background-color: var(--card-background-color);
3600
+ color: var(--text-color);
3601
+ border-radius: var(--border-radius);
3602
+ width: 90%;
3603
+ max-width: 800px;
3604
+ max-height: 90vh;
3605
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
3606
+ display: flex;
3607
+ flex-direction: column;
3608
+ overflow: hidden;
3609
+ }
3610
+ .ai-modal-header {
3611
+ padding: 18px 25px;
3612
+ border-bottom: 1px solid var(--border-color);
3613
+ display: flex;
3614
+ justify-content: space-between;
3615
+ align-items: center;
3616
+ }
3617
+ .ai-modal-header h3 {
3618
+ margin: 0;
3619
+ font-size: 1.25em;
3620
+ }
3621
+ .ai-modal-close {
3622
+ font-size: 2rem;
3623
+ font-weight: 300;
3624
+ cursor: pointer;
3625
+ color: var(--dark-gray-color);
3626
+ line-height: 1;
3627
+ transition: color 0.2s;
3628
+ }
3629
+ .ai-modal-close:hover {
3630
+ color: var(--danger-color);
3631
+ }
3632
+ .ai-modal-body {
3633
+ padding: 25px;
3634
+ overflow-y: auto;
3635
+ }
2640
3636
  .ai-modal-body h4 { margin-top: 18px; margin-bottom: 10px; font-size: 1.1em; color: var(--primary-color); }
2641
3637
  .ai-modal-body p { margin-bottom: 15px; }
2642
3638
  .ai-loader { margin: 40px auto; border: 5px solid #f3f3f3; border-top: 5px solid var(--primary-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; }
@@ -2650,9 +3646,68 @@ function generateHTML(reportData, trendData = null) {
2650
3646
  .view-trace:hover { background: #2c5282; }
2651
3647
  .download-trace { background: #e2e8f0; color: #2d3748; }
2652
3648
  .download-trace:hover { background: #cbd5e0; }
2653
- .filters button.clear-filters-btn { background-color: var(--medium-gray-color); color: var(--text-color); }
2654
- .filters button.clear-filters-btn:hover { background-color: var(--dark-gray-color); color: #fff; }
2655
- .copy-btn {color: var(--primary-color); background: #fefefe; border-radius: 8px; cursor: pointer; border-color: var(--primary-color); font-size: 1em; margin-left: 93%; font-weight: 600;}
3649
+ .filters button.clear-filters-btn {
3650
+ background-color: var(--medium-gray-color);
3651
+ color: var(--text-color);
3652
+ pointer-events: auto;
3653
+ cursor: pointer;
3654
+ }
3655
+ .filters button.clear-filters-btn:active,
3656
+ .filters button.clear-filters-btn:focus {
3657
+ background-color: var(--medium-gray-color);
3658
+ color: var(--text-color);
3659
+ transform: none;
3660
+ box-shadow: none;
3661
+ outline: none;
3662
+ }
3663
+ .copy-btn {
3664
+ color: #ffffff;
3665
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
3666
+ border: none;
3667
+ border-radius: 8px;
3668
+ cursor: pointer;
3669
+ font-size: 0.85em;
3670
+ font-weight: 600;
3671
+ padding: 10px 20px;
3672
+ transition: all 0.2s ease;
3673
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);
3674
+ text-transform: uppercase;
3675
+ letter-spacing: 0.5px;
3676
+ margin-left: auto;
3677
+ display: inline-flex;
3678
+ align-items: center;
3679
+ gap: 6px;
3680
+ }
3681
+ .copy-btn:hover {
3682
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
3683
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
3684
+ transform: translateY(-1px);
3685
+ }
3686
+ .copy-btn:active {
3687
+ transform: translateY(0);
3688
+ box-shadow: 0 2px 6px rgba(99, 102, 241, 0.2);
3689
+ }
3690
+ .log-wrapper {
3691
+ max-width: 100%;
3692
+ overflow-x: auto;
3693
+ overflow-y: auto;
3694
+ max-height: 400px;
3695
+ border-radius: 8px;
3696
+ background: #2d2d2d;
3697
+ }
3698
+ .log-wrapper pre {
3699
+ margin: 0;
3700
+ white-space: pre;
3701
+ word-wrap: normal;
3702
+ overflow-wrap: normal;
3703
+ }
3704
+ .console-output-section h4 {
3705
+ display: flex;
3706
+ align-items: center;
3707
+ justify-content: space-between;
3708
+ gap: 16px;
3709
+ margin-bottom: 12px;
3710
+ }
2656
3711
  /* Compact AI Failure Analyzer Styles */
2657
3712
  .ai-analyzer-stats {
2658
3713
  display: flex;
@@ -2852,6 +3907,62 @@ function generateHTML(reportData, trendData = null) {
2852
3907
  max-height: 300px;
2853
3908
  overflow-y: auto;
2854
3909
  }
3910
+ .ai-suggestion-container {
3911
+ margin-top: 15px;
3912
+ border-top: 2px solid #e2e8f0;
3913
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
3914
+ animation: slideDown 0.3s ease-out;
3915
+ }
3916
+ @keyframes slideDown {
3917
+ from {
3918
+ opacity: 0;
3919
+ max-height: 0;
3920
+ transform: translateY(-10px);
3921
+ }
3922
+ to {
3923
+ opacity: 1;
3924
+ max-height: 1000px;
3925
+ transform: translateY(0);
3926
+ }
3927
+ }
3928
+ .ai-suggestion-content {
3929
+ padding: 20px;
3930
+ }
3931
+ .ai-suggestion-header {
3932
+ display: flex;
3933
+ align-items: center;
3934
+ gap: 10px;
3935
+ margin-bottom: 15px;
3936
+ padding-bottom: 10px;
3937
+ border-bottom: 2px solid #6366f1;
3938
+ }
3939
+ .ai-suggestion-header h4 {
3940
+ margin: 0;
3941
+ color: #6366f1;
3942
+ font-size: 1.1em;
3943
+ font-weight: 700;
3944
+ }
3945
+ .ai-suggestion-body {
3946
+ color: #0f172a;
3947
+ line-height: 1.6;
3948
+ }
3949
+ .ai-suggestion-body h4 {
3950
+ color: #6366f1;
3951
+ margin-top: 15px;
3952
+ margin-bottom: 8px;
3953
+ font-size: 1em;
3954
+ }
3955
+ .ai-suggestion-body p {
3956
+ margin-bottom: 10px;
3957
+ }
3958
+ .ai-suggestion-body pre {
3959
+ background: #1e293b;
3960
+ color: #e2e8f0;
3961
+ padding: 12px;
3962
+ border-radius: 6px;
3963
+ overflow-x: auto;
3964
+ font-size: 0.9em;
3965
+ }
2855
3966
 
2856
3967
  /* Responsive adjustments for compact design */
2857
3968
  @media (max-width: 768px) {
@@ -2894,10 +4005,7 @@ function generateHTML(reportData, trendData = null) {
2894
4005
  }
2895
4006
  }
2896
4007
 
2897
- @media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
2898
- @media (max-width: 992px) { .dashboard-bottom-row { grid-template-columns: 1fr; } .pie-chart-wrapper div[id^="pieChart-"] { max-width: 350px; margin: 0 auto; } .filters input { min-width: 180px; } .filters select { min-width: 150px; } }
2899
- @media (max-width: 768px) { body { font-size: 15px; } .container { margin: 10px; padding: 20px; } .header { flex-direction: column; align-items: flex-start; gap: 15px; } .header h1 { font-size: 1.6em; } .run-info { text-align: left; font-size:0.9em; } .tabs { margin-bottom: 25px;} .tab-button { padding: 12px 20px; font-size: 1.05em;} .dashboard-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 18px;} .summary-card .value {font-size: 2em;} .summary-card h3 {font-size: 0.95em;} .filters { flex-direction: column; padding: 18px; gap: 12px;} .filters input, .filters select, .filters button {width: 100%; box-sizing: border-box;} .test-case-header { flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px; } .test-case-summary {gap: 10px;} .test-case-title {font-size: 1.05em;} .test-case-meta { flex-direction: row; flex-wrap: wrap; gap: 8px; margin-top: 8px;} .attachments-grid {grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px;} .test-history-grid {grid-template-columns: 1fr;} .pie-chart-wrapper {min-height: auto;} .ai-failure-cards-grid { grid-template-columns: 1fr; } }
2900
- @media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 50px; } .tab-button {padding: 10px 15px; font-size: 1em;} .summary-card .value {font-size: 1.8em;} .attachments-grid {grid-template-columns: 1fr;} .step-item {padding-left: calc(var(--depth, 0) * 18px);} .test-case-content, .step-details {padding: 15px;} .trend-charts-row {gap: 20px;} .trend-chart {padding: 20px;} }
4008
+
2901
4009
  </style>
2902
4010
  </head>
2903
4011
  <body>
@@ -2907,11 +4015,16 @@ function generateHTML(reportData, trendData = null) {
2907
4015
  <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
2908
4016
  <h1>Pulse Report</h1>
2909
4017
  </div>
2910
- <div class="run-info"><strong>Run Date:</strong> ${formatDate(
2911
- runSummary.timestamp
2912
- )}<br><strong>Total Duration:</strong> ${formatDuration(
2913
- runSummary.duration
2914
- )}</div>
4018
+ <div class="run-info">
4019
+ <div class="run-info-item">
4020
+ <strong>Run Date</strong>
4021
+ <span>${formatDate(runSummary.timestamp)}</span>
4022
+ </div>
4023
+ <div class="run-info-item">
4024
+ <strong>Total Duration</strong>
4025
+ <span>${formatDuration(runSummary.duration)}</span>
4026
+ </div>
4027
+ </div>
2915
4028
  </header>
2916
4029
  <div class="tabs">
2917
4030
  <button class="tab-button active" data-tab="dashboard">Dashboard</button>
@@ -2935,11 +4048,37 @@ function generateHTML(reportData, trendData = null) {
2935
4048
  }</div><div class="trend-percentage">${skipPercentage}%</div></div>
2936
4049
  <div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
2937
4050
  <div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
2938
- runSummary.duration
4051
+ runSummary.duration,
2939
4052
  )}</div></div>
4053
+ <div class="summary-card">
4054
+ <h3>🔄 Retry Count</h3>
4055
+ <div class="value">${totalRetried}</div>
4056
+ </div>
4057
+ <div class="summary-card">
4058
+ <h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
4059
+ <div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
4060
+ ${browserBreakdown
4061
+ .slice(0, 5)
4062
+ .map(
4063
+ (b) =>
4064
+ `<div class="browser-item">
4065
+ <span class="browser-name">${sanitizeHTML(b.browser)}</span>
4066
+ <span class="browser-stats">${b.percentage}% (${b.count})</span>
4067
+ </div>`,
4068
+ )
4069
+ .join("")}
4070
+ ${
4071
+ browserBreakdown.length > 5
4072
+ ? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid #e2e8f0; margin-top: 8px; padding-top: 8px;">
4073
+ <span>+${browserBreakdown.length - 5} more browsers</span>
4074
+ </div>`
4075
+ : ""
4076
+ }
4077
+ </div>
4078
+ </div>
2940
4079
  </div>
2941
4080
  <div class="dashboard-bottom-row">
2942
- <div style="display: flex; flex-direction: column; gap: 28px;">
4081
+ <div class="dashboard-column">
2943
4082
  ${generatePieChart(
2944
4083
  [
2945
4084
  { label: "Passed", value: runSummary.passed },
@@ -2947,7 +4086,7 @@ function generateHTML(reportData, trendData = null) {
2947
4086
  { label: "Skipped", value: runSummary.skipped || 0 },
2948
4087
  ],
2949
4088
  400,
2950
- 390
4089
+ 390,
2951
4090
  )}
2952
4091
  ${
2953
4092
  runSummary.environment &&
@@ -2957,7 +4096,7 @@ function generateHTML(reportData, trendData = null) {
2957
4096
  }
2958
4097
  </div>
2959
4098
 
2960
- <div style="display: flex; flex-direction: column; gap: 28px;">
4099
+ <div class="dashboard-column">
2961
4100
  ${generateSuitesWidget(suitesData)}
2962
4101
  ${generateSeverityDistributionChart(results)}
2963
4102
  </div>
@@ -2969,22 +4108,21 @@ function generateHTML(reportData, trendData = null) {
2969
4108
  <select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="skipped">Skipped</option></select>
2970
4109
  <select id="filter-browser"><option value="">All Browsers</option>${Array.from(
2971
4110
  new Set(
2972
- (results || []).map((test) => test.browser || "unknown")
2973
- )
4111
+ (results || []).map((test) => test.browser || "unknown"),
4112
+ ),
2974
4113
  )
2975
4114
  .map(
2976
4115
  (browser) =>
2977
4116
  `<option value="${sanitizeHTML(browser)}">${sanitizeHTML(
2978
- browser
2979
- )}</option>`
4117
+ browser,
4118
+ )}</option>`,
2980
4119
  )
2981
4120
  .join("")}</select>
2982
- <button id="expand-all-tests">Expand All</button> <button id="collapse-all-tests">Collapse All</button> <button id="clear-run-summary-filters" class="clear-filters-btn">Clear Filters</button>
4121
+ <button id="clear-run-summary-filters" class="clear-filters-btn">Clear Filters</button>
2983
4122
  </div>
2984
4123
  <div class="test-cases-list">${generateTestCasesHTML()}</div>
2985
4124
  </div>
2986
4125
  <div id="test-history" class="tab-content">
2987
- <h2 class="tab-main-title">Execution Trends</h2>
2988
4126
  <div class="trend-charts-row">
2989
4127
  <div class="trend-chart"><h3 class="chart-title-header">Test Volume & Outcome Trends</h3>
2990
4128
  ${
@@ -3011,13 +4149,15 @@ function generateHTML(reportData, trendData = null) {
3011
4149
  ${generateDescribeDurationChart(results)}
3012
4150
  </div>
3013
4151
  </div>
3014
- <h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
3015
4152
  <div class="trend-charts-row">
3016
4153
  <div class="trend-chart">
4154
+ <h3 class="chart-title-header">Test Distribution by Worker ${infoTooltip}</h3>
3017
4155
  ${generateWorkerDistributionChart(results)}
3018
4156
  </div>
3019
4157
  </div>
3020
- <h2 class="tab-main-title">Individual Test History</h2>
4158
+ <div class="trend-chart test-history-trend-section" style="border-bottom: none;">
4159
+ <h3 class="chart-title-header">Individual Test History</h3>
4160
+ </div>
3021
4161
  ${
3022
4162
  trendData &&
3023
4163
  trendData.testRuns &&
@@ -3032,7 +4172,7 @@ function generateHTML(reportData, trendData = null) {
3032
4172
  <footer style="padding: 0.5rem; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); text-align: center; font-family: 'Segoe UI', system-ui, sans-serif;">
3033
4173
  <div style="display: inline-flex; align-items: center; gap: 0.5rem; color: #333; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
3034
4174
  <span>Created by</span>
3035
- <a href="https://github.com/Arghajit47" target="_blank" rel="noopener noreferrer" style="color: #7737BF; font-weight: 700; font-style: italic; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.color='#BF5C37'" onmouseout="this.style.color='#7737BF'">Arghajit Singha</a>
4175
+ <a href="https://www.npmjs.com/package/@arghajit/playwright-pulse-report" target="_blank" rel="noopener noreferrer" style="color: #7737BF; font-weight: 700; font-style: italic; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.color='#BF5C37'" onmouseout="this.style.color='#7737BF'">Pulse Report</a>
3036
4176
  </div>
3037
4177
  <div style="margin-top: 0.5rem; font-size: 0.75rem; color: #666;">Crafted with precision</div>
3038
4178
  </footer>
@@ -3051,25 +4191,35 @@ function generateHTML(reportData, trendData = null) {
3051
4191
  console.error('Could not find log element with ID:', elementId);
3052
4192
  return;
3053
4193
  }
4194
+ const originalText = button.textContent;
3054
4195
  navigator.clipboard.writeText(logElement.innerText).then(() => {
3055
4196
  button.textContent = 'Copied!';
3056
- setTimeout(() => { button.textContent = 'Copy'; }, 2000);
4197
+ setTimeout(() => { button.textContent = originalText; }, 2000);
3057
4198
  }).catch(err => {
3058
4199
  console.error('Failed to copy log content:', err);
3059
4200
  button.textContent = 'Failed';
3060
- setTimeout(() => { button.textContent = 'Copy'; }, 2000);
4201
+ setTimeout(() => { button.textContent = originalText; }, 2000);
3061
4202
  });
3062
4203
  }
3063
4204
 
3064
4205
  // --- AI Failure Analyzer Functions ---
3065
4206
  function getAIFix(button) {
3066
- const modal = document.getElementById('ai-fix-modal');
3067
- const modalContent = document.getElementById('ai-fix-modal-content');
3068
- const modalTitle = document.getElementById('ai-fix-modal-title');
4207
+ const failureItem = button.closest('.compact-failure-item');
4208
+ const aiContainer = failureItem.querySelector('.ai-suggestion-container');
4209
+ const aiContent = failureItem.querySelector('.ai-suggestion-content');
3069
4210
 
3070
- modal.style.display = 'flex';
3071
- modalTitle.textContent = 'Analyzing...';
3072
- modalContent.innerHTML = '<div class="ai-loader"></div>';
4211
+ // Toggle if already visible
4212
+ if (aiContainer.style.display === 'block') {
4213
+ aiContainer.style.display = 'none';
4214
+ button.querySelector('.ai-text').textContent = 'AI Fix';
4215
+ return;
4216
+ }
4217
+
4218
+ // Show loading state
4219
+ aiContainer.style.display = 'block';
4220
+ aiContent.innerHTML = '<div class="ai-loader" style="margin: 40px auto;"></div>';
4221
+ button.querySelector('.ai-text').textContent = 'Loading...';
4222
+ button.disabled = true;
3073
4223
 
3074
4224
  try {
3075
4225
  const testJson = button.dataset.testJson;
@@ -3087,7 +4237,6 @@ function getAIFix(button) {
3087
4237
  const codeSnippet = test.snippet || '';
3088
4238
 
3089
4239
  const shortTestName = testName.split(' > ').pop();
3090
- modalTitle.textContent = \`Analysis for: \${shortTestName}\`;
3091
4240
 
3092
4241
  const apiUrl = 'https://ai-test-analyser.netlify.app/api/analyze';
3093
4242
  fetch(apiUrl, {
@@ -3151,18 +4300,31 @@ function getAIFix(button) {
3151
4300
  suggestionsHtml += \`<div class="code-section"><pre><code>No suggestion provided.</code></pre></div>\`;
3152
4301
  }
3153
4302
 
3154
- // Combine both parts and set the modal content
3155
- modalContent.innerHTML = analysisHtml + suggestionsHtml;
4303
+ // Combine both parts and display inline
4304
+ button.querySelector('.ai-text').textContent = 'Hide AI Fix';
4305
+ button.disabled = false;
4306
+ aiContent.innerHTML = \`
4307
+ <div class="ai-suggestion-header">
4308
+ <h4>🤖 AI Analysis Result</h4>
4309
+ </div>
4310
+ <div class="ai-suggestion-body">
4311
+ \${analysisHtml}
4312
+ \${suggestionsHtml}
4313
+ </div>
4314
+ \`;
3156
4315
  })
3157
4316
  .catch(err => {
3158
4317
  console.error('AI Fix Error:', err);
3159
- modalContent.innerHTML = \`<div class="test-error-summary"><strong>Error:</strong> Failed to get AI analysis. Please check the console for details. <br><br> \${err.message}</div>\`;
4318
+ button.disabled = false;
4319
+ button.querySelector('.ai-text').textContent = 'AI Fix';
4320
+ aiContent.innerHTML = \`<div class="test-error-summary"><strong>Error:</strong> Failed to get AI analysis. Please check the console for details. <br><br> \${err.message}</div>\`;
3160
4321
  });
3161
4322
 
3162
4323
  } catch (e) {
3163
4324
  console.error('Error processing test data for AI Fix:', e);
3164
- modalTitle.textContent = 'Error';
3165
- modalContent.innerHTML = \`<div class="test-error-summary">Could not process test data. Is it formatted correctly?</div>\`;
4325
+ button.disabled = false;
4326
+ button.querySelector('.ai-text').textContent = 'AI Fix';
4327
+ aiContent.innerHTML = \`<div class="test-error-summary">Could not process test data. Is it formatted correctly?</div>\`;
3166
4328
  }
3167
4329
  }
3168
4330
 
@@ -3245,6 +4407,7 @@ Code Snippet:
3245
4407
  function closeAiModal() {
3246
4408
  const modal = document.getElementById('ai-fix-modal');
3247
4409
  if(modal) modal.style.display = 'none';
4410
+ document.body.style.setProperty('overflow', '', 'important');
3248
4411
  }
3249
4412
 
3250
4413
  function toggleErrorDetails(button) {
@@ -3352,16 +4515,7 @@ Code Snippet:
3352
4515
  document.querySelectorAll('#test-runs .step-header').forEach(header => {
3353
4516
  header.addEventListener('click', () => toggleElementDetails(header, '.step-details'));
3354
4517
  });
3355
- const expandAllBtn = document.getElementById('expand-all-tests');
3356
- const collapseAllBtn = document.getElementById('collapse-all-tests');
3357
- function setAllTestRunDetailsVisibility(displayMode, ariaState) {
3358
- document.querySelectorAll('#test-runs .test-case-content').forEach(el => el.style.display = displayMode);
3359
- document.querySelectorAll('#test-runs .step-details').forEach(el => el.style.display = displayMode);
3360
- document.querySelectorAll('#test-runs .test-case-header[aria-expanded]').forEach(el => el.setAttribute('aria-expanded', ariaState));
3361
- document.querySelectorAll('#test-runs .step-header[aria-expanded]').forEach(el => el.setAttribute('aria-expanded', ariaState));
3362
- }
3363
- if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
3364
- if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
4518
+
3365
4519
  // --- Annotation Link Handler ---
3366
4520
  document.querySelectorAll('a.annotation-link').forEach(link => {
3367
4521
  link.addEventListener('click', (e) => {
@@ -3530,7 +4684,7 @@ async function main() {
3530
4684
  // Script to archive current run to JSON history (this is your modified "generate-trend.mjs")
3531
4685
  const archiveRunScriptPath = path.resolve(
3532
4686
  __dirname,
3533
- "generate-trend.mjs" // Keeping the filename as per your request
4687
+ "generate-trend.mjs", // Keeping the filename as per your request
3534
4688
  );
3535
4689
 
3536
4690
  const outputDir = await getOutputDir(customOutputDir);
@@ -3547,7 +4701,7 @@ async function main() {
3547
4701
  console.log(chalk.gray(` (from CLI argument)`));
3548
4702
  } else {
3549
4703
  console.log(
3550
- chalk.gray(` (auto-detected from playwright.config or using default)`)
4704
+ chalk.gray(` (auto-detected from playwright.config or using default)`),
3551
4705
  );
3552
4706
  }
3553
4707
 
@@ -3556,14 +4710,14 @@ async function main() {
3556
4710
  const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
3557
4711
  await runScript(archiveRunScriptPath, archiveArgs);
3558
4712
  console.log(
3559
- chalk.green("Current run data archiving to history completed.")
4713
+ chalk.green("Current run data archiving to history completed."),
3560
4714
  );
3561
4715
  } catch (error) {
3562
4716
  console.error(
3563
4717
  chalk.red(
3564
- "Failed to archive current run data. Report might use stale or incomplete historical trends."
4718
+ "Failed to archive current run data. Report might use stale or incomplete historical trends.",
3565
4719
  ),
3566
- error
4720
+ error,
3567
4721
  );
3568
4722
  }
3569
4723
 
@@ -3578,22 +4732,22 @@ async function main() {
3578
4732
  !currentRunReportData.results
3579
4733
  ) {
3580
4734
  throw new Error(
3581
- "Invalid report JSON structure. 'results' field is missing or invalid."
4735
+ "Invalid report JSON structure. 'results' field is missing or invalid.",
3582
4736
  );
3583
4737
  }
3584
4738
  if (!Array.isArray(currentRunReportData.results)) {
3585
4739
  currentRunReportData.results = [];
3586
4740
  console.warn(
3587
4741
  chalk.yellow(
3588
- "Warning: 'results' field in current run JSON was not an array. Treated as empty."
3589
- )
4742
+ "Warning: 'results' field in current run JSON was not an array. Treated as empty.",
4743
+ ),
3590
4744
  );
3591
4745
  }
3592
4746
  } catch (error) {
3593
4747
  console.error(
3594
4748
  chalk.red(
3595
- `Critical Error: Could not read or parse main report JSON at ${reportJsonPath}: ${error.message}`
3596
- )
4749
+ `Critical Error: Could not read or parse main report JSON at ${reportJsonPath}: ${error.message}`,
4750
+ ),
3597
4751
  );
3598
4752
  process.exit(1);
3599
4753
  }
@@ -3606,7 +4760,8 @@ async function main() {
3606
4760
 
3607
4761
  const jsonHistoryFiles = allHistoryFiles
3608
4762
  .filter(
3609
- (file) => file.startsWith(HISTORY_FILE_PREFIX) && file.endsWith(".json")
4763
+ (file) =>
4764
+ file.startsWith(HISTORY_FILE_PREFIX) && file.endsWith(".json"),
3610
4765
  )
3611
4766
  .map((file) => {
3612
4767
  const timestampPart = file
@@ -3623,7 +4778,7 @@ async function main() {
3623
4778
 
3624
4779
  const filesToLoadForTrend = jsonHistoryFiles.slice(
3625
4780
  0,
3626
- MAX_HISTORY_FILES_TO_LOAD_FOR_REPORT
4781
+ MAX_HISTORY_FILES_TO_LOAD_FOR_REPORT,
3627
4782
  );
3628
4783
 
3629
4784
  for (const fileMeta of filesToLoadForTrend) {
@@ -3634,29 +4789,29 @@ async function main() {
3634
4789
  } catch (fileReadError) {
3635
4790
  console.warn(
3636
4791
  chalk.yellow(
3637
- `Could not read/parse history file ${fileMeta.name}: ${fileReadError.message}`
3638
- )
4792
+ `Could not read/parse history file ${fileMeta.name}: ${fileReadError.message}`,
4793
+ ),
3639
4794
  );
3640
4795
  }
3641
4796
  }
3642
4797
  historicalRuns.reverse(); // Oldest first for charts
3643
4798
  console.log(
3644
4799
  chalk.green(
3645
- `Loaded ${historicalRuns.length} historical run(s) for trend analysis.`
3646
- )
4800
+ `Loaded ${historicalRuns.length} historical run(s) for trend analysis.`,
4801
+ ),
3647
4802
  );
3648
4803
  } catch (error) {
3649
4804
  if (error.code === "ENOENT") {
3650
4805
  console.warn(
3651
4806
  chalk.yellow(
3652
- `History directory '${historyDir}' not found. No historical trends will be displayed.`
3653
- )
4807
+ `History directory '${historyDir}' not found. No historical trends will be displayed.`,
4808
+ ),
3654
4809
  );
3655
4810
  } else {
3656
4811
  console.warn(
3657
4812
  chalk.yellow(
3658
- `Error loading historical data from '${historyDir}': ${error.message}`
3659
- )
4813
+ `Error loading historical data from '${historyDir}': ${error.message}`,
4814
+ ),
3660
4815
  );
3661
4816
  }
3662
4817
  }
@@ -3689,13 +4844,13 @@ async function main() {
3689
4844
  duration: test.duration,
3690
4845
  status: test.status,
3691
4846
  timestamp: new Date(test.startTime),
3692
- })
4847
+ }),
3693
4848
  );
3694
4849
  }
3695
4850
  }
3696
4851
  });
3697
4852
  trendData.overall.sort(
3698
- (a, b) => a.timestamp.getTime() - b.timestamp.getTime()
4853
+ (a, b) => a.timestamp.getTime() - b.timestamp.getTime(),
3699
4854
  );
3700
4855
  }
3701
4856
 
@@ -3705,8 +4860,8 @@ async function main() {
3705
4860
  await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
3706
4861
  console.log(
3707
4862
  chalk.green.bold(
3708
- `🎉 Pulse report generated successfully at: ${reportHtmlPath}`
3709
- )
4863
+ `🎉 Pulse report generated successfully at: ${reportHtmlPath}`,
4864
+ ),
3710
4865
  );
3711
4866
  console.log(chalk.gray(`(You can open this file in your browser)`));
3712
4867
  } catch (error) {
@@ -3717,7 +4872,7 @@ async function main() {
3717
4872
  }
3718
4873
  main().catch((err) => {
3719
4874
  console.error(
3720
- chalk.red.bold(`Unhandled error during script execution: ${err.message}`)
4875
+ chalk.red.bold(`Unhandled error during script execution: ${err.message}`),
3721
4876
  );
3722
4877
  console.error(err.stack);
3723
4878
  process.exit(1);