@arghajit/playwright-pulse-report 0.3.3 → 0.3.5

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.
@@ -5,7 +5,36 @@ import { readFileSync, existsSync as fsExistsSync } from "fs";
5
5
  import path from "path";
6
6
  import { fork } from "child_process";
7
7
  import { fileURLToPath } from "url";
8
- import { getOutputDir } from "./config-reader.mjs";
8
+ import { getReporterConfig } from "./config-reader.mjs";
9
+ import { animate } from "./terminal-logo.mjs";
10
+ import { mergeSequentialReportsIfNeeded } from "./merge-sequential-reports.mjs";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ let logo =
15
+ "data:image/png;base64,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";
16
+
17
+ // Load Highcharts content from node_modules for inlining
18
+ const highchartsPath = path.resolve(
19
+ process.cwd(),
20
+ "node_modules/highcharts/highcharts.js",
21
+ );
22
+ let highchartsContent = "";
23
+ try {
24
+ highchartsContent = readFileSync(highchartsPath, "utf8");
25
+ } catch (e) {
26
+ // If not found in process.cwd(), try relative to the script
27
+ try {
28
+ highchartsContent = readFileSync(
29
+ path.resolve(__dirname, "../node_modules/highcharts/highcharts.js"),
30
+ "utf8",
31
+ );
32
+ } catch (e2) {
33
+ console.warn(
34
+ "Highcharts could not be loaded from node_modules. Falling back to CDN.",
35
+ );
36
+ }
37
+ }
9
38
 
10
39
  // Use dynamic import for chalk as it's ESM only
11
40
  let chalk;
@@ -24,7 +53,6 @@ try {
24
53
  }
25
54
  // Default configuration
26
55
  const DEFAULT_OUTPUT_DIR = "pulse-report";
27
- const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
28
56
  const DEFAULT_HTML_FILE = "playwright-pulse-report.html";
29
57
  // Helper functions
30
58
  export function ansiToHtml(text) {
@@ -294,6 +322,12 @@ function generateTestTrendsChart(trendData) {
294
322
  color: "var(--warning-color)",
295
323
  marker: { symbol: "circle" },
296
324
  },
325
+ {
326
+ name: "Flaky",
327
+ data: runs.map((r) => r.flaky || 0),
328
+ color: "#00ccd3",
329
+ marker: { symbol: "circle" },
330
+ },
297
331
  ];
298
332
  const runsForTooltip = runs.map((r) => ({
299
333
  runId: r.runId,
@@ -480,6 +514,9 @@ function generateTestHistoryChart(history) {
480
514
  case "skipped":
481
515
  color = "var(--warning-color)";
482
516
  break;
517
+ case "flaky":
518
+ color = "var(--neutral-500)";
519
+ break;
483
520
  default:
484
521
  color = "var(--dark-gray-color)";
485
522
  }
@@ -589,6 +626,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
589
626
  case "Failed":
590
627
  color = "var(--danger-color)";
591
628
  break;
629
+ case "Flaky":
630
+ color = "#00ccd3";
631
+ break;
592
632
  case "Skipped":
593
633
  color = "var(--warning-color)";
594
634
  break;
@@ -688,21 +728,175 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
688
728
  </div>
689
729
  `;
690
730
  }
691
- function generateEnvironmentDashboard(environment) {
692
- // Format memory for display
693
- const formattedMemory = environment.memory.replace(/(\d+\.\d{2})GB/, "$1 GB");
694
-
695
- // Generate a unique ID for the dashboard
696
- const dashboardId = `envDashboard-${Date.now()}-${Math.random()
697
- .toString(36)
698
- .substring(2, 7)}`;
731
+ function generateEnvironmentSection(environmentData) {
732
+ if (!environmentData) {
733
+ return '<div class="no-data">Environment data not available.</div>';
734
+ }
735
+
736
+ if (Array.isArray(environmentData)) {
737
+ return `
738
+ <div class="sharded-env-section">
739
+ <div class="sharded-env-header">
740
+ <div class="sharded-env-title-row">
741
+ <div>
742
+ <div class="sharded-env-title">
743
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
744
+ <rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
745
+ <rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
746
+ <line x1="6" x2="6.01" y1="6" y2="6"></line>
747
+ <line x1="6" x2="6.01" y1="18" y2="18"></line>
748
+ </svg>
749
+ System Information
750
+ </div>
751
+ <div class="sharded-env-subtitle">Test execution environment details - ${environmentData.length} shard${environmentData.length > 1 ? "s" : ""}</div>
752
+ </div>
753
+ <div class="env-icon-badge">
754
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
755
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
756
+ </svg>
757
+ </div>
758
+ </div>
759
+ </div>
760
+ <div class="sharded-environments-container">
761
+ <div class="sharded-environments-wrapper">
762
+ ${environmentData
763
+ .map(
764
+ (env, index) => `
765
+ <div class="env-card-wrapper">
766
+ <div class="env-card-badge">Shard ${index + 1}</div>
767
+ ${generateEnvironmentDashboard(env, true)}
768
+ </div>
769
+ `,
770
+ )
771
+ .join("")}
772
+ </div>
773
+ </div>
774
+ </div>
775
+ <style>
776
+ .sharded-env-section {
777
+ border: 1px solid #e2e8f0;
778
+ border-radius: 12px;
779
+ background: #fafbfc;
780
+ overflow: hidden;
781
+ }
782
+ .sharded-env-header {
783
+ position: sticky;
784
+ top: 0;
785
+ z-index: 20;
786
+ background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
787
+ border-bottom: 1px solid #e2e8f0;
788
+ padding: 24px 24px 16px;
789
+ }
790
+ .sharded-env-title-row {
791
+ display: flex;
792
+ justify-content: space-between;
793
+ align-items: center;
794
+ }
795
+ .sharded-env-title {
796
+ display: flex;
797
+ align-items: center;
798
+ font-size: 18px;
799
+ font-weight: 600;
800
+ color: #0f172a;
801
+ }
802
+ .sharded-env-title svg {
803
+ width: 18px;
804
+ height: 18px;
805
+ margin-right: 8px;
806
+ stroke: currentColor;
807
+ fill: none;
808
+ }
809
+ .sharded-env-subtitle {
810
+ font-size: 13px;
811
+ color: #64748b;
812
+ margin-top: 4px;
813
+ }
814
+ .sharded-environments-container {
815
+ max-height: 520px;
816
+ overflow-y: auto;
817
+ overflow-x: hidden;
818
+ padding: 16px;
819
+ }
820
+ .sharded-environments-container::-webkit-scrollbar {
821
+ width: 8px;
822
+ }
823
+ .sharded-environments-container::-webkit-scrollbar-track {
824
+ background: #f1f1f1;
825
+ border-radius: 4px;
826
+ }
827
+ .sharded-environments-container::-webkit-scrollbar-thumb {
828
+ background: #cbd5e0;
829
+ border-radius: 4px;
830
+ }
831
+ .sharded-environments-container::-webkit-scrollbar-thumb:hover {
832
+ background: #a0aec0;
833
+ }
834
+ .sharded-environments-wrapper {
835
+ display: grid;
836
+ grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
837
+ gap: 24px;
838
+ }
839
+ @media (max-width: 768px) {
840
+ .sharded-environments-wrapper {
841
+ grid-template-columns: 1fr;
842
+ }
843
+ }
844
+ .env-card-wrapper {
845
+ position: relative;
846
+ }
847
+ .env-card-badge {
848
+ position: absolute;
849
+ top: -10px;
850
+ right: 16px;
851
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
852
+ color: white;
853
+ padding: 6px 14px;
854
+ border-radius: 20px;
855
+ font-size: 0.75em;
856
+ font-weight: 700;
857
+ text-transform: uppercase;
858
+ letter-spacing: 0.5px;
859
+ z-index: 10;
860
+ box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
861
+ }
862
+ </style>
863
+ `;
864
+ }
865
+
866
+ return generateEnvironmentDashboard(environmentData);
867
+ }
699
868
 
700
- // Logic for Run Context
869
+ function generateEnvironmentDashboard(environment, hideHeader = false) {
870
+ const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
871
+ const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
872
+ const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
873
+ const osInfo = environment.os || "N/A";
874
+ const nodeInfo = environment.node || "N/A";
875
+ const v8Info = environment.v8 || "N/A";
876
+ const cwdInfo = environment.cwd || "N/A";
877
+ const formattedMemory = environment.memory || "N/A";
701
878
  const runContext = process.env.CI ? "CI" : "Local Test";
702
879
 
703
880
  return `
704
- <div class="environment-dashboard-wrapper" id="${dashboardId}">
881
+ <div class="env-modern-card${hideHeader ? " no-header" : ""}">
705
882
  <style>
883
+ .env-modern-card {
884
+ background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
885
+ border: 0;
886
+ border-radius: 12px;
887
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
888
+ margin-top: 24px;
889
+ transition: all 0.3s ease;
890
+ font-family: var(--font-family);
891
+ overflow: hidden;
892
+ }
893
+ .env-modern-card:hover {
894
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
895
+ }
896
+ .env-modern-card {
897
+ margin-bottom: 0;
898
+ }
899
+
706
900
  .environment-dashboard-wrapper *,
707
901
  .environment-dashboard-wrapper *::before,
708
902
  .environment-dashboard-wrapper *::after {
@@ -726,279 +920,269 @@ function generateEnvironmentDashboard(environment) {
726
920
  transform: translateZ(0);
727
921
  }
728
922
 
729
- @media (max-width: 768px) {
730
- .environment-dashboard-wrapper {
731
- grid-template-columns: 1fr;
732
- padding: 32px 24px;
733
- }
923
+ .env-card-header {
924
+ display: flex;
925
+ flex-direction: column;
926
+ padding: 24px 24px 12px;
734
927
  }
735
- @media (max-width: 480px) {
736
- .environment-dashboard-wrapper {
737
- padding: 24px;
738
- }
928
+ .env-modern-card.no-header .env-card-header {
929
+ display: none;
739
930
  }
740
-
741
- .env-dashboard-header {
742
- grid-column: 1 / -1;
743
- margin-bottom: 24px;
931
+ .env-modern-card.no-header {
932
+ margin-top: 0;
744
933
  }
745
-
746
- .env-dashboard-title {
747
- font-size: 2em;
748
- font-weight: 900;
749
- color: #0f172a;
750
- letter-spacing: -0.02em;
751
- margin: 0 0 8px 0;
934
+ .env-modern-card.no-header .env-card-content {
935
+ padding-top: 24px;
752
936
  }
753
-
754
- .env-dashboard-subtitle {
755
- font-size: 1.05em;
756
- color: #64748b;
757
- margin: 0;
758
- font-weight: 400;
937
+ .env-card-title-row {
938
+ display: flex;
939
+ justify-content: space-between;
940
+ align-items: center;
759
941
  }
760
-
761
- .env-card {
762
- background: white;
763
- border: none;
764
- border-left: 4px solid #e2e8f0;
765
- padding: 28px;
942
+ .env-card-title {
766
943
  display: flex;
767
- flex-direction: column;
768
- gap: 20px;
769
- transition: all 0.12s ease;
770
- transform: translateZ(0);
944
+ align-items: center;
945
+ font-size: 16px;
946
+ font-weight: 600;
947
+ color: #0f172a;
948
+ transition: color 0.3s;
771
949
  }
772
-
773
- .env-card:hover {
774
- border-left-color: var(--primary-color);
775
- background: #fafbfc;
950
+ .env-modern-card:hover .env-card-title {
951
+ color: #6366f1;
776
952
  }
777
-
778
- .env-card-header {
779
- font-weight: 700;
780
- font-size: 1.05em;
781
- color: #0f172a;
953
+ .env-card-title svg {
954
+ width: 16px;
955
+ height: 16px;
956
+ margin-right: 8px;
957
+ stroke: currentColor;
958
+ fill: none;
959
+ }
960
+ .env-card-subtitle {
961
+ font-size: 12px;
962
+ color: #64748b;
963
+ margin-top: 4px;
964
+ }
965
+ .env-icon-badge {
966
+ width: 36px;
967
+ height: 36px;
968
+ border-radius: 50%;
969
+ background: linear-gradient(to bottom right, rgba(99, 102, 241, 0.1), rgba(99, 102, 241, 0.05));
782
970
  display: flex;
783
971
  align-items: center;
784
- gap: 10px;
785
- text-transform: uppercase;
786
- letter-spacing: 0.5px;
972
+ justify-content: center;
787
973
  }
788
-
789
- .env-card-header svg {
790
- width: 18px;
791
- height: 18px;
792
- fill: #6366f1;
974
+ .env-icon-badge svg {
975
+ width: 16px;
976
+ height: 16px;
977
+ stroke: #6366f1;
978
+ fill: none;
793
979
  }
794
-
795
980
  .env-card-content {
796
- display: flex;
797
- flex-direction: column;
798
- gap: 16px;
799
- }
800
-
801
- .env-detail-row {
802
- display: flex;
803
- justify-content: space-between;
804
- align-items: flex-start;
805
- gap: 16px;
806
- font-size: 1em;
807
- padding: 8px 0;
981
+ padding: 0 24px 24px;
808
982
  }
809
-
810
- .env-detail-label {
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;
983
+ .env-items-grid {
984
+ display: grid;
985
+ grid-template-columns: repeat(2, 1fr);
986
+ gap: 10px;
817
987
  }
818
-
819
- .env-detail-value {
820
- color: #0f172a;
821
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
822
- font-size: 0.95em;
823
- text-align: right;
824
- word-break: break-word;
825
- margin-left: auto;
988
+ @media (min-width: 768px) {
989
+ .env-items-grid {
990
+ grid-template-columns: repeat(4, 1fr);
991
+ }
826
992
  }
827
-
828
- .env-chip {
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;
993
+ .env-item {
994
+ display: flex;
995
+ align-items: flex-start;
996
+ gap: 8px;
997
+ padding: 8px;
998
+ border-radius: 8px;
999
+ transition: background-color 0.2s;
1000
+ min-height: 48px;
837
1001
  }
838
-
839
- .env-chip-primary {
840
- background-color: #ede9fe;
841
- color: #6366f1;
1002
+ .env-item:hover {
1003
+ background-color: rgba(100, 116, 139, 0.05);
842
1004
  }
843
-
844
- .env-chip-success {
845
- background-color: #d1fae5;
846
- color: #10b981;
1005
+ .env-item-icon {
1006
+ flex-shrink: 0;
847
1007
  }
848
-
849
- .env-chip-warning {
850
- background-color: #fef3c7;
851
- color: #f59e0b;
1008
+ .env-item-icon svg {
1009
+ width: 16px;
1010
+ height: 16px;
1011
+ stroke: #6366f1;
1012
+ fill: none;
852
1013
  }
853
-
854
- .env-cpu-cores {
855
- display: flex;
856
- align-items: center;
857
- gap: 6px;
1014
+ .env-item-content {
1015
+ flex-grow: 1;
1016
+ min-width: 0;
858
1017
  }
859
-
860
- .env-core-indicator {
861
- width: 12px;
862
- height: 12px;
863
- border-radius: 50%;
864
- background-color: var(--success-color);
865
- border: 1px solid rgba(0,0,0,0.1);
1018
+ .env-item-label {
1019
+ font-size: 12px;
1020
+ font-weight: 500;
1021
+ color: #64748b;
1022
+ white-space: nowrap;
1023
+ overflow: hidden;
1024
+ text-overflow: ellipsis;
866
1025
  }
867
-
868
- .env-core-indicator.inactive {
869
- background-color: var(--border-light-color);
870
- opacity: 0.7;
871
- border-color: var(--border-color);
1026
+ .env-item-value {
1027
+ font-size: 12px;
1028
+ font-weight: 600;
1029
+ color: #0f172a;
1030
+ word-wrap: break-word;
1031
+ overflow-wrap: break-word;
1032
+ line-height: 1.4;
872
1033
  }
873
1034
  </style>
874
1035
 
875
- <div class="env-dashboard-header">
876
- <div>
877
- <h3 class="env-dashboard-title">System Environment</h3>
878
- <p class="env-dashboard-subtitle">Snapshot of the execution environment</p>
879
- </div>
880
- <span class="env-chip env-chip-primary">${environment.host}</span>
881
- </div>
882
-
883
- <div class="env-card">
884
- <div class="env-card-header">
885
- <svg viewBox="0 0 24 24"><path d="M4 6h16V4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h-2v10H4V6zm18-2h-4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2H6a2 2 0 0 0-2 2v2h20V6a2 2 0 0 0-2-2zM8 12h8v2H8v-2zm0 4h8v2H8v-2z"/></svg>
886
- Hardware
887
- </div>
888
- <div class="env-card-content">
889
- <div class="env-detail-row">
890
- <span class="env-detail-label">CPU Model</span>
891
- <span class="env-detail-value">${environment.cpu.model}</span>
892
- </div>
893
- <div class="env-detail-row">
894
- <span class="env-detail-label">CPU Cores</span>
895
- <span class="env-detail-value">
896
- <div class="env-cpu-cores">
897
- <span>${environment.cpu.cores || "N/A"} core${environment.cpu.cores !== 1 ? "s" : ""}</span>
898
- </div>
899
- </span>
1036
+ <div class="env-card-header">
1037
+ <div class="env-card-title-row">
1038
+ <div>
1039
+ <div class="env-card-title">
1040
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1041
+ <rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
1042
+ <rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
1043
+ <line x1="6" x2="6.01" y1="6" y2="6"></line>
1044
+ <line x1="6" x2="6.01" y1="18" y2="18"></line>
1045
+ </svg>
1046
+ System Information
1047
+ </div>
1048
+ <div class="env-card-subtitle">Test execution environment details</div>
900
1049
  </div>
901
- <div class="env-detail-row">
902
- <span class="env-detail-label">Memory</span>
903
- <span class="env-detail-value">${formattedMemory}</span>
1050
+ <div class="env-icon-badge">
1051
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1052
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
1053
+ </svg>
904
1054
  </div>
905
1055
  </div>
906
1056
  </div>
907
1057
 
908
- <div class="env-card">
909
- <div class="env-card-header">
910
- <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-0.01 18c-2.76 0-5.26-1.12-7.07-2.93A7.973 7.973 0 0 1 4 12c0-2.21.9-4.21 2.36-5.64A7.994 7.994 0 0 1 11.99 4c4.41 0 8 3.59 8 8 0 2.76-1.12 5.26-2.93 7.07A7.973 7.973 0 0 1 11.99 20zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
911
- Operating System
912
- </div>
913
- <div class="env-card-content">
914
- <div class="env-detail-row">
915
- <span class="env-detail-label">OS Type</span>
916
- <span class="env-detail-value">${
917
- environment.os.split(" ")[0] === "darwin"
918
- ? "darwin (macOS)"
919
- : environment.os.split(" ")[0] || "Unknown"
920
- }</span>
921
- </div>
922
- <div class="env-detail-row">
923
- <span class="env-detail-label">OS Version</span>
924
- <span class="env-detail-value">${
925
- environment.os.split(" ")[1] || "N/A"
926
- }</span>
927
- </div>
928
- <div class="env-detail-row">
929
- <span class="env-detail-label">Hostname</span>
930
- <span class="env-detail-value" title="${environment.host}">${
931
- environment.host
932
- }</span>
1058
+ <div class="env-card-content">
1059
+ <div class="env-items-grid">
1060
+ <div class="env-item">
1061
+ <div class="env-item-icon">
1062
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1063
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
1064
+ </svg>
1065
+ </div>
1066
+ <div class="env-item-content">
1067
+ <p class="env-item-label">Host</p>
1068
+ <div class="env-item-value" title="${environment.host}">${environment.host}</div>
1069
+ </div>
933
1070
  </div>
934
- </div>
935
- </div>
936
-
937
- <div class="env-card">
938
- <div class="env-card-header">
939
- <svg viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
940
- Node.js Runtime
941
- </div>
942
- <div class="env-card-content">
943
- <div class="env-detail-row">
944
- <span class="env-detail-label">Node Version</span>
945
- <span class="env-detail-value">${environment.node}</span>
1071
+
1072
+ <div class="env-item">
1073
+ <div class="env-item-icon">
1074
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1075
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
1076
+ </svg>
1077
+ </div>
1078
+ <div class="env-item-content">
1079
+ <p class="env-item-label">Os</p>
1080
+ <div class="env-item-value" title="${environment.os}">${environment.os}</div>
1081
+ </div>
946
1082
  </div>
947
- <div class="env-detail-row">
948
- <span class="env-detail-label">V8 Engine</span>
949
- <span class="env-detail-value">${environment.v8}</span>
1083
+
1084
+ <div class="env-item">
1085
+ <div class="env-item-icon">
1086
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1087
+ <rect width="16" height="16" x="4" y="4" rx="2"></rect>
1088
+ <rect width="6" height="6" x="9" y="9" rx="1"></rect>
1089
+ <path d="M15 2v2"></path>
1090
+ <path d="M15 20v2"></path>
1091
+ <path d="M2 15h2"></path>
1092
+ <path d="M2 9h2"></path>
1093
+ <path d="M20 15h2"></path>
1094
+ <path d="M20 9h2"></path>
1095
+ <path d="M9 2v2"></path>
1096
+ <path d="M9 20v2"></path>
1097
+ </svg>
1098
+ </div>
1099
+ <div class="env-item-content">
1100
+ <p class="env-item-label">Cpu</p>
1101
+ <div class="env-item-value" title='${JSON.stringify(environment.cpu)}'>${cpuInfo}</div>
1102
+ </div>
950
1103
  </div>
951
- <div class="env-detail-row">
952
- <span class="env-detail-label">Working Dir</span>
953
- <span class="env-detail-value" title="${environment.cwd}">${
954
- environment.cwd.length > 25
955
- ? "..." + environment.cwd.slice(-22)
956
- : environment.cwd
957
- }</span>
1104
+
1105
+ <div class="env-item">
1106
+ <div class="env-item-icon">
1107
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1108
+ <path d="M6 19v-3"></path>
1109
+ <path d="M10 19v-3"></path>
1110
+ <path d="M14 19v-3"></path>
1111
+ <path d="M18 19v-3"></path>
1112
+ <path d="M8 11V9"></path>
1113
+ <path d="M16 11V9"></path>
1114
+ <path d="M12 11V9"></path>
1115
+ <path d="M2 15h20"></path>
1116
+ <path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z"></path>
1117
+ </svg>
1118
+ </div>
1119
+ <div class="env-item-content">
1120
+ <p class="env-item-label">Memory</p>
1121
+ <div class="env-item-value" title="${environment.memory}">${environment.memory}</div>
1122
+ </div>
958
1123
  </div>
959
- </div>
960
- </div>
961
-
962
- <div class="env-card">
963
- <div class="env-card-header">
964
- <svg viewBox="0 0 24 24"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 8.69 9.48 7 12 7c2.76 0 5 2.24 5 5v1h2c1.66 0 3 1.34 3 3s-1.34 3-3 3z"/></svg>
965
- System Summary
966
- </div>
967
- <div class="env-card-content">
968
- <div class="env-detail-row">
969
- <span class="env-detail-label">Platform Arch</span>
970
- <span class="env-detail-value">
971
- <span class="env-chip ${
972
- environment.os.includes("darwin") &&
973
- environment.cpu.model.toLowerCase().includes("apple")
974
- ? "env-chip-success"
975
- : "env-chip-warning"
976
- }">
977
- ${
978
- environment.os.includes("darwin") &&
979
- environment.cpu.model.toLowerCase().includes("apple")
980
- ? "Apple Silicon"
981
- : environment.cpu.model.toLowerCase().includes("arm") ||
982
- environment.cpu.model.toLowerCase().includes("aarch64")
983
- ? "ARM-based"
984
- : "x86/Other"
985
- }
986
- </span>
987
- </span>
1124
+
1125
+ <div class="env-item">
1126
+ <div class="env-item-icon">
1127
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1128
+ <path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
1129
+ <path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
1130
+ <path d="M12 2v2"></path>
1131
+ <path d="M12 22v-2"></path>
1132
+ <path d="m17 20.66-1-1.73"></path>
1133
+ <path d="M11 10.27 7 3.34"></path>
1134
+ <path d="m20.66 17-1.73-1"></path>
1135
+ <path d="m3.34 7 1.73 1"></path>
1136
+ <path d="M14 12h8"></path>
1137
+ <path d="M2 12h2"></path>
1138
+ <path d="m20.66 7-1.73 1"></path>
1139
+ <path d="m3.34 17 1.73-1"></path>
1140
+ <path d="m17 3.34-1 1.73"></path>
1141
+ <path d="m11 13.73-4 6.93"></path>
1142
+ </svg>
1143
+ </div>
1144
+ <div class="env-item-content">
1145
+ <p class="env-item-label">Node</p>
1146
+ <div class="env-item-value" title="${environment.node}">${environment.node}</div>
1147
+ </div>
988
1148
  </div>
989
- <div class="env-detail-row">
990
- <span class="env-detail-label">Memory per Core</span>
991
- <span class="env-detail-value">${
992
- environment.cpu.cores > 0
993
- ? (
994
- parseFloat(environment.memory) / environment.cpu.cores
995
- ).toFixed(2) + " GB"
996
- : "N/A"
997
- }</span>
1149
+
1150
+ <div class="env-item">
1151
+ <div class="env-item-icon">
1152
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1153
+ <path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
1154
+ <path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
1155
+ <path d="M12 2v2"></path>
1156
+ <path d="M12 22v-2"></path>
1157
+ <path d="m17 20.66-1-1.73"></path>
1158
+ <path d="M11 10.27 7 3.34"></path>
1159
+ <path d="m20.66 17-1.73-1"></path>
1160
+ <path d="m3.34 7 1.73 1"></path>
1161
+ <path d="M14 12h8"></path>
1162
+ <path d="M2 12h2"></path>
1163
+ <path d="m20.66 7-1.73 1"></path>
1164
+ <path d="m3.34 17 1.73-1"></path>
1165
+ <path d="m17 3.34-1 1.73"></path>
1166
+ <path d="m11 13.73-4 6.93"></path>
1167
+ </svg>
1168
+ </div>
1169
+ <div class="env-item-content">
1170
+ <p class="env-item-label">V8</p>
1171
+ <div class="env-item-value" title="${environment.v8}">${environment.v8}</div>
1172
+ </div>
998
1173
  </div>
999
- <div class="env-detail-row">
1000
- <span class="env-detail-label">Run Context</span>
1001
- <span class="env-detail-value">${runContext}</span>
1174
+
1175
+ <div class="env-item">
1176
+ <div class="env-item-icon">
1177
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1178
+ <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
1179
+ <polyline points="9 22 9 12 15 12 15 22"></polyline>
1180
+ </svg>
1181
+ </div>
1182
+ <div class="env-item-content">
1183
+ <p class="env-item-label">Working Dir</p>
1184
+ <div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
1185
+ </div>
1002
1186
  </div>
1003
1187
  </div>
1004
1188
  </div>
@@ -1021,11 +1205,11 @@ function generateWorkerDistributionChart(results) {
1021
1205
  const workerId =
1022
1206
  typeof test.workerId !== "undefined" ? test.workerId : "N/A";
1023
1207
  if (!acc[workerId]) {
1024
- acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
1208
+ acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
1025
1209
  }
1026
1210
 
1027
1211
  const status = String(test.status).toLowerCase();
1028
- if (status === "passed" || status === "failed" || status === "skipped") {
1212
+ if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
1029
1213
  acc[workerId][status]++;
1030
1214
  }
1031
1215
 
@@ -1070,12 +1254,14 @@ function generateWorkerDistributionChart(results) {
1070
1254
  const passedData = workerIds.map((id) => workerData[id].passed);
1071
1255
  const failedData = workerIds.map((id) => workerData[id].failed);
1072
1256
  const skippedData = workerIds.map((id) => workerData[id].skipped);
1257
+ const flakyData = workerIds.map((id) => workerData[id].flaky);
1073
1258
 
1074
1259
  const categoriesString = JSON.stringify(categories);
1075
1260
  const fullDataString = JSON.stringify(fullWorkerData);
1076
1261
  const seriesString = JSON.stringify([
1077
1262
  { name: "Passed", data: passedData, color: "var(--success-color)" },
1078
1263
  { name: "Failed", data: failedData, color: "var(--danger-color)" },
1264
+ { name: "Flaky", data: flakyData, color: "#00ccd3" },
1079
1265
  { name: "Skipped", data: skippedData, color: "var(--warning-color)" },
1080
1266
  ]);
1081
1267
 
@@ -1084,33 +1270,39 @@ function generateWorkerDistributionChart(results) {
1084
1270
  <style>
1085
1271
  .worker-modal-overlay {
1086
1272
  position: fixed; z-index: 1050; left: 0; top: 0; width: 100%; height: 100%;
1087
- overflow: auto; background-color: rgba(0,0,0,0.6);
1273
+ overflow: auto; background-color: rgba(0,0,0,0.85);
1088
1274
  display: none; align-items: center; justify-content: center;
1275
+ backdrop-filter: blur(4px);
1276
+ -webkit-backdrop-filter: blur(4px);
1089
1277
  }
1090
1278
  .worker-modal-content {
1091
- background-color: #3d4043;
1092
- color: var(--card-background-color);
1093
- margin: auto; padding: 20px; border: 1px solid var(--border-color, #888);
1094
- width: 80%; max-width: 700px; border-radius: 8px;
1095
- position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
1279
+ background-color: var(--bg-card, #ffffff);
1280
+ color: var(--text-color, #1f2937);
1281
+ padding: 30px; border: 1px solid var(--border-color, #e5e7eb);
1282
+ width: 80%; max-width: 700px; border-radius: 12px;
1283
+ position: relative; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
1284
+ flex-shrink: 0; margin: 20px;
1285
+ z-index: 1051;
1286
+ transform: translateZ(0); /* Force hardware acceleration/composite layer in safari */
1287
+ -webkit-transform: translateZ(0);
1096
1288
  }
1097
1289
  .worker-modal-close {
1098
1290
  position: absolute;
1099
- top: 15px;
1291
+ top: 20px;
1100
1292
  right: 25px;
1101
- font-size: 32px;
1102
- font-weight: bold;
1293
+ font-size: 28px;
1294
+ font-weight: 400;
1103
1295
  cursor: pointer;
1104
1296
  line-height: 1;
1105
1297
  z-index: 10;
1106
- color: #fff;
1107
- transition: color 0.2s ease;
1298
+ color: var(--text-color-secondary, #6b7280);
1299
+ transition: color 0.2s ease, transform 0.2s ease;
1108
1300
  user-select: none;
1109
1301
  -webkit-user-select: none;
1110
1302
  }
1111
1303
  .worker-modal-close:hover, .worker-modal-close:focus {
1112
- color: #ef4444;
1113
- transform: scale(1.1);
1304
+ color: var(--danger-color, #ef4444);
1305
+ transform: scale(1.15);
1114
1306
  }
1115
1307
  #worker-modal-body-${chartId} ul {
1116
1308
  list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
@@ -1138,18 +1330,21 @@ function generateWorkerDistributionChart(results) {
1138
1330
  <div id="worker-modal-${chartId}" class="worker-modal-overlay">
1139
1331
  <div class="worker-modal-content">
1140
1332
  <span class="worker-modal-close" onclick="window.${modalJsNamespace}.close?.()">×</span>
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>
1333
+ <h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color, #1f2937)"></h3>
1142
1334
  <div id="worker-modal-body-${chartId}"></div>
1143
1335
  </div>
1144
1336
  </div>
1145
1337
 
1146
1338
  <script>
1147
1339
  // Namespace for modal functions to avoid global scope pollution
1148
- window.${modalJsNamespace} = {};
1340
+ if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
1149
1341
 
1150
1342
  window.${renderFunctionName} = function() {
1151
1343
  const chartContainer = document.getElementById('${chartId}');
1152
1344
  if (!chartContainer) { console.error("Chart container ${chartId} not found."); return; }
1345
+
1346
+ // Ensure namespace exists when rendering
1347
+ if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
1153
1348
 
1154
1349
  // --- Modal Setup ---
1155
1350
  const modal = document.getElementById('worker-modal-${chartId}');
@@ -1168,8 +1363,10 @@ function generateWorkerDistributionChart(results) {
1168
1363
  if (test.status === 'passed') color = 'var(--success-color)';
1169
1364
  else if (test.status === 'failed') color = 'var(--danger-color)';
1170
1365
  else if (test.status === 'skipped') color = 'var(--warning-color)';
1366
+ else if (test.status === 'flaky') color = '#00ccd3';
1171
1367
 
1172
- const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1368
+ // Updated escaping logic
1369
+ const escapedName = test.name.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1173
1370
  testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
1174
1371
  });
1175
1372
  } else {
@@ -1256,20 +1453,31 @@ function generateWorkerDistributionChart(results) {
1256
1453
  `;
1257
1454
  }
1258
1455
  const infoTooltip = `
1259
- <span class="info-tooltip" style="display: inline-block; margin-left: 8px;">
1456
+ <span class="info-tooltip" style="display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; vertical-align: middle;">
1260
1457
  <span class="info-icon"
1261
- style="cursor: pointer; font-size: 1.25rem;"
1262
- onclick="window.workerInfoPrompt()">ℹ️</span>
1458
+ style="cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-color-secondary, #6b7280); transition: color 0.2s ease, transform 0.2s ease;"
1459
+ onmouseover="this.style.color='var(--accent-color, #764ba2)'; this.style.transform='scale(1.1)';"
1460
+ onmouseout="this.style.color='var(--text-color-secondary, #6b7280)'; this.style.transform='scale(1)';"
1461
+ onclick="window.workerInfoPrompt()"
1462
+ title="Click to understand Worker -1">
1463
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1464
+ <circle cx="12" cy="12" r="10"></circle>
1465
+ <line x1="12" y1="16" x2="12" y2="12"></line>
1466
+ <line x1="12" y1="8" x2="12.01" y2="8"></line>
1467
+ </svg>
1468
+ </span>
1263
1469
  </span>
1264
1470
  <script>
1265
- window.workerInfoPrompt = function() {
1266
- const message = 'Why is worker -1 special?\\n\\n' +
1267
- 'Playwright assigns skipped tests to worker -1 because:\\n' +
1268
- '1. They don\\'t require browser execution\\n' +
1269
- '2. This keeps real workers focused on actual tests\\n' +
1270
- '3. Maintains clean reporting\\n\\n' +
1271
- 'This is an intentional optimization by Playwright.';
1272
- alert(message);
1471
+ if (!window.workerInfoPrompt) {
1472
+ window.workerInfoPrompt = function() {
1473
+ const message = 'Why is worker -1 special?\\n\\n' +
1474
+ 'Playwright assigns all pre-skipped tests/test.skip() to worker -1 because:\\n' +
1475
+ '1. They don\\'t require browser execution\\n' +
1476
+ '2. This keeps real workers focused on actual tests\\n' +
1477
+ '3. Maintains clean reporting\\n\\n' +
1478
+ 'This is an intentional optimization by Playwright.';
1479
+ alert(message);
1480
+ }
1273
1481
  }
1274
1482
  </script>
1275
1483
  `;
@@ -1333,6 +1541,7 @@ function generateTestHistoryContent(trendData) {
1333
1541
  <option value="">All Statuses</option>
1334
1542
  <option value="passed">Passed</option>
1335
1543
  <option value="failed">Failed</option>
1544
+ <option value="flaky">Flaky</option>
1336
1545
  <option value="skipped">Skipped</option>
1337
1546
  </select>
1338
1547
  <button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
@@ -1400,6 +1609,8 @@ function getStatusClass(status) {
1400
1609
  return "status-failed";
1401
1610
  case "skipped":
1402
1611
  return "status-skipped";
1612
+ case "flaky":
1613
+ return "status-flaky";
1403
1614
  default:
1404
1615
  return "status-unknown";
1405
1616
  }
@@ -1412,6 +1623,8 @@ function getStatusIcon(status) {
1412
1623
  return "❌";
1413
1624
  case "skipped":
1414
1625
  return "⏭️";
1626
+ case "flaky":
1627
+ return "⚠️";
1415
1628
  default:
1416
1629
  return "❓";
1417
1630
  }
@@ -1447,6 +1660,7 @@ function getSuitesData(results) {
1447
1660
  browser: browser,
1448
1661
  passed: 0,
1449
1662
  failed: 0,
1663
+ flaky: 0,
1450
1664
  skipped: 0,
1451
1665
  count: 0,
1452
1666
  statusOverall: "passed",
@@ -1454,12 +1668,15 @@ function getSuitesData(results) {
1454
1668
  }
1455
1669
  const suite = suitesMap.get(key);
1456
1670
  suite.count++;
1457
- const currentStatus = String(test.status).toLowerCase();
1671
+ let currentStatus = String(test.status).toLowerCase();
1672
+ if (test.outcome === 'flaky') currentStatus = 'flaky';
1458
1673
  if (currentStatus && suite[currentStatus] !== undefined) {
1459
1674
  suite[currentStatus]++;
1460
1675
  }
1461
1676
  if (currentStatus === "failed") suite.statusOverall = "failed";
1462
- else if (currentStatus === "skipped" && suite.statusOverall !== "failed")
1677
+ else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
1678
+ suite.statusOverall = "flaky";
1679
+ else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
1463
1680
  suite.statusOverall = "skipped";
1464
1681
  });
1465
1682
  return Array.from(suitesMap.values());
@@ -1470,10 +1687,10 @@ function generateSuitesWidget(suitesData) {
1470
1687
  return `<div class="suites-widget" style="height: 450px;"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
1471
1688
  }
1472
1689
 
1473
- // Added inline styles for height consistency with Pie Chart (approx 450px) and scrolling
1690
+ // Uses CSS classes for responsiveness instead of inline styles
1474
1691
  return `
1475
- <div class="suites-widget" style="height: 450px; display: flex; flex-direction: column;">
1476
- <div class="suites-header" style="flex-shrink: 0;">
1692
+ <div class="suites-widget fixed-height-widget">
1693
+ <div class="suites-header">
1477
1694
  <h2>Test Suites</h2>
1478
1695
  <span class="summary-badge">${
1479
1696
  suitesData.length
@@ -1483,40 +1700,40 @@ function generateSuitesWidget(suitesData) {
1483
1700
  )} tests</span>
1484
1701
  </div>
1485
1702
 
1486
- <div class="suites-grid-container" style="flex-grow: 1; overflow-y: auto; padding-right: 5px;">
1703
+ <div class="suites-grid-container">
1487
1704
  <div class="suites-grid">
1488
1705
  ${suitesData
1489
1706
  .map(
1490
1707
  (suite) => `
1491
1708
  <div class="suite-card status-${suite.statusOverall}">
1492
1709
  <div class="suite-card-header">
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">
1501
- <span class="test-count">${suite.count} test${
1502
- suite.count !== 1 ? "s" : ""
1503
- }</span>
1710
+ <h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
1711
+ <div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
1712
+ </div>
1713
+
1714
+ <div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
1715
+ <span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
1716
+ </div>
1717
+
1718
+ <div class="suite-card-body">
1719
+ <span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
1504
1720
  <div class="suite-stats">
1505
- ${
1506
- suite.passed > 0
1507
- ? `<span class="stat-passed" title="Passed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg> ${suite.passed}</span>`
1508
- : ""
1509
- }
1510
- ${
1511
- suite.failed > 0
1512
- ? `<span class="stat-failed" title="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg> ${suite.failed}</span>`
1513
- : ""
1514
- }
1515
- ${
1516
- suite.skipped > 0
1517
- ? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg> ${suite.skipped}</span>`
1518
- : ""
1519
- }
1721
+ <span class="stat-pill passed" title="Passed">
1722
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>
1723
+ ${suite.passed}
1724
+ </span>
1725
+ <span class="stat-pill failed" title="Failed">
1726
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>
1727
+ ${suite.failed}
1728
+ </span>
1729
+ <span class="stat-pill flaky" title="Flaky">
1730
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
1731
+ ${suite.flaky || 0}
1732
+ </span>
1733
+ <span class="stat-pill skipped" title="Skipped">
1734
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
1735
+ ${suite.skipped}
1736
+ </span>
1520
1737
  </div>
1521
1738
  </div>
1522
1739
  </div>`,
@@ -1917,6 +2134,7 @@ function generateSeverityDistributionChart(results) {
1917
2134
  const data = {
1918
2135
  passed: [0, 0, 0, 0, 0],
1919
2136
  failed: [0, 0, 0, 0, 0],
2137
+ flaky: [0, 0, 0, 0, 0],
1920
2138
  skipped: [0, 0, 0, 0, 0],
1921
2139
  };
1922
2140
 
@@ -1935,6 +2153,8 @@ function generateSeverityDistributionChart(results) {
1935
2153
  status === "interrupted"
1936
2154
  ) {
1937
2155
  data.failed[index]++;
2156
+ } else if (status === "flaky") {
2157
+ data.flaky[index]++;
1938
2158
  } else {
1939
2159
  data.skipped[index]++;
1940
2160
  }
@@ -1948,6 +2168,7 @@ function generateSeverityDistributionChart(results) {
1948
2168
  const seriesData = [
1949
2169
  { name: "Passed", data: data.passed, color: "var(--success-color)" },
1950
2170
  { name: "Failed", data: data.failed, color: "var(--danger-color)" },
2171
+ { name: "Flaky", data: data.flaky, color: "#00ccd3" },
1951
2172
  { name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
1952
2173
  ];
1953
2174
 
@@ -2061,32 +2282,85 @@ function generateHTML(reportData, trendData = null) {
2061
2282
  return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
2062
2283
  };
2063
2284
 
2064
- const totalTestsOr1 = runSummary.totalTests || 1;
2065
- const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
2066
- const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
2067
- const skipPercentage = Math.round(
2068
- ((runSummary.skipped || 0) / totalTestsOr1) * 100,
2069
- );
2285
+
2070
2286
  const avgTestDuration =
2071
2287
  runSummary.totalTests > 0
2072
2288
  ? formatDuration(runSummary.duration / runSummary.totalTests)
2073
2289
  : "0.0s";
2074
2290
 
2291
+ const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
2292
+
2075
2293
  // Calculate retry statistics
2294
+ let retriedTestsCount = 0;
2076
2295
  const totalRetried = (results || []).reduce((acc, test) => {
2077
- if (test.retries && test.retries > 0) {
2078
- return acc + 1;
2296
+ if (test.retryHistory && test.retryHistory.length > 0) {
2297
+ // Filter out any "passed" or "skipped" entries in the history
2298
+ // We only count attempts that actually failed or timed out, triggering a retry.
2299
+ const unsuccessfulRetries = test.retryHistory.filter(attempt =>
2300
+ attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
2301
+ );
2302
+ if (unsuccessfulRetries.length > 0) {
2303
+ retriedTestsCount++;
2304
+ }
2305
+ return acc + unsuccessfulRetries.length;
2079
2306
  }
2080
2307
  return acc;
2081
2308
  }, 0);
2082
2309
 
2310
+ // --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
2311
+ let calculatedPassed = 0;
2312
+ let calculatedFailed = 0;
2313
+ let calculatedSkipped = 0;
2314
+ let calculatedFlaky = 0;
2315
+ let calculatedTotal = 0;
2316
+
2317
+ (results || []).forEach(test => {
2318
+ calculatedTotal++;
2319
+ // New Logic: If outcome is 'flaky', it overrides everything.
2320
+ let statusToUse = test.status;
2321
+ if (test.outcome === 'flaky') {
2322
+ statusToUse = 'flaky';
2323
+ } else if (test.status === 'flaky') {
2324
+ // Just in case outcome wasn't set but status was (unlikely with new reporter)
2325
+ statusToUse = 'flaky';
2326
+ } else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
2327
+ statusToUse = test.final_status;
2328
+ }
2329
+
2330
+ // Update test status in place for charts
2331
+ test.status = statusToUse;
2332
+
2333
+ const s = String(statusToUse).toLowerCase();
2334
+ if (s === 'passed') calculatedPassed++;
2335
+ else if (s === 'skipped') calculatedSkipped++;
2336
+ else if (s === 'flaky') calculatedFlaky++;
2337
+ else calculatedFailed++; // failed, timedout, interrupted
2338
+ });
2339
+
2340
+ // Override runSummary counts with our calculated ones if results exist
2341
+ if (results && results.length > 0) {
2342
+ runSummary.passed = calculatedPassed;
2343
+ runSummary.failed = calculatedFailed;
2344
+ runSummary.skipped = calculatedSkipped;
2345
+ runSummary.flaky = calculatedFlaky;
2346
+ runSummary.totalTests = calculatedTotal;
2347
+ }
2348
+
2349
+ const totalTestsOr1 = runSummary.totalTests || 1;
2350
+ const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
2351
+ const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
2352
+ const skipPercentage = Math.round(
2353
+ ((runSummary.skipped || 0) / totalTestsOr1) * 100,
2354
+ );
2355
+ const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
2356
+
2357
+
2083
2358
  // Calculate browser distribution
2084
2359
  const browserStats = (results || []).reduce((acc, test) => {
2085
2360
  let browserName = "unknown";
2086
2361
  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;
2362
+ // Use full browser name
2363
+ browserName = test.browser;
2090
2364
  }
2091
2365
  acc[browserName] = (acc[browserName] || 0) + 1;
2092
2366
  return acc;
@@ -2112,6 +2386,10 @@ function generateHTML(reportData, trendData = null) {
2112
2386
  // --- Simplified Severity Badge ---
2113
2387
  const severity = test.severity || "Medium";
2114
2388
  const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
2389
+
2390
+ // --- Retry Count Badge (only show if retries occurred) ---
2391
+ const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
2392
+ const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
2115
2393
  const generateStepsHTML = (steps, depth = 0) => {
2116
2394
  if (!steps || steps.length === 0)
2117
2395
  return "<div class='no-steps'>No steps recorded for this test.</div>";
@@ -2119,17 +2397,20 @@ function generateHTML(reportData, trendData = null) {
2119
2397
  .map((step) => {
2120
2398
  const hasNestedSteps = step.steps && step.steps.length > 0;
2121
2399
  const isHook = step.hookType;
2400
+ const isFailedStep = step.isFailedStep === true;
2122
2401
  const stepClass = isHook
2123
2402
  ? `step-hook step-hook-${step.hookType}`
2124
2403
  : "";
2404
+ const failedStepClass = isFailedStep ? " failed-step-highlight" : "";
2125
2405
  const hookIndicator = isHook ? ` (${step.hookType} hook)` : "";
2406
+ const failedStepIndicator = isFailedStep ? ` <span class="failed-step-marker">⚠️ Failed at this step</span>` : "";
2126
2407
  return `
2127
- <div class="step-item" style="--depth: ${depth};">
2408
+ <div class="step-item${failedStepClass}" style="--depth: ${depth};">
2128
2409
  <div class="step-header ${stepClass}" role="button" aria-expanded="false">
2129
2410
  <span class="step-icon">${getStatusIcon(step.status)}</span>
2130
2411
  <span class="step-title">${sanitizeHTML(
2131
2412
  step.title,
2132
- )}${hookIndicator}</span>
2413
+ )}${hookIndicator}${failedStepIndicator}</span>
2133
2414
  <span class="step-duration">${formatDuration(
2134
2415
  step.duration,
2135
2416
  )}</span>
@@ -2142,6 +2423,13 @@ function generateHTML(reportData, trendData = null) {
2142
2423
  )}</div>`
2143
2424
  : ""
2144
2425
  }
2426
+ ${
2427
+ step.codeSnippet
2428
+ ? `<div class="code-snippet-section"><pre class="code-snippet">${sanitizeHTML(
2429
+ step.codeSnippet,
2430
+ )}</pre></div>`
2431
+ : ""
2432
+ }
2145
2433
  ${
2146
2434
  step.errorMessage
2147
2435
  ? `<div class="test-error-summary">
@@ -2157,7 +2445,7 @@ function generateHTML(reportData, trendData = null) {
2157
2445
  onclick="copyErrorToClipboard(this)"
2158
2446
  style="
2159
2447
  margin-top: 8px;
2160
- padding: 4px 8px;
2448
+ padding: 6px 12px;
2161
2449
  background: #f0f0f0;
2162
2450
  border: 2px solid #ccc;
2163
2451
  border-radius: 4px;
@@ -2165,6 +2453,8 @@ function generateHTML(reportData, trendData = null) {
2165
2453
  font-size: 12px;
2166
2454
  border-color: #8B0000;
2167
2455
  color: #8B0000;
2456
+ align-self: flex-end;
2457
+ width: auto;
2168
2458
  "
2169
2459
  onmouseover="this.style.background='#e0e0e0'"
2170
2460
  onmouseout="this.style.background='#f0f0f0'"
@@ -2177,54 +2467,47 @@ function generateHTML(reportData, trendData = null) {
2177
2467
  ${
2178
2468
  hasNestedSteps
2179
2469
  ? `<div class="nested-steps">${generateStepsHTML(
2180
- step.steps,
2181
- depth + 1,
2182
- )}</div>`
2183
- : ""
2184
- }
2185
- </div>
2186
- </div>`;
2187
- })
2188
- .join("");
2189
- };
2190
-
2191
- return `
2192
- <div class="test-case" data-status="${
2193
- test.status
2194
- }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
2195
- .join(",")
2196
- .toLowerCase()}">
2197
- <div class="test-case-header" role="button" aria-expanded="false">
2198
- <div class="test-case-summary">
2199
- <span class="test-case-title" title="${sanitizeHTML(
2200
- test.name,
2201
- )}">${sanitizeHTML(testTitle)}</span>
2202
- <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2203
- </div>
2204
- <div class="test-case-meta">
2205
- ${severityBadge}
2206
- ${
2207
- test.tags && test.tags.length > 0
2208
- ? test.tags
2209
- .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
2210
- .join(" ")
2211
- : ""
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>
2218
- <span class="test-duration">${formatDuration(test.duration)}</span>
2219
- </div>
2220
- </div>
2221
- <div class="test-case-content" style="display: none;">
2222
- <p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
2470
+ step.steps,
2471
+ depth + 1,
2472
+ )}</div>`
2473
+ : ""
2474
+ }
2475
+ </div>
2476
+ </div>`;
2477
+ })
2478
+ .join("");
2479
+ };
2480
+
2481
+ // Helper for Tab Badges
2482
+ const getSmallStatusBadge = (status) => {
2483
+ const s = String(status).toLowerCase();
2484
+ let colorVar = 'var(--text-tertiary)';
2485
+ if(s === 'passed') colorVar = 'var(--success-color)';
2486
+ else if(s === 'failed') colorVar = 'var(--danger-color)';
2487
+ else if(s === 'skipped') colorVar = 'var(--warning-color)';
2488
+ else if(s === 'flaky') colorVar = '#00ccd3';
2489
+
2490
+ return `<span style="
2491
+ display: inline-block;
2492
+ width: 8px;
2493
+ height: 8px;
2494
+ border-radius: 50%;
2495
+ background-color: ${colorVar};
2496
+ margin-left: 6px;
2497
+ vertical-align: middle;
2498
+ " title="${s}"></span>`;
2499
+ };
2500
+
2501
+ // Function to generate test content HTML (used for base run and retry tabs)
2502
+ const getTestContentHTML = (testData, runSuffix) => {
2503
+ const logId = `stdout-log-${test.id || index}-${runSuffix}`;
2504
+ return `
2505
+ <p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
2223
2506
  ${
2224
- test.annotations && test.annotations.length > 0
2507
+ testData.annotations && testData.annotations.length > 0
2225
2508
  ? `<div class="annotations-section" style="margin: 12px 0; padding: 12px; background-color: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); border-left: 4px solid #8b5cf6; border-radius: 4px;">
2226
2509
  <h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
2227
- ${test.annotations
2510
+ ${testData.annotations
2228
2511
  .map((annotation, idx) => {
2229
2512
  const isIssueOrBug =
2230
2513
  annotation.type === "issue" ||
@@ -2232,7 +2515,7 @@ function generateHTML(reportData, trendData = null) {
2232
2515
  const descriptionText = annotation.description || "";
2233
2516
  const typeLabel = sanitizeHTML(annotation.type);
2234
2517
  const descriptionHtml =
2235
- isIssueOrBug && descriptionText.match(/^[A-Z]+-\d+$/)
2518
+ isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
2236
2519
  ? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
2237
2520
  descriptionText,
2238
2521
  )}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
@@ -2247,7 +2530,7 @@ function generateHTML(reportData, trendData = null) {
2247
2530
  }</div>`
2248
2531
  : "";
2249
2532
  return `<div style="margin-bottom: ${
2250
- idx < test.annotations.length - 1 ? "10px" : "0"
2533
+ idx < testData.annotations.length - 1 ? "10px" : "0"
2251
2534
  };">
2252
2535
  <strong style="color: #8b5cf6;">Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>
2253
2536
  ${
@@ -2263,21 +2546,21 @@ function generateHTML(reportData, trendData = null) {
2263
2546
  : ""
2264
2547
  }
2265
2548
  <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
2266
- test.workerId,
2549
+ testData.workerId,
2267
2550
  )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
2268
- test.totalWorkers,
2551
+ testData.totalWorkers,
2269
2552
  )}]</p>
2270
2553
  ${
2271
- test.errorMessage
2272
- ? `<div class="test-error-summary">${formatPlaywrightError(
2273
- test.errorMessage,
2274
- )}
2554
+ testData.errorMessage
2555
+ ? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
2556
+ testData.errorMessage,
2557
+ )}</div>
2275
2558
  <button
2276
2559
  class="copy-error-btn"
2277
2560
  onclick="copyErrorToClipboard(this)"
2278
2561
  style="
2279
2562
  margin-top: 8px;
2280
- padding: 4px 8px;
2563
+ padding: 6px 12px;
2281
2564
  background: #f0f0f0;
2282
2565
  border: 2px solid #ccc;
2283
2566
  border-radius: 4px;
@@ -2285,6 +2568,8 @@ function generateHTML(reportData, trendData = null) {
2285
2568
  font-size: 12px;
2286
2569
  border-color: #8B0000;
2287
2570
  color: #8B0000;
2571
+ align-self: flex-end;
2572
+ width: auto;
2288
2573
  "
2289
2574
  onmouseover="this.style.background='#e0e0e0'"
2290
2575
  onmouseout="this.style.background='#f0f0f0'"
@@ -2295,25 +2580,23 @@ function generateHTML(reportData, trendData = null) {
2295
2580
  : ""
2296
2581
  }
2297
2582
  ${
2298
- test.snippet
2583
+ testData.snippet
2299
2584
  ? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
2300
- test.snippet,
2585
+ testData.snippet,
2301
2586
  )}</code></pre></div>`
2302
2587
  : ""
2303
2588
  }
2304
2589
  <h4>Steps</h4>
2305
- <div class="steps-list">${generateStepsHTML(test.steps)}</div>
2590
+ <div class="steps-list">${generateStepsHTML(testData.steps)}</div>
2306
2591
  ${(() => {
2307
- if (!test.stdout || test.stdout.length === 0) return "";
2308
- // Create a unique ID for the <pre> element to target it for copying
2309
- const logId = `stdout-log-${test.id || index}`;
2592
+ if (!testData.stdout || testData.stdout.length === 0) return "";
2310
2593
  return `<div class="console-output-section">
2311
2594
  <h4>Console Output (stdout)
2312
- <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button>
2595
+ <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
2313
2596
  </h4>
2314
2597
  <div class="log-wrapper">
2315
2598
  <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(
2316
- test.stdout
2599
+ testData.stdout
2317
2600
  .map((line) => sanitizeHTML(line))
2318
2601
  .join("\n"),
2319
2602
  )}</pre>
@@ -2321,24 +2604,24 @@ function generateHTML(reportData, trendData = null) {
2321
2604
  </div>`;
2322
2605
  })()}
2323
2606
  ${
2324
- test.stderr && test.stderr.length > 0
2607
+ testData.stderr && testData.stderr.length > 0
2325
2608
  ? `<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(
2326
- test.stderr.map((line) => sanitizeHTML(line)).join("\n"),
2609
+ testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
2327
2610
  )}</pre></div>`
2328
2611
  : ""
2329
2612
  }
2330
2613
  ${
2331
- test.screenshots && test.screenshots.length > 0
2614
+ testData.screenshots && testData.screenshots.length > 0
2332
2615
  ? `
2333
2616
  <div class="attachments-section">
2334
2617
  <h4>Screenshots</h4>
2335
2618
  <div class="attachments-grid">
2336
- ${test.screenshots
2619
+ ${testData.screenshots
2337
2620
  .map(
2338
- (screenshot, index) => `
2621
+ (screenshot, screenshotIndex) => `
2339
2622
  <div class="attachment-item">
2340
2623
  <img src="${fixPath(screenshot)}" alt="Screenshot ${
2341
- index + 1
2624
+ screenshotIndex + 1
2342
2625
  }">
2343
2626
  <div class="attachment-info">
2344
2627
  <div class="trace-actions">
@@ -2347,7 +2630,7 @@ function generateHTML(reportData, trendData = null) {
2347
2630
  )}" target="_blank" class="view-full">View Full Image</a>
2348
2631
  <a href="${fixPath(
2349
2632
  screenshot,
2350
- )}" target="_blank" download="screenshot-${Date.now()}-${index}.png">Download</a>
2633
+ )}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
2351
2634
  </div>
2352
2635
  </div>
2353
2636
  </div>
@@ -2360,9 +2643,9 @@ function generateHTML(reportData, trendData = null) {
2360
2643
  : ""
2361
2644
  }
2362
2645
  ${
2363
- test.videoPath && test.videoPath.length > 0
2364
- ? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${test.videoPath
2365
- .map((videoUrl, index) => {
2646
+ testData.videoPath && testData.videoPath.length > 0
2647
+ ? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
2648
+ .map((videoUrl, videoIndex) => {
2366
2649
  const fixedVideoUrl = fixPath(videoUrl);
2367
2650
  const fileExtension = String(fixedVideoUrl)
2368
2651
  .split(".")
@@ -2378,7 +2661,7 @@ function generateHTML(reportData, trendData = null) {
2378
2661
  }[fileExtension] || "video/mp4";
2379
2662
  return `<div class="attachment-item video-item">
2380
2663
  <video controls width="100%" height="auto" title="Video ${
2381
- index + 1
2664
+ videoIndex + 1
2382
2665
  }">
2383
2666
  <source src="${sanitizeHTML(
2384
2667
  fixedVideoUrl,
@@ -2389,7 +2672,7 @@ function generateHTML(reportData, trendData = null) {
2389
2672
  <div class="trace-actions">
2390
2673
  <a href="${sanitizeHTML(
2391
2674
  fixedVideoUrl,
2392
- )}" target="_blank" download="video-${Date.now()}-${index}.${fileExtension}">Download</a>
2675
+ )}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
2393
2676
  </div>
2394
2677
  </div>
2395
2678
  </div>`;
@@ -2398,7 +2681,7 @@ function generateHTML(reportData, trendData = null) {
2398
2681
  : ""
2399
2682
  }
2400
2683
  ${
2401
- test.tracePath
2684
+ testData.tracePath
2402
2685
  ? `
2403
2686
  <div class="attachments-section">
2404
2687
  <h4>Trace Files</h4>
@@ -2407,15 +2690,15 @@ function generateHTML(reportData, trendData = null) {
2407
2690
  <div class="trace-preview">
2408
2691
  <span class="trace-icon">📄</span>
2409
2692
  <span class="trace-name">${sanitizeHTML(
2410
- path.basename(test.tracePath),
2693
+ path.basename(testData.tracePath),
2411
2694
  )}</span>
2412
2695
  </div>
2413
2696
  <div class="attachment-info">
2414
2697
  <div class="trace-actions">
2415
2698
  <a href="${sanitizeHTML(
2416
- fixPath(test.tracePath),
2699
+ fixPath(testData.tracePath),
2417
2700
  )}" target="_blank" download="${sanitizeHTML(
2418
- path.basename(test.tracePath),
2701
+ path.basename(testData.tracePath),
2419
2702
  )}" class="download-trace">Download Trace</a>
2420
2703
  </div>
2421
2704
  </div>
@@ -2426,12 +2709,12 @@ function generateHTML(reportData, trendData = null) {
2426
2709
  : ""
2427
2710
  }
2428
2711
  ${
2429
- test.attachments && test.attachments.length > 0
2712
+ testData.attachments && testData.attachments.length > 0
2430
2713
  ? `
2431
2714
  <div class="attachments-section">
2432
2715
  <h4>Other Attachments</h4>
2433
2716
  <div class="attachments-grid">
2434
- ${test.attachments
2717
+ ${testData.attachments
2435
2718
  .map(
2436
2719
  (attachment) => `
2437
2720
  <div class="attachment-item generic-attachment">
@@ -2467,13 +2750,76 @@ function generateHTML(reportData, trendData = null) {
2467
2750
  `
2468
2751
  : ""
2469
2752
  }
2470
- ${
2471
- test.codeSnippet
2472
- ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
2473
- sanitizeHTML(test.codeSnippet),
2474
- )}</code></pre></div>`
2475
- : ""
2476
- }
2753
+
2754
+ `;
2755
+ };
2756
+
2757
+ // Determine header status: use final_status if retried, else normal status
2758
+ const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
2759
+ ? test.final_status
2760
+ : test.status;
2761
+
2762
+ const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
2763
+ ? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
2764
+ : '';
2765
+
2766
+ return `
2767
+ <div class="test-case" data-status="${
2768
+ headerStatus
2769
+ }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
2770
+ .join(",")
2771
+ .toLowerCase()}">
2772
+ <div class="test-case-header" role="button" aria-expanded="false">
2773
+ <div class="test-case-summary">
2774
+ <span class="test-case-title" title="${sanitizeHTML(
2775
+ test.name,
2776
+ )}">${sanitizeHTML(testTitle)}</span>
2777
+ <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2778
+ </div>
2779
+ <div class="test-case-meta">
2780
+ ${severityBadge}
2781
+ ${retryBadge}
2782
+ ${outcomeBadge}
2783
+ ${
2784
+ test.tags && test.tags.length > 0
2785
+ ? test.tags
2786
+ .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
2787
+ .join(" ")
2788
+ : ""
2789
+ }
2790
+ </div>
2791
+ <div class="test-case-status-duration">
2792
+ <span class="status-badge ${getStatusClass(headerStatus)}">${String(
2793
+ headerStatus,
2794
+ ).toUpperCase()}</span>
2795
+ <span class="test-duration">${formatDuration(test.duration)}</span>
2796
+ </div>
2797
+ </div>
2798
+ <div class="test-case-content" style="display: none;">
2799
+ ${test.retryHistory && test.retryHistory.length > 0 ? `
2800
+ <div class="retry-tabs-container">
2801
+ <div class="retry-tabs-header">
2802
+ <button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
2803
+ Base Run ${getSmallStatusBadge(test.final_status || test.status)}
2804
+ </button>
2805
+ ${test.retryHistory.map((retry, idx) => `
2806
+ <button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
2807
+ Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
2808
+ </button>
2809
+ `).join('')}
2810
+ </div>
2811
+
2812
+ <div id="base-run-${test.id}" class="retry-tab-content active">
2813
+ ${getTestContentHTML(test, 'base')}
2814
+ </div>
2815
+
2816
+ ${test.retryHistory.map((retry, idx) => `
2817
+ <div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
2818
+ ${getTestContentHTML(retry, `retry-${idx + 1}`)}
2819
+ </div>
2820
+ `).join('')}
2821
+ </div>
2822
+ ` : getTestContentHTML(test, 'single')}
2477
2823
  </div>
2478
2824
  </div>`;
2479
2825
  })
@@ -2485,18 +2831,18 @@ function generateHTML(reportData, trendData = null) {
2485
2831
  <head>
2486
2832
  <meta charset="UTF-8">
2487
2833
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2488
- <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2489
- <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2834
+ <link rel="icon" type="image/png" href=${logo}>
2835
+ <link rel="apple-touch-icon" href=${logo}>
2490
2836
  <!-- Preconnect to external domains -->
2491
2837
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
2492
2838
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2493
- <link rel="preconnect" href="https://code.highcharts.com">
2494
2839
 
2495
2840
  <!-- Preload critical font -->
2841
+
2496
2842
  <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
2843
  <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
2498
2844
 
2499
- <script src="https://code.highcharts.com/highcharts.js" defer></script>
2845
+ ${highchartsContent ? `<script>${highchartsContent}</script>` : '<script src="https://code.highcharts.com/highcharts.js" defer></script>'}
2500
2846
  <title>Pulse Report</title>
2501
2847
  <style>
2502
2848
  :root {
@@ -2506,7 +2852,8 @@ function generateHTML(reportData, trendData = null) {
2506
2852
  --success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
2507
2853
  --danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
2508
2854
  --warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
2509
- --info-color: #3b82f6;
2855
+ --info-color: #3b82f6;
2856
+ --flaky-color: #00ccd3;
2510
2857
  --neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
2511
2858
  --neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
2512
2859
  --neutral-800: #262626; --neutral-900: #171717;
@@ -2524,7 +2871,9 @@ function generateHTML(reportData, trendData = null) {
2524
2871
  --glow-primary: 0 0 20px rgba(99, 102, 241, 0.4), 0 0 40px rgba(99, 102, 241, 0.2);
2525
2872
  --glow-success: 0 0 20px rgba(16, 185, 129, 0.4), 0 0 40px rgba(16, 185, 129, 0.2);
2526
2873
  --glow-danger: 0 0 20px rgba(239, 68, 68, 0.4), 0 0 40px rgba(239, 68, 68, 0.2);
2527
- }
2874
+ --bg-card: #ffffff; --bg-card-hover: #f8fafc;
2875
+ --gradient-card: linear-gradient(145deg, #ffffff 0%, #f9fafb 100%);
2876
+ --border-medium: #cbd5e1;
2528
2877
  * { margin: 0; padding: 0; box-sizing: border-box; }
2529
2878
  ::selection { background: var(--primary-color); color: white; }
2530
2879
  ::-webkit-scrollbar { width: 0; height: 0; display: none; }
@@ -2596,11 +2945,11 @@ function generateHTML(reportData, trendData = null) {
2596
2945
  display: flex;
2597
2946
  gap: 16px;
2598
2947
  align-items: stretch;
2599
- background: #ffffff;
2948
+ background: transparent;
2600
2949
  border-radius: 12px;
2601
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
2602
- border: 1px solid #e2e8f0;
2603
- overflow: hidden;
2950
+ padding: 0;
2951
+ box-shadow: var(--shadow-md); /* Inherited from base static style */
2952
+ overflow: hidden; /* Inherited */
2604
2953
  }
2605
2954
  .run-info-item {
2606
2955
  display: flex;
@@ -2609,54 +2958,61 @@ function generateHTML(reportData, trendData = null) {
2609
2958
  padding: 16px 28px;
2610
2959
  position: relative;
2611
2960
  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);
2961
+ min-width: fit-content;
2625
2962
  }
2963
+
2626
2964
  .run-info-item:first-child {
2627
- background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
2965
+ background: linear-gradient(135deg, rgba(251, 191, 36, 0.2) 0%, rgba(245, 158, 11, 0.15) 50%, rgba(217, 119, 6, 0.1) 100%);
2966
+ border: 1px solid rgba(251, 191, 36, 0.3);
2967
+ border-radius: var(--radius-md);
2968
+ box-shadow: 0 4px 16px rgba(251, 191, 36, 0.2), inset 0 1px 0 rgba(251, 191, 36, 0.25), 0 0 40px rgba(251, 191, 36, 0.08);
2969
+ }
2970
+ .run-info-item:first-child:hover {
2971
+ background: linear-gradient(135deg, rgba(251, 191, 36, 0.28) 0%, rgba(245, 158, 11, 0.22) 50%, rgba(217, 119, 6, 0.15) 100%);
2972
+ border-color: rgba(251, 191, 36, 0.4);
2973
+ box-shadow: 0 8px 24px rgba(251, 191, 36, 0.3), inset 0 1px 0 rgba(251, 191, 36, 0.35), 0 0 50px rgba(251, 191, 36, 0.15);
2628
2974
  }
2629
2975
  .run-info-item:last-child {
2630
- background: linear-gradient(135deg, #ddd6fe 0%, #c4b5fd 100%);
2976
+ background: linear-gradient(135deg, rgba(139, 92, 246, 0.18) 0%, rgba(124, 58, 237, 0.12) 50%, rgba(109, 40, 217, 0.08) 100%);
2977
+ border: 1px solid rgba(139, 92, 246, 0.3);
2978
+ border-radius: var(--radius-md);
2979
+ box-shadow: 0 4px 16px rgba(139, 92, 246, 0.2), inset 0 1px 0 rgba(139, 92, 246, 0.25), 0 0 40px rgba(139, 92, 246, 0.08);
2980
+ }
2981
+ .run-info-item:last-child:hover {
2982
+ background: linear-gradient(135deg, rgba(139, 92, 246, 0.25) 0%, rgba(124, 58, 237, 0.18) 50%, rgba(109, 40, 217, 0.12) 100%);
2983
+ border-color: rgba(139, 92, 246, 0.4);
2984
+ box-shadow: 0 8px 24px rgba(139, 92, 246, 0.3), inset 0 1px 0 rgba(139, 92, 246, 0.35), 0 0 50px rgba(139, 92, 246, 0.15);
2631
2985
  }
2632
2986
  .run-info strong {
2633
2987
  display: flex;
2634
2988
  align-items: center;
2635
- gap: 6px;
2989
+ gap: 8px;
2636
2990
  font-size: 0.7em;
2637
2991
  text-transform: uppercase;
2638
- letter-spacing: 1px;
2639
- color: #64748b;
2992
+ letter-spacing: 1.2px;
2993
+ color: #9ca3af;
2640
2994
  margin: 0;
2641
2995
  font-weight: 700;
2642
2996
  }
2643
2997
  .run-info strong::before {
2644
2998
  content: '';
2645
- width: 8px;
2646
- height: 8px;
2999
+ width: 10px;
3000
+ height: 10px;
2647
3001
  border-radius: 50%;
2648
3002
  background: currentColor;
2649
- opacity: 0.6;
3003
+ opacity: 0.7;
3004
+ box-shadow: 0 0 8px currentColor;
2650
3005
  }
2651
3006
  .run-info-item:first-child strong {
2652
- color: #92400e;
3007
+ color: var(--warning-light);
2653
3008
  }
2654
3009
  .run-info-item:last-child strong {
2655
- color: #5b21b6;
3010
+ color: var(--secondary-light);
2656
3011
  }
2657
3012
  .run-info span {
3013
+ font-size: 1.5em;
2658
3014
  font-weight: 800;
2659
- color: #0f172a;
3015
+ color: #0f172a; /* Adjusted for light theme consistency, static uses #f9fafb */
2660
3016
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
2661
3017
  letter-spacing: -0.02em;
2662
3018
  line-height: 1.2;
@@ -2720,12 +3076,17 @@ function generateHTML(reportData, trendData = null) {
2720
3076
  }
2721
3077
  }
2722
3078
 
3079
+
3080
+ .stat-pill.flaky { color: #4b5563; }
3081
+
2723
3082
  .dashboard-grid {
2724
3083
  display: grid;
2725
3084
  grid-template-columns: repeat(4, 1fr);
2726
3085
  gap: 0;
2727
3086
  margin: 0 0 40px 0;
2728
3087
  }
3088
+ .stats-pill.failed { color: var(--danger-dark); }
3089
+ .stats-pill.flaky { color: #4b5563; }
2729
3090
  .browser-breakdown {
2730
3091
  display: flex;
2731
3092
  flex-direction: column;
@@ -2766,9 +3127,17 @@ function generateHTML(reportData, trendData = null) {
2766
3127
  color: #0f172a;
2767
3128
  text-transform: capitalize;
2768
3129
  font-size: 1.05em;
3130
+ white-space: nowrap;
3131
+ overflow: hidden;
3132
+ text-overflow: ellipsis;
3133
+ flex: 1;
3134
+ min-width: 0;
3135
+ margin-right: 8px;
2769
3136
  }
2770
3137
  .browser-stats {
2771
3138
  color: #64748b;
3139
+ white-space: nowrap;
3140
+ flex-shrink: 0;
2772
3141
  font-weight: 700;
2773
3142
  font-size: 0.95em;
2774
3143
  }
@@ -2812,9 +3181,11 @@ function generateHTML(reportData, trendData = null) {
2812
3181
  align-items: flex-start;
2813
3182
  }
2814
3183
  .run-info {
3184
+ flex-direction: column;
3185
+ gap: 0;
2815
3186
  width: 100%;
2816
- justify-content: flex-start;
2817
- gap: 24px;
3187
+ border-radius: 14px;
3188
+ overflow: hidden;
2818
3189
  }
2819
3190
  .dashboard-grid {
2820
3191
  grid-template-columns: repeat(2, 1fr);
@@ -2823,11 +3194,23 @@ function generateHTML(reportData, trendData = null) {
2823
3194
  .summary-card:nth-child(n+7) { border-bottom: none; }
2824
3195
  .filters {
2825
3196
  padding: 24px;
2826
- flex-direction: column;
3197
+ flex-wrap: wrap;
3198
+ gap: 12px;
3199
+ }
3200
+ .filters input {
3201
+ flex: 1 1 auto;
3202
+ min-width: 0;
3203
+ width: auto;
3204
+ }
3205
+ .filters select {
3206
+ flex: 0 0 auto;
3207
+ min-width: 0;
3208
+ width: auto;
3209
+ }
3210
+ .filters button {
3211
+ width: auto;
3212
+ flex: 0 0 auto;
2827
3213
  }
2828
- .filters input { min-width: 100%; }
2829
- .filters select { min-width: 100%; }
2830
- .filters button { width: 100%; }
2831
3214
  .copy-btn {
2832
3215
  font-size: 0.75em;
2833
3216
  padding: 8px 16px;
@@ -3026,16 +3409,13 @@ function generateHTML(reportData, trendData = null) {
3026
3409
  display: none;
3027
3410
  }
3028
3411
  .run-info-item:not(:last-child) {
3029
- border-bottom: 1px solid var(--light-gray-color);
3412
+ border-bottom: 1px solid var(--border-medium);
3030
3413
  }
3031
- .run-info strong {
3032
- font-size: 0.65em;
3414
+ .run-info strong {
3415
+ font-size: 0.65em;
3033
3416
  }
3034
- .run-info span {
3035
- font-size: 1.1em;
3036
- white-space: normal;
3037
- word-break: break-word;
3038
- overflow-wrap: break-word;
3417
+ .run-info span {
3418
+ font-size: 1.1em;
3039
3419
  }
3040
3420
  .tabs {
3041
3421
  flex-wrap: wrap;
@@ -3151,12 +3531,19 @@ function generateHTML(reportData, trendData = null) {
3151
3531
  box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
3152
3532
  }
3153
3533
  .summary-card.status-failed .value { color: #ef4444; }
3534
+ .summary-card.status-flaky::before { background: #00ccd3; }
3154
3535
  .summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
3155
3536
  .summary-card.status-skipped:hover {
3156
3537
  background: rgba(245, 158, 11, 0.15);
3157
3538
  box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
3158
3539
  }
3159
3540
  .summary-card.status-skipped .value { color: #f59e0b; }
3541
+ .summary-card.flaky-status { background: rgba(0, 204, 211, 0.05); }
3542
+ .summary-card.flaky-status:hover {
3543
+ background: rgba(0, 204, 211, 0.15);
3544
+ box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
3545
+ }
3546
+ .summary-card.flaky-status .value { color: #00ccd3; }
3160
3547
  .summary-card:not([class*='status-']) .value { color: #0f172a; }
3161
3548
  .dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
3162
3549
  .dashboard-column {
@@ -3197,58 +3584,167 @@ function generateHTML(reportData, trendData = null) {
3197
3584
  .status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
3198
3585
  .status-badge-small-tooltip.status-skipped { background-color: var(--warning-color); }
3199
3586
  .status-badge-small-tooltip.status-unknown { background-color: var(--dark-gray-color); }
3200
- .suites-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
3587
+ .suites-header {
3588
+ flex-shrink: 0;
3589
+ display: flex;
3590
+ justify-content: space-between;
3591
+ align-items: center;
3592
+ margin-bottom: 20px;
3593
+ }
3201
3594
  .summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
3202
3595
  .suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
3203
- .suite-card {
3204
- border: none;
3205
- border-left: 4px solid #e2e8f0;
3206
- padding: 24px;
3207
- background: white;
3208
- transition: all 0.15s ease;
3596
+ .suites-widget {
3597
+ display: flex;
3598
+ flex-direction: column;
3209
3599
  }
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); }
3220
- .suite-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
3221
- .suite-name { font-weight: 600; font-size: 1.05em; color: var(--text-color); margin-right: 10px; word-break: break-word;}
3600
+ .fixed-height-widget {
3601
+ height: 450px;
3602
+ }
3603
+ .suites-grid-container {
3604
+ flex-grow: 1;
3605
+ overflow-y: auto;
3606
+ padding-right: 5px;
3607
+ }
3608
+
3609
+ @media (max-width: 768px) {
3610
+ .fixed-height-widget {
3611
+ height: auto;
3612
+ max-height: 600px;
3613
+ }
3614
+ }
3615
+ .suite-card {
3616
+ background: #ffffff;
3617
+ border: 1px solid var(--border-light);
3618
+ border-radius: 16px;
3619
+ padding: 24px;
3620
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
3621
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3622
+ display: flex;
3623
+ flex-direction: column;
3624
+ height: 100%;
3625
+ position: relative;
3626
+ overflow: hidden;
3627
+ }
3628
+ .suite-card:hover {
3629
+ transform: translateY(-4px);
3630
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
3631
+ border-color: var(--primary-light);
3632
+ }
3633
+ .suite-card::before {
3634
+ content: '';
3635
+ position: absolute;
3636
+ top: 0;
3637
+ left: 0;
3638
+ width: 100%;
3639
+ height: 4px;
3640
+ background: var(--neutral-200);
3641
+ opacity: 0.8;
3642
+ transition: background 0.3s ease;
3643
+ }
3644
+ .suite-card.status-passed::before { background: var(--success-color); }
3645
+ .suite-card.status-failed::before { background: var(--danger-color); }
3646
+ .suite-card.status-flaky::before { background: #00ccd3; }
3647
+ .suite-card.status-skipped::before { background: var(--warning-color); }
3648
+
3649
+ /* Outcome Badge */
3650
+ .outcome-badge {
3651
+ background-color: var(--secondary-color);
3652
+ color: #fff;
3653
+ padding: 2px 8px;
3654
+ border-radius: 4px;
3655
+ font-size: 0.75em;
3656
+ font-weight: 700;
3657
+ text-transform: uppercase;
3658
+ margin-right: 8px;
3659
+ letter-spacing: 0.5px;
3660
+ }
3661
+ .outcome-badge.flaky {
3662
+ background-color: #00ccd3;
3663
+ color: #fff;
3664
+ }
3665
+
3666
+ .suite-card-header {
3667
+ display: flex;
3668
+ justify-content: space-between;
3669
+ align-items: flex-start;
3670
+ margin-bottom: 16px;
3671
+ }
3672
+ .suite-name {
3673
+ font-size: 1.15em;
3674
+ font-weight: 700;
3675
+ color: var(--text-primary);
3676
+ line-height: 1.4;
3677
+ display: -webkit-box;
3678
+ -webkit-line-clamp: 2;
3679
+ -webkit-box-orient: vertical;
3680
+ overflow: hidden;
3681
+ margin-right: 12px;
3682
+ }
3683
+ .status-indicator-dot {
3684
+ width: 10px;
3685
+ height: 10px;
3686
+ border-radius: 50%;
3687
+ flex-shrink: 0;
3688
+ margin-top: 6px;
3689
+ }
3690
+ .status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
3691
+ .status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
3692
+ .status-indicator-dot.status-flaky { background-color: #00ccd3; box-shadow: 0 0 0 4px rgba(0, 204, 211, 0.15); }
3693
+ .status-indicator-dot.status-skipped { background-color: rgba(245, 158, 11, 0.1); color: var(--warning-dark); border: 1px solid rgba(245, 158, 11, 0.2); }
3694
+ .status-flaky { background-color: rgba(0, 204, 211, 0.1); color: #00ccd3; border: 1px solid #00ccd3; }
3695
+
3222
3696
  .browser-tag {
3697
+ font-size: 0.8em;
3698
+ font-weight: 600;
3699
+ background: var(--bg-secondary);
3700
+ color: var(--text-secondary);
3701
+ padding: 4px 10px;
3702
+ border-radius: 20px;
3703
+ border: 1px solid var(--border-light);
3704
+ display: inline-flex;
3705
+ align-items: center;
3706
+ gap: 6px;
3707
+ margin-bottom: 20px;
3708
+ align-self: flex-start;
3709
+ box-shadow: none;
3710
+ text-shadow: none;
3711
+ }
3712
+
3713
+ .suite-card-body {
3714
+ margin-top: auto;
3715
+ }
3716
+
3717
+ .test-count-label {
3223
3718
  font-size: 0.85em;
3224
3719
  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;
3720
+ color: var(--text-tertiary);
3721
+ text-transform: uppercase;
3722
+ letter-spacing: 0.05em;
3723
+ margin-bottom: 8px;
3724
+ display: block;
3725
+ }
3726
+
3727
+ .suite-stats {
3728
+ display: flex;
3729
+ gap: 8px;
3730
+ background: var(--bg-secondary);
3731
+ padding: 10px 14px;
3732
+ border-radius: 10px;
3733
+ justify-content: space-between;
3734
+ }
3735
+
3736
+ .stat-pill {
3737
+ display: flex;
3738
+ align-items: center;
3739
+ gap: 6px;
3740
+ font-size: 0.9em;
3741
+ font-weight: 600;
3240
3742
  }
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
- }
3245
- .suite-card-body .test-count { font-size: 0.95em; color: var(--text-color-secondary); display: block; margin-bottom: 10px; }
3246
- .suite-stats { display: flex; gap: 14px; font-size: 0.95em; align-items: center; }
3247
- .suite-stats span { display: flex; align-items: center; gap: 6px; }
3248
- .suite-stats svg { vertical-align: middle; font-size: 1.15em; }
3249
- .suite-stats .stat-passed { color: #10b981; }
3250
- .suite-stats .stat-failed { color: #ef4444; }
3251
- .suite-stats .stat-skipped { color: #f59e0b; }
3743
+ .stat-pill svg { width: 14px; height: 14px; }
3744
+ .stat-pill.passed { color: var(--success-dark); }
3745
+ .stat-pill.failed { color: var(--danger-dark); }
3746
+ .stat-pill.flaky { color: #00ccd3; }
3747
+ .stat-pill.skipped { color: var(--warning-dark); }
3252
3748
  .filters {
3253
3749
  display: flex;
3254
3750
  flex-wrap: wrap;
@@ -3280,6 +3776,7 @@ function generateHTML(reportData, trendData = null) {
3280
3776
  min-width: 180px;
3281
3777
  background: white;
3282
3778
  cursor: pointer;
3779
+ width: 100%;
3283
3780
  }
3284
3781
  .filters select:focus {
3285
3782
  outline: none;
@@ -3411,7 +3908,7 @@ function generateHTML(reportData, trendData = null) {
3411
3908
  border-radius: 0;
3412
3909
  font-size: 0.7em;
3413
3910
  font-weight: 800;
3414
- color: white;
3911
+ color: black;
3415
3912
  text-transform: uppercase;
3416
3913
  min-width: 100px;
3417
3914
  text-align: center;
@@ -3473,6 +3970,65 @@ function generateHTML(reportData, trendData = null) {
3473
3970
  border-color: rgba(148, 163, 184, 0.25);
3474
3971
  }
3475
3972
 
3973
+ /* --- RETRY COUNT BADGE --- */
3974
+ .retry-badge {
3975
+ display: inline-flex;
3976
+ align-items: center;
3977
+ padding: 5px 12px;
3978
+ border-radius: 12px;
3979
+ font-size: 0.75rem;
3980
+ font-weight: 600;
3981
+ background: rgba(147, 51, 234, 0.15);
3982
+ color: #a855f7;
3983
+ border: 1px solid rgba(147, 51, 234, 0.3);
3984
+ margin-left: 8px;
3985
+ }
3986
+
3987
+ /* --- RETRY TABS --- */
3988
+ .retry-tabs-container {
3989
+ margin-top: 16px;
3990
+ }
3991
+
3992
+ .retry-tabs-header {
3993
+ display: flex;
3994
+ gap: 8px;
3995
+ border-bottom: 2px solid var(--border-medium);
3996
+ margin-bottom: 20px;
3997
+ flex-wrap: wrap;
3998
+ }
3999
+
4000
+ .retry-tab {
4001
+ padding: 10px 20px;
4002
+ background: transparent;
4003
+ border: none;
4004
+ border-bottom: 3px solid transparent;
4005
+ cursor: pointer;
4006
+ font-size: 0.95rem;
4007
+ font-weight: 600;
4008
+ color: var(--text-color-secondary);
4009
+ transition: all 0.2s ease;
4010
+ }
4011
+
4012
+ .retry-tab:hover {
4013
+ color: var(--primary-color);
4014
+ background: rgba(147, 51, 234, 0.05);
4015
+ }
4016
+
4017
+ .retry-tab.active {
4018
+ color: #a855f7;
4019
+ border-bottom-color: #a855f7;
4020
+ background: rgba(147, 51, 234, 0.1);
4021
+ }
4022
+
4023
+ .retry-tab-content {
4024
+ animation: fadeIn 0.3s ease-in;
4025
+ }
4026
+
4027
+ @keyframes fadeIn {
4028
+ from { opacity: 0; }
4029
+ to { opacity: 1; }
4030
+ }
4031
+
3476
4032
  .tag {
3477
4033
  display: inline-flex;
3478
4034
  align-items: center;
@@ -3503,7 +4059,16 @@ function generateHTML(reportData, trendData = null) {
3503
4059
  }
3504
4060
  .test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
3505
4061
  .test-case-content p { margin-bottom: 10px; font-size: 1em; }
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; }
4062
+ .test-error-summary {
4063
+ margin-bottom: 20px;
4064
+ padding: 14px;
4065
+ background-color: rgba(244,67,54,0.05);
4066
+ border: 1px solid rgba(244,67,54,0.2);
4067
+ border-left: 4px solid var(--danger-color);
4068
+ border-radius: 4px;
4069
+ display: flex;
4070
+ flex-direction: column;
4071
+ }
3507
4072
  .test-error-summary h4 { color: var(--danger-color); margin-top:0;}
3508
4073
  .test-error-summary pre { white-space: pre-wrap; word-break: break-all; color: var(--danger-color); font-size: 0.95em;}
3509
4074
  .steps-list { margin: 18px 0; }
@@ -3515,10 +4080,15 @@ function generateHTML(reportData, trendData = null) {
3515
4080
  .step-duration { color: var(--dark-gray-color); font-size: 0.9em; }
3516
4081
  .step-details { display: none; padding: 14px; margin-top: 8px; background: #fdfdfd; border-radius: 6px; font-size: 0.95em; border: 1px solid var(--light-gray-color); }
3517
4082
  .step-info { margin-bottom: 8px; }
4083
+ .code-snippet-section { margin: 12px 0; }
4084
+ .code-snippet { background-color: #f8f9fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 12px; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 0.9em; line-height: 1.5; overflow-x: auto; color: #24292e; margin: 0; white-space: pre; }
3518
4085
  .test-error-summary { color: var(--danger-color); margin-top: 12px; padding: 14px; background: rgba(244,67,54,0.05); border-radius: 4px; font-size: 0.95em; border-left: 3px solid var(--danger-color); }
3519
4086
  .test-error-summary pre.stack-trace { margin-top: 10px; padding: 12px; background-color: rgba(0,0,0,0.03); border-radius: 4px; font-size:0.9em; max-height: 280px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
3520
4087
  .step-hook { background-color: rgba(33,150,243,0.04); border-left: 3px solid var(--info-color) !important; }
3521
4088
  .step-hook .step-title { font-style: italic; color: var(--info-color)}
4089
+ .failed-step-highlight { border-left: 4px solid var(--danger-color) !important; background-color: rgba(244,67,54,0.03); }
4090
+ .failed-step-highlight .step-header { background-color: rgba(244,67,54,0.05); border-color: rgba(244,67,54,0.3); }
4091
+ .failed-step-marker { display: inline-block; margin-left: 10px; padding: 2px 8px; background-color: var(--danger-color); color: white; border-radius: 4px; font-size: 0.85em; font-weight: 600; }
3522
4092
  .nested-steps { margin-top: 12px; }
3523
4093
  .attachments-section { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--light-gray-color); }
3524
4094
  .attachments-section h4 { margin-top: 0; margin-bottom: 20px; font-size: 1.1em; color: var(--text-color); }
@@ -3651,6 +4221,7 @@ function generateHTML(reportData, trendData = null) {
3651
4221
  color: var(--text-color);
3652
4222
  pointer-events: auto;
3653
4223
  cursor: pointer;
4224
+ width: 100%;
3654
4225
  }
3655
4226
  .filters button.clear-filters-btn:active,
3656
4227
  .filters button.clear-filters-btn:focus {
@@ -4012,7 +4583,7 @@ function generateHTML(reportData, trendData = null) {
4012
4583
  <div class="container">
4013
4584
  <header class="header">
4014
4585
  <div class="header-title">
4015
- <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
4586
+ <img id="report-logo" src=${logo} alt="Report Logo">
4016
4587
  <h1>Pulse Report</h1>
4017
4588
  </div>
4018
4589
  <div class="run-info">
@@ -4026,6 +4597,17 @@ function generateHTML(reportData, trendData = null) {
4026
4597
  </div>
4027
4598
  </div>
4028
4599
  </header>
4600
+ ${
4601
+ reportData.metadata?.reportDescription
4602
+ ? `<div class="report-description" title="${sanitizeHTML(reportData.metadata.reportDescription)}" style="margin: 0 0 24px 0; padding: 18px 24px; background-color: var(--bg-card, var(--card-bg, #ffffff)); border: 1px solid var(--border-color, var(--border-medium, #e5e7eb)); border-left: 4px solid #764ba2; border-radius: 8px; display: flex; align-items: flex-start; gap: 16px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);">
4603
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#764ba2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 1px;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
4604
+ <div style="flex: 1; min-width: 0;">
4605
+ <h4 style="margin: 0 0 6px 0; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.5px; color: #764ba2; font-weight: 700;">Report Description</h4>
4606
+ <p style="margin: 0; font-size: 0.95em; color: var(--text-color, #1f2937); line-height: 1.6; font-weight: 400; overflow-wrap: break-word;">${sanitizeHTML(reportData.metadata.reportDescription.length > 130 ? reportData.metadata.reportDescription.substring(0, 130) + "..." : reportData.metadata.reportDescription)}</p>
4607
+ </div>
4608
+ </div>`
4609
+ : ""
4610
+ }
4029
4611
  <div class="tabs">
4030
4612
  <button class="tab-button active" data-tab="dashboard">Dashboard</button>
4031
4613
  <button class="tab-button" data-tab="test-runs">Test Run Summary</button>
@@ -4046,31 +4628,33 @@ function generateHTML(reportData, trendData = null) {
4046
4628
  <div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
4047
4629
  runSummary.skipped || 0
4048
4630
  }</div><div class="trend-percentage">${skipPercentage}%</div></div>
4049
- <div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
4050
- <div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
4051
- runSummary.duration,
4052
- )}</div></div>
4053
- <div class="summary-card">
4054
- <h3>🔄 Retry Count</h3>
4055
- <div class="value">${totalRetried}</div>
4056
- </div>
4631
+ <div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
4632
+ <div class="trend-percentage">${flakyPercentage}%</div></div>
4633
+ <div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
4634
+ runSummary.duration,
4635
+ )}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
4636
+ <div class="summary-card">
4637
+ <h3>Total Retry Count</h3>
4638
+ <div class="value">${totalRetried}</div>
4639
+ <div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
4640
+ </div>
4057
4641
  <div class="summary-card">
4058
4642
  <h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
4059
4643
  <div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
4060
4644
  ${browserBreakdown
4061
- .slice(0, 5)
4645
+ .slice(0, 3)
4062
4646
  .map(
4063
4647
  (b) =>
4064
4648
  `<div class="browser-item">
4065
- <span class="browser-name">${sanitizeHTML(b.browser)}</span>
4649
+ <span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
4066
4650
  <span class="browser-stats">${b.percentage}% (${b.count})</span>
4067
4651
  </div>`,
4068
4652
  )
4069
4653
  .join("")}
4070
4654
  ${
4071
- browserBreakdown.length > 5
4655
+ browserBreakdown.length > 3
4072
4656
  ? `<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>
4657
+ <span>+${browserBreakdown.length - 3} more browsers</span>
4074
4658
  </div>`
4075
4659
  : ""
4076
4660
  }
@@ -4083,17 +4667,13 @@ function generateHTML(reportData, trendData = null) {
4083
4667
  [
4084
4668
  { label: "Passed", value: runSummary.passed },
4085
4669
  { label: "Failed", value: runSummary.failed },
4670
+ { label: "Flaky", value: runSummary.flaky || 0 },
4086
4671
  { label: "Skipped", value: runSummary.skipped || 0 },
4087
4672
  ],
4088
4673
  400,
4089
4674
  390,
4090
4675
  )}
4091
- ${
4092
- runSummary.environment &&
4093
- Object.keys(runSummary.environment).length > 0
4094
- ? generateEnvironmentDashboard(runSummary.environment)
4095
- : '<div class="no-data">Environment data not available.</div>'
4096
- }
4676
+ ${generateEnvironmentSection(runSummary.environment)}
4097
4677
  </div>
4098
4678
 
4099
4679
  <div class="dashboard-column">
@@ -4105,7 +4685,7 @@ function generateHTML(reportData, trendData = null) {
4105
4685
  <div id="test-runs" class="tab-content">
4106
4686
  <div class="filters">
4107
4687
  <input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
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>
4688
+ <select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="flaky">Flaky</option><option value="skipped">Skipped</option></select>
4109
4689
  <select id="filter-browser"><option value="">All Browsers</option>${Array.from(
4110
4690
  new Set(
4111
4691
  (results || []).map((test) => test.browser || "unknown"),
@@ -4155,7 +4735,7 @@ function generateHTML(reportData, trendData = null) {
4155
4735
  ${generateWorkerDistributionChart(results)}
4156
4736
  </div>
4157
4737
  </div>
4158
- <div class="trend-chart test-history-trend-section" style="border-bottom: none;">
4738
+ <div class="trend-chart test-history-trend-section" style="border-bottom: none; background: none !important; box-shadow: none !important; border: none !important; border-radius: none !important;">
4159
4739
  <h3 class="chart-title-header">Individual Test History</h3>
4160
4740
  </div>
4161
4741
  ${
@@ -4172,6 +4752,7 @@ function generateHTML(reportData, trendData = null) {
4172
4752
  <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;">
4173
4753
  <div style="display: inline-flex; align-items: center; gap: 0.5rem; color: #333; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
4174
4754
  <span>Created by</span>
4755
+ <img id="report-logo" src=${logo} alt="Pulse Report Logo" style="height: 20px;">
4175
4756
  <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>
4176
4757
  </div>
4177
4758
  <div style="margin-top: 0.5rem; font-size: 0.75rem; color: #666;">Crafted with precision</div>
@@ -4202,6 +4783,33 @@ function generateHTML(reportData, trendData = null) {
4202
4783
  });
4203
4784
  }
4204
4785
 
4786
+ // --- Retry Tab Switching Function ---
4787
+ function switchRetryTab(event, tabId) {
4788
+ const tabButton = event.currentTarget;
4789
+ const tabsContainer = tabButton.closest('.retry-tabs-container');
4790
+
4791
+ // Hide all tab contents in this container
4792
+ const allTabContents = tabsContainer.querySelectorAll('.retry-tab-content');
4793
+ allTabContents.forEach(content => {
4794
+ content.style.display = 'none';
4795
+ content.classList.remove('active');
4796
+ });
4797
+
4798
+ // Remove active class from all tabs
4799
+ const allTabs = tabsContainer.querySelectorAll('.retry-tab');
4800
+ allTabs.forEach(tab => tab.classList.remove('active'));
4801
+
4802
+ // Show selected tab content
4803
+ const selectedContent = document.getElementById(tabId);
4804
+ if (selectedContent) {
4805
+ selectedContent.style.display = 'block';
4806
+ selectedContent.classList.add('active');
4807
+ }
4808
+
4809
+ // Add active class to clicked tab
4810
+ tabButton.classList.add('active');
4811
+ }
4812
+
4205
4813
  // --- AI Failure Analyzer Functions ---
4206
4814
  function getAIFix(button) {
4207
4815
  const failureItem = button.closest('.compact-failure-item');
@@ -4669,6 +5277,8 @@ async function runScript(scriptPath, args = []) {
4669
5277
  });
4670
5278
  }
4671
5279
  async function main() {
5280
+ await animate();
5281
+
4672
5282
  const __filename = fileURLToPath(import.meta.url);
4673
5283
  const __dirname = path.dirname(__filename);
4674
5284
 
@@ -4687,8 +5297,12 @@ async function main() {
4687
5297
  "generate-trend.mjs", // Keeping the filename as per your request
4688
5298
  );
4689
5299
 
4690
- const outputDir = await getOutputDir(customOutputDir);
4691
- const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE); // Current run's main JSON
5300
+ const config = await getReporterConfig(customOutputDir);
5301
+ const outputDir = config.outputDir;
5302
+ const outputFile = config.outputFile;
5303
+
5304
+ await mergeSequentialReportsIfNeeded(outputDir);
5305
+ const reportJsonPath = path.resolve(outputDir, outputFile); // Current run's main JSON
4692
5306
  const reportHtmlPath = path.resolve(outputDir, DEFAULT_HTML_FILE);
4693
5307
 
4694
5308
  const historyDir = path.join(outputDir, "history"); // Directory for historical JSON files
@@ -4726,6 +5340,32 @@ async function main() {
4726
5340
  try {
4727
5341
  const jsonData = await fs.readFile(reportJsonPath, "utf-8");
4728
5342
  currentRunReportData = JSON.parse(jsonData);
5343
+
5344
+ // Process custom logo if provided in metadata
5345
+ if (currentRunReportData.metadata?.logo) {
5346
+ const logoPath = path.resolve(
5347
+ process.cwd(),
5348
+ currentRunReportData.metadata.logo,
5349
+ );
5350
+ try {
5351
+ const ext = path.extname(logoPath).toLowerCase();
5352
+ let mimeType = "image/png";
5353
+ if (ext === ".svg") mimeType = "image/svg+xml";
5354
+ else if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
5355
+ else if (ext === ".gif") mimeType = "image/gif";
5356
+ else if (ext === ".webp") mimeType = "image/webp";
5357
+
5358
+ const logoData = await fs.readFile(logoPath, "base64");
5359
+ logo = `data:${mimeType};base64,${logoData}`;
5360
+ } catch (error) {
5361
+ console.warn(
5362
+ chalk.yellow(
5363
+ `Warning: Could not read custom logo file at ${logoPath}. Falling back to default logo. Error: ${error.message}`,
5364
+ ),
5365
+ );
5366
+ }
5367
+ }
5368
+
4729
5369
  if (
4730
5370
  !currentRunReportData ||
4731
5371
  typeof currentRunReportData !== "object" ||
@@ -4834,6 +5474,7 @@ async function main() {
4834
5474
  passed: histRunReport.run.passed,
4835
5475
  failed: histRunReport.run.failed,
4836
5476
  skipped: histRunReport.run.skipped || 0,
5477
+ flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
4837
5478
  });
4838
5479
 
4839
5480
  if (histRunReport.results && Array.isArray(histRunReport.results)) {
@@ -4842,7 +5483,7 @@ async function main() {
4842
5483
  (test) => ({
4843
5484
  testName: test.name,
4844
5485
  duration: test.duration,
4845
- status: test.status,
5486
+ status: test.final_status || test.status,
4846
5487
  timestamp: new Date(test.startTime),
4847
5488
  }),
4848
5489
  );
@@ -4860,7 +5501,7 @@ async function main() {
4860
5501
  await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
4861
5502
  console.log(
4862
5503
  chalk.green.bold(
4863
- `🎉 Pulse report generated successfully at: ${reportHtmlPath}`,
5504
+ `Pulse report generated successfully at: ${reportHtmlPath}`,
4864
5505
  ),
4865
5506
  );
4866
5507
  console.log(chalk.gray(`(You can open this file in your browser)`));