@arghajit/playwright-pulse-report 0.3.3 → 0.3.4
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.
- package/README.md +95 -85
- package/dist/reporter/playwright-pulse-reporter.d.ts +1 -0
- package/dist/reporter/playwright-pulse-reporter.js +85 -25
- package/dist/types/index.d.ts +8 -2
- package/dist/utils/compression-utils.d.ts +19 -0
- package/dist/utils/compression-utils.js +112 -0
- package/package.json +10 -3
- package/scripts/generate-email-report.mjs +41 -11
- package/scripts/generate-report.mjs +1060 -509
- package/scripts/generate-static-report.mjs +1139 -495
- package/scripts/merge-pulse-report.js +11 -2
- package/scripts/sendReport.mjs +3 -0
- package/scripts/terminal-logo.mjs +51 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -26
- package/dist/playwright-pulse-reporter.d.ts +0 -26
- package/dist/playwright-pulse-reporter.js +0 -304
- package/dist/reporter/lib/report-types.d.ts +0 -8
- package/dist/reporter/lib/report-types.js +0 -2
- package/dist/reporter/reporter/playwright-pulse-reporter.d.ts +0 -1
- package/dist/reporter/reporter/playwright-pulse-reporter.js +0 -398
- package/dist/reporter/types/index.d.ts +0 -52
- package/dist/reporter/types/index.js +0 -2
|
@@ -6,6 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { fork } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { getOutputDir } from "./config-reader.mjs";
|
|
9
|
+
import { animate } from "./terminal-logo.mjs";
|
|
9
10
|
|
|
10
11
|
// Use dynamic import for chalk as it's ESM only
|
|
11
12
|
let chalk;
|
|
@@ -294,6 +295,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
294
295
|
color: "var(--warning-color)",
|
|
295
296
|
marker: { symbol: "circle" },
|
|
296
297
|
},
|
|
298
|
+
{
|
|
299
|
+
name: "Flaky",
|
|
300
|
+
data: runs.map((r) => r.flaky || 0),
|
|
301
|
+
color: "#00ccd3",
|
|
302
|
+
marker: { symbol: "circle" },
|
|
303
|
+
},
|
|
297
304
|
];
|
|
298
305
|
const runsForTooltip = runs.map((r) => ({
|
|
299
306
|
runId: r.runId,
|
|
@@ -480,6 +487,9 @@ function generateTestHistoryChart(history) {
|
|
|
480
487
|
case "skipped":
|
|
481
488
|
color = "var(--warning-color)";
|
|
482
489
|
break;
|
|
490
|
+
case "flaky":
|
|
491
|
+
color = "var(--neutral-500)";
|
|
492
|
+
break;
|
|
483
493
|
default:
|
|
484
494
|
color = "var(--dark-gray-color)";
|
|
485
495
|
}
|
|
@@ -589,6 +599,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
589
599
|
case "Failed":
|
|
590
600
|
color = "var(--danger-color)";
|
|
591
601
|
break;
|
|
602
|
+
case "Flaky":
|
|
603
|
+
color = "#00ccd3";
|
|
604
|
+
break;
|
|
592
605
|
case "Skipped":
|
|
593
606
|
color = "var(--warning-color)";
|
|
594
607
|
break;
|
|
@@ -688,21 +701,175 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
688
701
|
</div>
|
|
689
702
|
`;
|
|
690
703
|
}
|
|
691
|
-
function
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
704
|
+
function generateEnvironmentSection(environmentData) {
|
|
705
|
+
if (!environmentData) {
|
|
706
|
+
return '<div class="no-data">Environment data not available.</div>';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (Array.isArray(environmentData)) {
|
|
710
|
+
return `
|
|
711
|
+
<div class="sharded-env-section">
|
|
712
|
+
<div class="sharded-env-header">
|
|
713
|
+
<div class="sharded-env-title-row">
|
|
714
|
+
<div>
|
|
715
|
+
<div class="sharded-env-title">
|
|
716
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
717
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
718
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
719
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
720
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
721
|
+
</svg>
|
|
722
|
+
System Information
|
|
723
|
+
</div>
|
|
724
|
+
<div class="sharded-env-subtitle">Test execution environment details - ${environmentData.length} shard${environmentData.length > 1 ? "s" : ""}</div>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="env-icon-badge">
|
|
727
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
728
|
+
<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>
|
|
729
|
+
</svg>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="sharded-environments-container">
|
|
734
|
+
<div class="sharded-environments-wrapper">
|
|
735
|
+
${environmentData
|
|
736
|
+
.map(
|
|
737
|
+
(env, index) => `
|
|
738
|
+
<div class="env-card-wrapper">
|
|
739
|
+
<div class="env-card-badge">Shard ${index + 1}</div>
|
|
740
|
+
${generateEnvironmentDashboard(env, true)}
|
|
741
|
+
</div>
|
|
742
|
+
`,
|
|
743
|
+
)
|
|
744
|
+
.join("")}
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
<style>
|
|
749
|
+
.sharded-env-section {
|
|
750
|
+
border: 1px solid #e2e8f0;
|
|
751
|
+
border-radius: 12px;
|
|
752
|
+
background: #fafbfc;
|
|
753
|
+
overflow: hidden;
|
|
754
|
+
}
|
|
755
|
+
.sharded-env-header {
|
|
756
|
+
position: sticky;
|
|
757
|
+
top: 0;
|
|
758
|
+
z-index: 20;
|
|
759
|
+
background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
|
|
760
|
+
border-bottom: 1px solid #e2e8f0;
|
|
761
|
+
padding: 24px 24px 16px;
|
|
762
|
+
}
|
|
763
|
+
.sharded-env-title-row {
|
|
764
|
+
display: flex;
|
|
765
|
+
justify-content: space-between;
|
|
766
|
+
align-items: center;
|
|
767
|
+
}
|
|
768
|
+
.sharded-env-title {
|
|
769
|
+
display: flex;
|
|
770
|
+
align-items: center;
|
|
771
|
+
font-size: 18px;
|
|
772
|
+
font-weight: 600;
|
|
773
|
+
color: #0f172a;
|
|
774
|
+
}
|
|
775
|
+
.sharded-env-title svg {
|
|
776
|
+
width: 18px;
|
|
777
|
+
height: 18px;
|
|
778
|
+
margin-right: 8px;
|
|
779
|
+
stroke: currentColor;
|
|
780
|
+
fill: none;
|
|
781
|
+
}
|
|
782
|
+
.sharded-env-subtitle {
|
|
783
|
+
font-size: 13px;
|
|
784
|
+
color: #64748b;
|
|
785
|
+
margin-top: 4px;
|
|
786
|
+
}
|
|
787
|
+
.sharded-environments-container {
|
|
788
|
+
max-height: 520px;
|
|
789
|
+
overflow-y: auto;
|
|
790
|
+
overflow-x: hidden;
|
|
791
|
+
padding: 16px;
|
|
792
|
+
}
|
|
793
|
+
.sharded-environments-container::-webkit-scrollbar {
|
|
794
|
+
width: 8px;
|
|
795
|
+
}
|
|
796
|
+
.sharded-environments-container::-webkit-scrollbar-track {
|
|
797
|
+
background: #f1f1f1;
|
|
798
|
+
border-radius: 4px;
|
|
799
|
+
}
|
|
800
|
+
.sharded-environments-container::-webkit-scrollbar-thumb {
|
|
801
|
+
background: #cbd5e0;
|
|
802
|
+
border-radius: 4px;
|
|
803
|
+
}
|
|
804
|
+
.sharded-environments-container::-webkit-scrollbar-thumb:hover {
|
|
805
|
+
background: #a0aec0;
|
|
806
|
+
}
|
|
807
|
+
.sharded-environments-wrapper {
|
|
808
|
+
display: grid;
|
|
809
|
+
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
|
810
|
+
gap: 24px;
|
|
811
|
+
}
|
|
812
|
+
@media (max-width: 768px) {
|
|
813
|
+
.sharded-environments-wrapper {
|
|
814
|
+
grid-template-columns: 1fr;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
.env-card-wrapper {
|
|
818
|
+
position: relative;
|
|
819
|
+
}
|
|
820
|
+
.env-card-badge {
|
|
821
|
+
position: absolute;
|
|
822
|
+
top: -10px;
|
|
823
|
+
right: 16px;
|
|
824
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
825
|
+
color: white;
|
|
826
|
+
padding: 6px 14px;
|
|
827
|
+
border-radius: 20px;
|
|
828
|
+
font-size: 0.75em;
|
|
829
|
+
font-weight: 700;
|
|
830
|
+
text-transform: uppercase;
|
|
831
|
+
letter-spacing: 0.5px;
|
|
832
|
+
z-index: 10;
|
|
833
|
+
box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
|
|
834
|
+
}
|
|
835
|
+
</style>
|
|
836
|
+
`;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return generateEnvironmentDashboard(environmentData);
|
|
840
|
+
}
|
|
699
841
|
|
|
700
|
-
|
|
842
|
+
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
843
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
844
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
845
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
846
|
+
const osInfo = environment.os || "N/A";
|
|
847
|
+
const nodeInfo = environment.node || "N/A";
|
|
848
|
+
const v8Info = environment.v8 || "N/A";
|
|
849
|
+
const cwdInfo = environment.cwd || "N/A";
|
|
850
|
+
const formattedMemory = environment.memory || "N/A";
|
|
701
851
|
const runContext = process.env.CI ? "CI" : "Local Test";
|
|
702
852
|
|
|
703
853
|
return `
|
|
704
|
-
<div class="
|
|
854
|
+
<div class="env-modern-card${hideHeader ? " no-header" : ""}">
|
|
705
855
|
<style>
|
|
856
|
+
.env-modern-card {
|
|
857
|
+
background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
|
|
858
|
+
border: 0;
|
|
859
|
+
border-radius: 12px;
|
|
860
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
861
|
+
margin-top: 24px;
|
|
862
|
+
transition: all 0.3s ease;
|
|
863
|
+
font-family: var(--font-family);
|
|
864
|
+
overflow: hidden;
|
|
865
|
+
}
|
|
866
|
+
.env-modern-card:hover {
|
|
867
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
868
|
+
}
|
|
869
|
+
.env-modern-card {
|
|
870
|
+
margin-bottom: 0;
|
|
871
|
+
}
|
|
872
|
+
|
|
706
873
|
.environment-dashboard-wrapper *,
|
|
707
874
|
.environment-dashboard-wrapper *::before,
|
|
708
875
|
.environment-dashboard-wrapper *::after {
|
|
@@ -726,279 +893,269 @@ function generateEnvironmentDashboard(environment) {
|
|
|
726
893
|
transform: translateZ(0);
|
|
727
894
|
}
|
|
728
895
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
896
|
+
.env-card-header {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-direction: column;
|
|
899
|
+
padding: 24px 24px 12px;
|
|
734
900
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
padding: 24px;
|
|
738
|
-
}
|
|
901
|
+
.env-modern-card.no-header .env-card-header {
|
|
902
|
+
display: none;
|
|
739
903
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
grid-column: 1 / -1;
|
|
743
|
-
margin-bottom: 24px;
|
|
904
|
+
.env-modern-card.no-header {
|
|
905
|
+
margin-top: 0;
|
|
744
906
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
font-size: 2em;
|
|
748
|
-
font-weight: 900;
|
|
749
|
-
color: #0f172a;
|
|
750
|
-
letter-spacing: -0.02em;
|
|
751
|
-
margin: 0 0 8px 0;
|
|
907
|
+
.env-modern-card.no-header .env-card-content {
|
|
908
|
+
padding-top: 24px;
|
|
752
909
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
margin: 0;
|
|
758
|
-
font-weight: 400;
|
|
910
|
+
.env-card-title-row {
|
|
911
|
+
display: flex;
|
|
912
|
+
justify-content: space-between;
|
|
913
|
+
align-items: center;
|
|
759
914
|
}
|
|
760
|
-
|
|
761
|
-
.env-card {
|
|
762
|
-
background: white;
|
|
763
|
-
border: none;
|
|
764
|
-
border-left: 4px solid #e2e8f0;
|
|
765
|
-
padding: 28px;
|
|
915
|
+
.env-card-title {
|
|
766
916
|
display: flex;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
917
|
+
align-items: center;
|
|
918
|
+
font-size: 16px;
|
|
919
|
+
font-weight: 600;
|
|
920
|
+
color: #0f172a;
|
|
921
|
+
transition: color 0.3s;
|
|
771
922
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
border-left-color: var(--primary-color);
|
|
775
|
-
background: #fafbfc;
|
|
923
|
+
.env-modern-card:hover .env-card-title {
|
|
924
|
+
color: #6366f1;
|
|
776
925
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
926
|
+
.env-card-title svg {
|
|
927
|
+
width: 16px;
|
|
928
|
+
height: 16px;
|
|
929
|
+
margin-right: 8px;
|
|
930
|
+
stroke: currentColor;
|
|
931
|
+
fill: none;
|
|
932
|
+
}
|
|
933
|
+
.env-card-subtitle {
|
|
934
|
+
font-size: 12px;
|
|
935
|
+
color: #64748b;
|
|
936
|
+
margin-top: 4px;
|
|
937
|
+
}
|
|
938
|
+
.env-icon-badge {
|
|
939
|
+
width: 36px;
|
|
940
|
+
height: 36px;
|
|
941
|
+
border-radius: 50%;
|
|
942
|
+
background: linear-gradient(to bottom right, rgba(99, 102, 241, 0.1), rgba(99, 102, 241, 0.05));
|
|
782
943
|
display: flex;
|
|
783
944
|
align-items: center;
|
|
784
|
-
|
|
785
|
-
text-transform: uppercase;
|
|
786
|
-
letter-spacing: 0.5px;
|
|
945
|
+
justify-content: center;
|
|
787
946
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
fill:
|
|
947
|
+
.env-icon-badge svg {
|
|
948
|
+
width: 16px;
|
|
949
|
+
height: 16px;
|
|
950
|
+
stroke: #6366f1;
|
|
951
|
+
fill: none;
|
|
793
952
|
}
|
|
794
|
-
|
|
795
953
|
.env-card-content {
|
|
796
|
-
|
|
797
|
-
flex-direction: column;
|
|
798
|
-
gap: 16px;
|
|
954
|
+
padding: 0 24px 24px;
|
|
799
955
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
align-items: flex-start;
|
|
805
|
-
gap: 16px;
|
|
806
|
-
font-size: 1em;
|
|
807
|
-
padding: 8px 0;
|
|
808
|
-
}
|
|
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;
|
|
956
|
+
.env-items-grid {
|
|
957
|
+
display: grid;
|
|
958
|
+
grid-template-columns: repeat(2, 1fr);
|
|
959
|
+
gap: 10px;
|
|
817
960
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
font-size: 0.95em;
|
|
823
|
-
text-align: right;
|
|
824
|
-
word-break: break-word;
|
|
825
|
-
margin-left: auto;
|
|
961
|
+
@media (min-width: 768px) {
|
|
962
|
+
.env-items-grid {
|
|
963
|
+
grid-template-columns: repeat(4, 1fr);
|
|
964
|
+
}
|
|
826
965
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
padding:
|
|
832
|
-
border-radius:
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
text-transform: uppercase;
|
|
836
|
-
letter-spacing: 0.5px;
|
|
966
|
+
.env-item {
|
|
967
|
+
display: flex;
|
|
968
|
+
align-items: flex-start;
|
|
969
|
+
gap: 8px;
|
|
970
|
+
padding: 8px;
|
|
971
|
+
border-radius: 8px;
|
|
972
|
+
transition: background-color 0.2s;
|
|
973
|
+
min-height: 48px;
|
|
837
974
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
background-color: #ede9fe;
|
|
841
|
-
color: #6366f1;
|
|
975
|
+
.env-item:hover {
|
|
976
|
+
background-color: rgba(100, 116, 139, 0.05);
|
|
842
977
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
background-color: #d1fae5;
|
|
846
|
-
color: #10b981;
|
|
978
|
+
.env-item-icon {
|
|
979
|
+
flex-shrink: 0;
|
|
847
980
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
981
|
+
.env-item-icon svg {
|
|
982
|
+
width: 16px;
|
|
983
|
+
height: 16px;
|
|
984
|
+
stroke: #6366f1;
|
|
985
|
+
fill: none;
|
|
852
986
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
align-items: center;
|
|
857
|
-
gap: 6px;
|
|
987
|
+
.env-item-content {
|
|
988
|
+
flex-grow: 1;
|
|
989
|
+
min-width: 0;
|
|
858
990
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
991
|
+
.env-item-label {
|
|
992
|
+
font-size: 12px;
|
|
993
|
+
font-weight: 500;
|
|
994
|
+
color: #64748b;
|
|
995
|
+
white-space: nowrap;
|
|
996
|
+
overflow: hidden;
|
|
997
|
+
text-overflow: ellipsis;
|
|
866
998
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
999
|
+
.env-item-value {
|
|
1000
|
+
font-size: 12px;
|
|
1001
|
+
font-weight: 600;
|
|
1002
|
+
color: #0f172a;
|
|
1003
|
+
word-wrap: break-word;
|
|
1004
|
+
overflow-wrap: break-word;
|
|
1005
|
+
line-height: 1.4;
|
|
872
1006
|
}
|
|
873
1007
|
</style>
|
|
874
1008
|
|
|
875
|
-
<div class="env-
|
|
876
|
-
<div>
|
|
877
|
-
<
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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>
|
|
1009
|
+
<div class="env-card-header">
|
|
1010
|
+
<div class="env-card-title-row">
|
|
1011
|
+
<div>
|
|
1012
|
+
<div class="env-card-title">
|
|
1013
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1014
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
1015
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
1016
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
1017
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
1018
|
+
</svg>
|
|
1019
|
+
System Information
|
|
1020
|
+
</div>
|
|
1021
|
+
<div class="env-card-subtitle">Test execution environment details</div>
|
|
900
1022
|
</div>
|
|
901
|
-
<div class="env-
|
|
902
|
-
<
|
|
903
|
-
|
|
1023
|
+
<div class="env-icon-badge">
|
|
1024
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1025
|
+
<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>
|
|
1026
|
+
</svg>
|
|
904
1027
|
</div>
|
|
905
1028
|
</div>
|
|
906
1029
|
</div>
|
|
907
1030
|
|
|
908
|
-
<div class="env-card">
|
|
909
|
-
<div class="env-
|
|
910
|
-
<
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
<
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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>
|
|
1031
|
+
<div class="env-card-content">
|
|
1032
|
+
<div class="env-items-grid">
|
|
1033
|
+
<div class="env-item">
|
|
1034
|
+
<div class="env-item-icon">
|
|
1035
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1036
|
+
<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>
|
|
1037
|
+
</svg>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div class="env-item-content">
|
|
1040
|
+
<p class="env-item-label">Host</p>
|
|
1041
|
+
<div class="env-item-value" title="${environment.host}">${environment.host}</div>
|
|
1042
|
+
</div>
|
|
933
1043
|
</div>
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
<span class="env-detail-value">${environment.node}</span>
|
|
1044
|
+
|
|
1045
|
+
<div class="env-item">
|
|
1046
|
+
<div class="env-item-icon">
|
|
1047
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1048
|
+
<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>
|
|
1049
|
+
</svg>
|
|
1050
|
+
</div>
|
|
1051
|
+
<div class="env-item-content">
|
|
1052
|
+
<p class="env-item-label">Os</p>
|
|
1053
|
+
<div class="env-item-value" title="${environment.os}">${environment.os}</div>
|
|
1054
|
+
</div>
|
|
946
1055
|
</div>
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
<
|
|
1056
|
+
|
|
1057
|
+
<div class="env-item">
|
|
1058
|
+
<div class="env-item-icon">
|
|
1059
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1060
|
+
<rect width="16" height="16" x="4" y="4" rx="2"></rect>
|
|
1061
|
+
<rect width="6" height="6" x="9" y="9" rx="1"></rect>
|
|
1062
|
+
<path d="M15 2v2"></path>
|
|
1063
|
+
<path d="M15 20v2"></path>
|
|
1064
|
+
<path d="M2 15h2"></path>
|
|
1065
|
+
<path d="M2 9h2"></path>
|
|
1066
|
+
<path d="M20 15h2"></path>
|
|
1067
|
+
<path d="M20 9h2"></path>
|
|
1068
|
+
<path d="M9 2v2"></path>
|
|
1069
|
+
<path d="M9 20v2"></path>
|
|
1070
|
+
</svg>
|
|
1071
|
+
</div>
|
|
1072
|
+
<div class="env-item-content">
|
|
1073
|
+
<p class="env-item-label">Cpu</p>
|
|
1074
|
+
<div class="env-item-value" title='${JSON.stringify(environment.cpu)}'>${cpuInfo}</div>
|
|
1075
|
+
</div>
|
|
950
1076
|
</div>
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
<
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1077
|
+
|
|
1078
|
+
<div class="env-item">
|
|
1079
|
+
<div class="env-item-icon">
|
|
1080
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1081
|
+
<path d="M6 19v-3"></path>
|
|
1082
|
+
<path d="M10 19v-3"></path>
|
|
1083
|
+
<path d="M14 19v-3"></path>
|
|
1084
|
+
<path d="M18 19v-3"></path>
|
|
1085
|
+
<path d="M8 11V9"></path>
|
|
1086
|
+
<path d="M16 11V9"></path>
|
|
1087
|
+
<path d="M12 11V9"></path>
|
|
1088
|
+
<path d="M2 15h20"></path>
|
|
1089
|
+
<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>
|
|
1090
|
+
</svg>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div class="env-item-content">
|
|
1093
|
+
<p class="env-item-label">Memory</p>
|
|
1094
|
+
<div class="env-item-value" title="${environment.memory}">${environment.memory}</div>
|
|
1095
|
+
</div>
|
|
958
1096
|
</div>
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
? "ARM-based"
|
|
984
|
-
: "x86/Other"
|
|
985
|
-
}
|
|
986
|
-
</span>
|
|
987
|
-
</span>
|
|
1097
|
+
|
|
1098
|
+
<div class="env-item">
|
|
1099
|
+
<div class="env-item-icon">
|
|
1100
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1101
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1102
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1103
|
+
<path d="M12 2v2"></path>
|
|
1104
|
+
<path d="M12 22v-2"></path>
|
|
1105
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1106
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1107
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1108
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1109
|
+
<path d="M14 12h8"></path>
|
|
1110
|
+
<path d="M2 12h2"></path>
|
|
1111
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1112
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1113
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1114
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1115
|
+
</svg>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="env-item-content">
|
|
1118
|
+
<p class="env-item-label">Node</p>
|
|
1119
|
+
<div class="env-item-value" title="${environment.node}">${environment.node}</div>
|
|
1120
|
+
</div>
|
|
988
1121
|
</div>
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
<
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1122
|
+
|
|
1123
|
+
<div class="env-item">
|
|
1124
|
+
<div class="env-item-icon">
|
|
1125
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1126
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1127
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1128
|
+
<path d="M12 2v2"></path>
|
|
1129
|
+
<path d="M12 22v-2"></path>
|
|
1130
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1131
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1132
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1133
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1134
|
+
<path d="M14 12h8"></path>
|
|
1135
|
+
<path d="M2 12h2"></path>
|
|
1136
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1137
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1138
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1139
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1140
|
+
</svg>
|
|
1141
|
+
</div>
|
|
1142
|
+
<div class="env-item-content">
|
|
1143
|
+
<p class="env-item-label">V8</p>
|
|
1144
|
+
<div class="env-item-value" title="${environment.v8}">${environment.v8}</div>
|
|
1145
|
+
</div>
|
|
998
1146
|
</div>
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
<
|
|
1147
|
+
|
|
1148
|
+
<div class="env-item">
|
|
1149
|
+
<div class="env-item-icon">
|
|
1150
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1151
|
+
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
1152
|
+
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
1153
|
+
</svg>
|
|
1154
|
+
</div>
|
|
1155
|
+
<div class="env-item-content">
|
|
1156
|
+
<p class="env-item-label">Working Dir</p>
|
|
1157
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1158
|
+
</div>
|
|
1002
1159
|
</div>
|
|
1003
1160
|
</div>
|
|
1004
1161
|
</div>
|
|
@@ -1021,11 +1178,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1021
1178
|
const workerId =
|
|
1022
1179
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1023
1180
|
if (!acc[workerId]) {
|
|
1024
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1181
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1025
1182
|
}
|
|
1026
1183
|
|
|
1027
1184
|
const status = String(test.status).toLowerCase();
|
|
1028
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1185
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1029
1186
|
acc[workerId][status]++;
|
|
1030
1187
|
}
|
|
1031
1188
|
|
|
@@ -1070,12 +1227,14 @@ function generateWorkerDistributionChart(results) {
|
|
|
1070
1227
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1071
1228
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1072
1229
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1230
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1073
1231
|
|
|
1074
1232
|
const categoriesString = JSON.stringify(categories);
|
|
1075
1233
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
1076
1234
|
const seriesString = JSON.stringify([
|
|
1077
1235
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1078
1236
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1237
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1079
1238
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1080
1239
|
]);
|
|
1081
1240
|
|
|
@@ -1168,6 +1327,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1168
1327
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1169
1328
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1170
1329
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1330
|
+
else if (test.status === 'flaky') color = '#00ccd3';
|
|
1171
1331
|
|
|
1172
1332
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1173
1333
|
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
@@ -1333,6 +1493,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1333
1493
|
<option value="">All Statuses</option>
|
|
1334
1494
|
<option value="passed">Passed</option>
|
|
1335
1495
|
<option value="failed">Failed</option>
|
|
1496
|
+
<option value="flaky">Flaky</option>
|
|
1336
1497
|
<option value="skipped">Skipped</option>
|
|
1337
1498
|
</select>
|
|
1338
1499
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1400,6 +1561,8 @@ function getStatusClass(status) {
|
|
|
1400
1561
|
return "status-failed";
|
|
1401
1562
|
case "skipped":
|
|
1402
1563
|
return "status-skipped";
|
|
1564
|
+
case "flaky":
|
|
1565
|
+
return "status-flaky";
|
|
1403
1566
|
default:
|
|
1404
1567
|
return "status-unknown";
|
|
1405
1568
|
}
|
|
@@ -1412,6 +1575,8 @@ function getStatusIcon(status) {
|
|
|
1412
1575
|
return "❌";
|
|
1413
1576
|
case "skipped":
|
|
1414
1577
|
return "⏭️";
|
|
1578
|
+
case "flaky":
|
|
1579
|
+
return "⚠️";
|
|
1415
1580
|
default:
|
|
1416
1581
|
return "❓";
|
|
1417
1582
|
}
|
|
@@ -1447,6 +1612,7 @@ function getSuitesData(results) {
|
|
|
1447
1612
|
browser: browser,
|
|
1448
1613
|
passed: 0,
|
|
1449
1614
|
failed: 0,
|
|
1615
|
+
flaky: 0,
|
|
1450
1616
|
skipped: 0,
|
|
1451
1617
|
count: 0,
|
|
1452
1618
|
statusOverall: "passed",
|
|
@@ -1454,12 +1620,15 @@ function getSuitesData(results) {
|
|
|
1454
1620
|
}
|
|
1455
1621
|
const suite = suitesMap.get(key);
|
|
1456
1622
|
suite.count++;
|
|
1457
|
-
|
|
1623
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1624
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1458
1625
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1459
1626
|
suite[currentStatus]++;
|
|
1460
1627
|
}
|
|
1461
1628
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1462
|
-
else if (currentStatus === "
|
|
1629
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1630
|
+
suite.statusOverall = "flaky";
|
|
1631
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1463
1632
|
suite.statusOverall = "skipped";
|
|
1464
1633
|
});
|
|
1465
1634
|
return Array.from(suitesMap.values());
|
|
@@ -1470,10 +1639,10 @@ function generateSuitesWidget(suitesData) {
|
|
|
1470
1639
|
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
1640
|
}
|
|
1472
1641
|
|
|
1473
|
-
//
|
|
1642
|
+
// Uses CSS classes for responsiveness instead of inline styles
|
|
1474
1643
|
return `
|
|
1475
|
-
<div class="suites-widget
|
|
1476
|
-
<div class="suites-header"
|
|
1644
|
+
<div class="suites-widget fixed-height-widget">
|
|
1645
|
+
<div class="suites-header">
|
|
1477
1646
|
<h2>Test Suites</h2>
|
|
1478
1647
|
<span class="summary-badge">${
|
|
1479
1648
|
suitesData.length
|
|
@@ -1483,40 +1652,40 @@ function generateSuitesWidget(suitesData) {
|
|
|
1483
1652
|
)} tests</span>
|
|
1484
1653
|
</div>
|
|
1485
1654
|
|
|
1486
|
-
<div class="suites-grid-container"
|
|
1655
|
+
<div class="suites-grid-container">
|
|
1487
1656
|
<div class="suites-grid">
|
|
1488
1657
|
${suitesData
|
|
1489
1658
|
.map(
|
|
1490
1659
|
(suite) => `
|
|
1491
1660
|
<div class="suite-card status-${suite.statusOverall}">
|
|
1492
1661
|
<div class="suite-card-header">
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}</span>
|
|
1662
|
+
<h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1663
|
+
<div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
|
|
1664
|
+
</div>
|
|
1665
|
+
|
|
1666
|
+
<div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
|
|
1667
|
+
<span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
|
|
1668
|
+
</div>
|
|
1669
|
+
|
|
1670
|
+
<div class="suite-card-body">
|
|
1671
|
+
<span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
|
|
1504
1672
|
<div class="suite-stats">
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1673
|
+
<span class="stat-pill passed" title="Passed">
|
|
1674
|
+
<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>
|
|
1675
|
+
${suite.passed}
|
|
1676
|
+
</span>
|
|
1677
|
+
<span class="stat-pill failed" title="Failed">
|
|
1678
|
+
<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>
|
|
1679
|
+
${suite.failed}
|
|
1680
|
+
</span>
|
|
1681
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1682
|
+
<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>
|
|
1683
|
+
${suite.flaky || 0}
|
|
1684
|
+
</span>
|
|
1685
|
+
<span class="stat-pill skipped" title="Skipped">
|
|
1686
|
+
<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>
|
|
1687
|
+
${suite.skipped}
|
|
1688
|
+
</span>
|
|
1520
1689
|
</div>
|
|
1521
1690
|
</div>
|
|
1522
1691
|
</div>`,
|
|
@@ -1917,6 +2086,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
1917
2086
|
const data = {
|
|
1918
2087
|
passed: [0, 0, 0, 0, 0],
|
|
1919
2088
|
failed: [0, 0, 0, 0, 0],
|
|
2089
|
+
flaky: [0, 0, 0, 0, 0],
|
|
1920
2090
|
skipped: [0, 0, 0, 0, 0],
|
|
1921
2091
|
};
|
|
1922
2092
|
|
|
@@ -1935,6 +2105,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
1935
2105
|
status === "interrupted"
|
|
1936
2106
|
) {
|
|
1937
2107
|
data.failed[index]++;
|
|
2108
|
+
} else if (status === "flaky") {
|
|
2109
|
+
data.flaky[index]++;
|
|
1938
2110
|
} else {
|
|
1939
2111
|
data.skipped[index]++;
|
|
1940
2112
|
}
|
|
@@ -1948,6 +2120,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
1948
2120
|
const seriesData = [
|
|
1949
2121
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
1950
2122
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2123
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
1951
2124
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
1952
2125
|
];
|
|
1953
2126
|
|
|
@@ -2061,32 +2234,85 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2061
2234
|
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
|
|
2062
2235
|
};
|
|
2063
2236
|
|
|
2064
|
-
|
|
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
|
-
);
|
|
2237
|
+
|
|
2070
2238
|
const avgTestDuration =
|
|
2071
2239
|
runSummary.totalTests > 0
|
|
2072
2240
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2073
2241
|
: "0.0s";
|
|
2074
2242
|
|
|
2243
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2244
|
+
|
|
2075
2245
|
// Calculate retry statistics
|
|
2246
|
+
let retriedTestsCount = 0;
|
|
2076
2247
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2077
|
-
if (test.
|
|
2078
|
-
|
|
2248
|
+
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2249
|
+
// Filter out any "passed" or "skipped" entries in the history
|
|
2250
|
+
// We only count attempts that actually failed or timed out, triggering a retry.
|
|
2251
|
+
const unsuccessfulRetries = test.retryHistory.filter(attempt =>
|
|
2252
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
2253
|
+
);
|
|
2254
|
+
if (unsuccessfulRetries.length > 0) {
|
|
2255
|
+
retriedTestsCount++;
|
|
2256
|
+
}
|
|
2257
|
+
return acc + unsuccessfulRetries.length;
|
|
2079
2258
|
}
|
|
2080
2259
|
return acc;
|
|
2081
2260
|
}, 0);
|
|
2082
2261
|
|
|
2262
|
+
// --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
|
|
2263
|
+
let calculatedPassed = 0;
|
|
2264
|
+
let calculatedFailed = 0;
|
|
2265
|
+
let calculatedSkipped = 0;
|
|
2266
|
+
let calculatedFlaky = 0;
|
|
2267
|
+
let calculatedTotal = 0;
|
|
2268
|
+
|
|
2269
|
+
(results || []).forEach(test => {
|
|
2270
|
+
calculatedTotal++;
|
|
2271
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2272
|
+
let statusToUse = test.status;
|
|
2273
|
+
if (test.outcome === 'flaky') {
|
|
2274
|
+
statusToUse = 'flaky';
|
|
2275
|
+
} else if (test.status === 'flaky') {
|
|
2276
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2277
|
+
statusToUse = 'flaky';
|
|
2278
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2279
|
+
statusToUse = test.final_status;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// Update test status in place for charts
|
|
2283
|
+
test.status = statusToUse;
|
|
2284
|
+
|
|
2285
|
+
const s = String(statusToUse).toLowerCase();
|
|
2286
|
+
if (s === 'passed') calculatedPassed++;
|
|
2287
|
+
else if (s === 'skipped') calculatedSkipped++;
|
|
2288
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2289
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2290
|
+
});
|
|
2291
|
+
|
|
2292
|
+
// Override runSummary counts with our calculated ones if results exist
|
|
2293
|
+
if (results && results.length > 0) {
|
|
2294
|
+
runSummary.passed = calculatedPassed;
|
|
2295
|
+
runSummary.failed = calculatedFailed;
|
|
2296
|
+
runSummary.skipped = calculatedSkipped;
|
|
2297
|
+
runSummary.flaky = calculatedFlaky;
|
|
2298
|
+
runSummary.totalTests = calculatedTotal;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2302
|
+
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2303
|
+
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2304
|
+
const skipPercentage = Math.round(
|
|
2305
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2306
|
+
);
|
|
2307
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2308
|
+
|
|
2309
|
+
|
|
2083
2310
|
// Calculate browser distribution
|
|
2084
2311
|
const browserStats = (results || []).reduce((acc, test) => {
|
|
2085
2312
|
let browserName = "unknown";
|
|
2086
2313
|
if (test.browser) {
|
|
2087
|
-
//
|
|
2088
|
-
|
|
2089
|
-
browserName = match ? match[1] : test.browser;
|
|
2314
|
+
// Use full browser name
|
|
2315
|
+
browserName = test.browser;
|
|
2090
2316
|
}
|
|
2091
2317
|
acc[browserName] = (acc[browserName] || 0) + 1;
|
|
2092
2318
|
return acc;
|
|
@@ -2112,6 +2338,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2112
2338
|
// --- Simplified Severity Badge ---
|
|
2113
2339
|
const severity = test.severity || "Medium";
|
|
2114
2340
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2341
|
+
|
|
2342
|
+
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2343
|
+
const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
|
|
2344
|
+
const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2115
2345
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2116
2346
|
if (!steps || steps.length === 0)
|
|
2117
2347
|
return "<div class='no-steps'>No steps recorded for this test.</div>";
|
|
@@ -2119,17 +2349,20 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2119
2349
|
.map((step) => {
|
|
2120
2350
|
const hasNestedSteps = step.steps && step.steps.length > 0;
|
|
2121
2351
|
const isHook = step.hookType;
|
|
2352
|
+
const isFailedStep = step.isFailedStep === true;
|
|
2122
2353
|
const stepClass = isHook
|
|
2123
2354
|
? `step-hook step-hook-${step.hookType}`
|
|
2124
2355
|
: "";
|
|
2356
|
+
const failedStepClass = isFailedStep ? " failed-step-highlight" : "";
|
|
2125
2357
|
const hookIndicator = isHook ? ` (${step.hookType} hook)` : "";
|
|
2358
|
+
const failedStepIndicator = isFailedStep ? ` <span class="failed-step-marker">⚠️ Failed at this step</span>` : "";
|
|
2126
2359
|
return `
|
|
2127
|
-
<div class="step-item" style="--depth: ${depth};">
|
|
2360
|
+
<div class="step-item${failedStepClass}" style="--depth: ${depth};">
|
|
2128
2361
|
<div class="step-header ${stepClass}" role="button" aria-expanded="false">
|
|
2129
2362
|
<span class="step-icon">${getStatusIcon(step.status)}</span>
|
|
2130
2363
|
<span class="step-title">${sanitizeHTML(
|
|
2131
2364
|
step.title,
|
|
2132
|
-
)}${hookIndicator}</span>
|
|
2365
|
+
)}${hookIndicator}${failedStepIndicator}</span>
|
|
2133
2366
|
<span class="step-duration">${formatDuration(
|
|
2134
2367
|
step.duration,
|
|
2135
2368
|
)}</span>
|
|
@@ -2142,6 +2375,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2142
2375
|
)}</div>`
|
|
2143
2376
|
: ""
|
|
2144
2377
|
}
|
|
2378
|
+
${
|
|
2379
|
+
step.codeSnippet
|
|
2380
|
+
? `<div class="code-snippet-section"><pre class="code-snippet">${sanitizeHTML(
|
|
2381
|
+
step.codeSnippet,
|
|
2382
|
+
)}</pre></div>`
|
|
2383
|
+
: ""
|
|
2384
|
+
}
|
|
2145
2385
|
${
|
|
2146
2386
|
step.errorMessage
|
|
2147
2387
|
? `<div class="test-error-summary">
|
|
@@ -2157,74 +2397,69 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2157
2397
|
onclick="copyErrorToClipboard(this)"
|
|
2158
2398
|
style="
|
|
2159
2399
|
margin-top: 8px;
|
|
2160
|
-
padding:
|
|
2400
|
+
padding: 6px 12px;
|
|
2161
2401
|
background: #f0f0f0;
|
|
2162
2402
|
border: 2px solid #ccc;
|
|
2163
2403
|
border-radius: 4px;
|
|
2164
2404
|
cursor: pointer;
|
|
2165
2405
|
font-size: 12px;
|
|
2166
|
-
border-color: #8B0000;
|
|
2167
|
-
color: #8B0000;
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
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>
|
|
2406
|
+
border-color: #8B0000;
|
|
2407
|
+
color: #8B0000;
|
|
2408
|
+
align-self: flex-end;
|
|
2409
|
+
width: auto;
|
|
2410
|
+
"
|
|
2411
|
+
onmouseover="this.style.background='#e0e0e0'"
|
|
2412
|
+
onmouseout="this.style.background='#f0f0f0'"
|
|
2413
|
+
>
|
|
2414
|
+
Copy Error Prompt
|
|
2415
|
+
</button>
|
|
2416
|
+
</div>`
|
|
2417
|
+
: ""
|
|
2418
|
+
}
|
|
2419
|
+
${
|
|
2420
|
+
hasNestedSteps
|
|
2421
|
+
? `<div class="nested-steps">${generateStepsHTML(
|
|
2422
|
+
step.steps,
|
|
2423
|
+
depth + 1,
|
|
2424
|
+
)}</div>`
|
|
2425
|
+
: ""
|
|
2426
|
+
}
|
|
2427
|
+
</div>
|
|
2428
|
+
</div>`;
|
|
2429
|
+
})
|
|
2430
|
+
.join("");
|
|
2431
|
+
};
|
|
2432
|
+
|
|
2433
|
+
// Helper for Tab Badges
|
|
2434
|
+
const getSmallStatusBadge = (status) => {
|
|
2435
|
+
const s = String(status).toLowerCase();
|
|
2436
|
+
let colorVar = 'var(--text-tertiary)';
|
|
2437
|
+
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2438
|
+
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2439
|
+
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2440
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2441
|
+
|
|
2442
|
+
return `<span style="
|
|
2443
|
+
display: inline-block;
|
|
2444
|
+
width: 8px;
|
|
2445
|
+
height: 8px;
|
|
2446
|
+
border-radius: 50%;
|
|
2447
|
+
background-color: ${colorVar};
|
|
2448
|
+
margin-left: 6px;
|
|
2449
|
+
vertical-align: middle;
|
|
2450
|
+
" title="${s}"></span>`;
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2453
|
+
// Function to generate test content HTML (used for base run and retry tabs)
|
|
2454
|
+
const getTestContentHTML = (testData, runSuffix) => {
|
|
2455
|
+
const logId = `stdout-log-${test.id || index}-${runSuffix}`;
|
|
2456
|
+
return `
|
|
2457
|
+
<p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
|
|
2223
2458
|
${
|
|
2224
|
-
|
|
2459
|
+
testData.annotations && testData.annotations.length > 0
|
|
2225
2460
|
? `<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
2461
|
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
2227
|
-
${
|
|
2462
|
+
${testData.annotations
|
|
2228
2463
|
.map((annotation, idx) => {
|
|
2229
2464
|
const isIssueOrBug =
|
|
2230
2465
|
annotation.type === "issue" ||
|
|
@@ -2232,7 +2467,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2232
2467
|
const descriptionText = annotation.description || "";
|
|
2233
2468
|
const typeLabel = sanitizeHTML(annotation.type);
|
|
2234
2469
|
const descriptionHtml =
|
|
2235
|
-
isIssueOrBug && descriptionText.match(/^[A-Z]
|
|
2470
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
|
|
2236
2471
|
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
2237
2472
|
descriptionText,
|
|
2238
2473
|
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
@@ -2247,7 +2482,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2247
2482
|
}</div>`
|
|
2248
2483
|
: "";
|
|
2249
2484
|
return `<div style="margin-bottom: ${
|
|
2250
|
-
idx <
|
|
2485
|
+
idx < testData.annotations.length - 1 ? "10px" : "0"
|
|
2251
2486
|
};">
|
|
2252
2487
|
<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
2488
|
${
|
|
@@ -2263,21 +2498,21 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2263
2498
|
: ""
|
|
2264
2499
|
}
|
|
2265
2500
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
2266
|
-
|
|
2501
|
+
testData.workerId,
|
|
2267
2502
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2268
|
-
|
|
2503
|
+
testData.totalWorkers,
|
|
2269
2504
|
)}]</p>
|
|
2270
2505
|
${
|
|
2271
|
-
|
|
2272
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2273
|
-
|
|
2274
|
-
)}
|
|
2506
|
+
testData.errorMessage
|
|
2507
|
+
? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
|
|
2508
|
+
testData.errorMessage,
|
|
2509
|
+
)}</div>
|
|
2275
2510
|
<button
|
|
2276
2511
|
class="copy-error-btn"
|
|
2277
2512
|
onclick="copyErrorToClipboard(this)"
|
|
2278
2513
|
style="
|
|
2279
2514
|
margin-top: 8px;
|
|
2280
|
-
padding:
|
|
2515
|
+
padding: 6px 12px;
|
|
2281
2516
|
background: #f0f0f0;
|
|
2282
2517
|
border: 2px solid #ccc;
|
|
2283
2518
|
border-radius: 4px;
|
|
@@ -2285,6 +2520,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2285
2520
|
font-size: 12px;
|
|
2286
2521
|
border-color: #8B0000;
|
|
2287
2522
|
color: #8B0000;
|
|
2523
|
+
align-self: flex-end;
|
|
2524
|
+
width: auto;
|
|
2288
2525
|
"
|
|
2289
2526
|
onmouseover="this.style.background='#e0e0e0'"
|
|
2290
2527
|
onmouseout="this.style.background='#f0f0f0'"
|
|
@@ -2295,50 +2532,48 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2295
2532
|
: ""
|
|
2296
2533
|
}
|
|
2297
2534
|
${
|
|
2298
|
-
|
|
2535
|
+
testData.snippet
|
|
2299
2536
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2300
|
-
|
|
2537
|
+
testData.snippet,
|
|
2301
2538
|
)}</code></pre></div>`
|
|
2302
2539
|
: ""
|
|
2303
2540
|
}
|
|
2304
2541
|
<h4>Steps</h4>
|
|
2305
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2542
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2306
2543
|
${(() => {
|
|
2307
|
-
if (!
|
|
2308
|
-
// Create a unique ID for the <pre> element to target it for copying
|
|
2309
|
-
const logId = `stdout-log-${test.id || index}`;
|
|
2544
|
+
if (!testData.stdout || testData.stdout.length === 0) return "";
|
|
2310
2545
|
return `<div class="console-output-section">
|
|
2311
2546
|
<h4>Console Output (stdout)
|
|
2312
|
-
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button>
|
|
2547
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
|
|
2313
2548
|
</h4>
|
|
2314
2549
|
<div class="log-wrapper">
|
|
2315
2550
|
<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
|
-
|
|
2551
|
+
testData.stdout
|
|
2317
2552
|
.map((line) => sanitizeHTML(line))
|
|
2318
|
-
.join("
|
|
2553
|
+
.join("\\n"),
|
|
2319
2554
|
)}</pre>
|
|
2320
2555
|
</div>
|
|
2321
2556
|
</div>`;
|
|
2322
2557
|
})()}
|
|
2323
2558
|
${
|
|
2324
|
-
|
|
2559
|
+
testData.stderr && testData.stderr.length > 0
|
|
2325
2560
|
? `<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
|
-
|
|
2561
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\\n"),
|
|
2327
2562
|
)}</pre></div>`
|
|
2328
2563
|
: ""
|
|
2329
2564
|
}
|
|
2330
2565
|
${
|
|
2331
|
-
|
|
2566
|
+
testData.screenshots && testData.screenshots.length > 0
|
|
2332
2567
|
? `
|
|
2333
2568
|
<div class="attachments-section">
|
|
2334
2569
|
<h4>Screenshots</h4>
|
|
2335
2570
|
<div class="attachments-grid">
|
|
2336
|
-
${
|
|
2571
|
+
${testData.screenshots
|
|
2337
2572
|
.map(
|
|
2338
|
-
(screenshot,
|
|
2573
|
+
(screenshot, screenshotIndex) => `
|
|
2339
2574
|
<div class="attachment-item">
|
|
2340
2575
|
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
2341
|
-
|
|
2576
|
+
screenshotIndex + 1
|
|
2342
2577
|
}">
|
|
2343
2578
|
<div class="attachment-info">
|
|
2344
2579
|
<div class="trace-actions">
|
|
@@ -2347,7 +2582,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2347
2582
|
)}" target="_blank" class="view-full">View Full Image</a>
|
|
2348
2583
|
<a href="${fixPath(
|
|
2349
2584
|
screenshot,
|
|
2350
|
-
)}" target="_blank" download="screenshot-${Date.now()}-${
|
|
2585
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
|
|
2351
2586
|
</div>
|
|
2352
2587
|
</div>
|
|
2353
2588
|
</div>
|
|
@@ -2360,9 +2595,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2360
2595
|
: ""
|
|
2361
2596
|
}
|
|
2362
2597
|
${
|
|
2363
|
-
|
|
2364
|
-
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${
|
|
2365
|
-
.map((videoUrl,
|
|
2598
|
+
testData.videoPath && testData.videoPath.length > 0
|
|
2599
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
|
|
2600
|
+
.map((videoUrl, videoIndex) => {
|
|
2366
2601
|
const fixedVideoUrl = fixPath(videoUrl);
|
|
2367
2602
|
const fileExtension = String(fixedVideoUrl)
|
|
2368
2603
|
.split(".")
|
|
@@ -2378,7 +2613,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2378
2613
|
}[fileExtension] || "video/mp4";
|
|
2379
2614
|
return `<div class="attachment-item video-item">
|
|
2380
2615
|
<video controls width="100%" height="auto" title="Video ${
|
|
2381
|
-
|
|
2616
|
+
videoIndex + 1
|
|
2382
2617
|
}">
|
|
2383
2618
|
<source src="${sanitizeHTML(
|
|
2384
2619
|
fixedVideoUrl,
|
|
@@ -2389,7 +2624,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2389
2624
|
<div class="trace-actions">
|
|
2390
2625
|
<a href="${sanitizeHTML(
|
|
2391
2626
|
fixedVideoUrl,
|
|
2392
|
-
)}" target="_blank" download="video-${Date.now()}-${
|
|
2627
|
+
)}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
|
|
2393
2628
|
</div>
|
|
2394
2629
|
</div>
|
|
2395
2630
|
</div>`;
|
|
@@ -2398,7 +2633,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2398
2633
|
: ""
|
|
2399
2634
|
}
|
|
2400
2635
|
${
|
|
2401
|
-
|
|
2636
|
+
testData.tracePath
|
|
2402
2637
|
? `
|
|
2403
2638
|
<div class="attachments-section">
|
|
2404
2639
|
<h4>Trace Files</h4>
|
|
@@ -2407,15 +2642,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2407
2642
|
<div class="trace-preview">
|
|
2408
2643
|
<span class="trace-icon">📄</span>
|
|
2409
2644
|
<span class="trace-name">${sanitizeHTML(
|
|
2410
|
-
path.basename(
|
|
2645
|
+
path.basename(testData.tracePath),
|
|
2411
2646
|
)}</span>
|
|
2412
2647
|
</div>
|
|
2413
2648
|
<div class="attachment-info">
|
|
2414
2649
|
<div class="trace-actions">
|
|
2415
2650
|
<a href="${sanitizeHTML(
|
|
2416
|
-
fixPath(
|
|
2651
|
+
fixPath(testData.tracePath),
|
|
2417
2652
|
)}" target="_blank" download="${sanitizeHTML(
|
|
2418
|
-
path.basename(
|
|
2653
|
+
path.basename(testData.tracePath),
|
|
2419
2654
|
)}" class="download-trace">Download Trace</a>
|
|
2420
2655
|
</div>
|
|
2421
2656
|
</div>
|
|
@@ -2426,12 +2661,12 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2426
2661
|
: ""
|
|
2427
2662
|
}
|
|
2428
2663
|
${
|
|
2429
|
-
|
|
2664
|
+
testData.attachments && testData.attachments.length > 0
|
|
2430
2665
|
? `
|
|
2431
2666
|
<div class="attachments-section">
|
|
2432
2667
|
<h4>Other Attachments</h4>
|
|
2433
2668
|
<div class="attachments-grid">
|
|
2434
|
-
${
|
|
2669
|
+
${testData.attachments
|
|
2435
2670
|
.map(
|
|
2436
2671
|
(attachment) => `
|
|
2437
2672
|
<div class="attachment-item generic-attachment">
|
|
@@ -2467,13 +2702,76 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2467
2702
|
`
|
|
2468
2703
|
: ""
|
|
2469
2704
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2705
|
+
|
|
2706
|
+
`;
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
// Determine header status: use final_status if retried, else normal status
|
|
2710
|
+
const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
|
|
2711
|
+
? test.final_status
|
|
2712
|
+
: test.status;
|
|
2713
|
+
|
|
2714
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2715
|
+
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2716
|
+
: '';
|
|
2717
|
+
|
|
2718
|
+
return `
|
|
2719
|
+
<div class="test-case" data-status="${
|
|
2720
|
+
headerStatus
|
|
2721
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
2722
|
+
.join(",")
|
|
2723
|
+
.toLowerCase()}">
|
|
2724
|
+
<div class="test-case-header" role="button" aria-expanded="false">
|
|
2725
|
+
<div class="test-case-summary">
|
|
2726
|
+
<span class="test-case-title" title="${sanitizeHTML(
|
|
2727
|
+
test.name,
|
|
2728
|
+
)}">${sanitizeHTML(testTitle)}</span>
|
|
2729
|
+
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
2730
|
+
</div>
|
|
2731
|
+
<div class="test-case-meta">
|
|
2732
|
+
${severityBadge}
|
|
2733
|
+
${retryBadge}
|
|
2734
|
+
${outcomeBadge}
|
|
2735
|
+
${
|
|
2736
|
+
test.tags && test.tags.length > 0
|
|
2737
|
+
? test.tags
|
|
2738
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
2739
|
+
.join(" ")
|
|
2740
|
+
: ""
|
|
2741
|
+
}
|
|
2742
|
+
</div>
|
|
2743
|
+
<div class="test-case-status-duration">
|
|
2744
|
+
<span class="status-badge ${getStatusClass(headerStatus)}">${String(
|
|
2745
|
+
headerStatus,
|
|
2746
|
+
).toUpperCase()}</span>
|
|
2747
|
+
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2748
|
+
</div>
|
|
2749
|
+
</div>
|
|
2750
|
+
<div class="test-case-content" style="display: none;">
|
|
2751
|
+
${test.retryHistory && test.retryHistory.length > 0 ? `
|
|
2752
|
+
<div class="retry-tabs-container">
|
|
2753
|
+
<div class="retry-tabs-header">
|
|
2754
|
+
<button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
|
|
2755
|
+
Base Run ${getSmallStatusBadge(test.final_status || test.status)}
|
|
2756
|
+
</button>
|
|
2757
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2758
|
+
<button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
|
|
2759
|
+
Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
|
|
2760
|
+
</button>
|
|
2761
|
+
`).join('')}
|
|
2762
|
+
</div>
|
|
2763
|
+
|
|
2764
|
+
<div id="base-run-${test.id}" class="retry-tab-content active">
|
|
2765
|
+
${getTestContentHTML(test, 'base')}
|
|
2766
|
+
</div>
|
|
2767
|
+
|
|
2768
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2769
|
+
<div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
|
|
2770
|
+
${getTestContentHTML(retry, `retry-${idx + 1}`)}
|
|
2771
|
+
</div>
|
|
2772
|
+
`).join('')}
|
|
2773
|
+
</div>
|
|
2774
|
+
` : getTestContentHTML(test, 'single')}
|
|
2477
2775
|
</div>
|
|
2478
2776
|
</div>`;
|
|
2479
2777
|
})
|
|
@@ -2506,7 +2804,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2506
2804
|
--success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
|
|
2507
2805
|
--danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
|
|
2508
2806
|
--warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
|
|
2509
|
-
--info-color: #3b82f6;
|
|
2807
|
+
--info-color: #3b82f6;
|
|
2808
|
+
--flaky-color: #00ccd3;
|
|
2510
2809
|
--neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
|
|
2511
2810
|
--neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
|
|
2512
2811
|
--neutral-800: #262626; --neutral-900: #171717;
|
|
@@ -2524,7 +2823,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2524
2823
|
--glow-primary: 0 0 20px rgba(99, 102, 241, 0.4), 0 0 40px rgba(99, 102, 241, 0.2);
|
|
2525
2824
|
--glow-success: 0 0 20px rgba(16, 185, 129, 0.4), 0 0 40px rgba(16, 185, 129, 0.2);
|
|
2526
2825
|
--glow-danger: 0 0 20px rgba(239, 68, 68, 0.4), 0 0 40px rgba(239, 68, 68, 0.2);
|
|
2527
|
-
|
|
2826
|
+
--bg-card: #ffffff; --bg-card-hover: #f8fafc;
|
|
2827
|
+
--gradient-card: linear-gradient(145deg, #ffffff 0%, #f9fafb 100%);
|
|
2828
|
+
--border-medium: #cbd5e1;
|
|
2528
2829
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2529
2830
|
::selection { background: var(--primary-color); color: white; }
|
|
2530
2831
|
::-webkit-scrollbar { width: 0; height: 0; display: none; }
|
|
@@ -2596,11 +2897,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2596
2897
|
display: flex;
|
|
2597
2898
|
gap: 16px;
|
|
2598
2899
|
align-items: stretch;
|
|
2599
|
-
background:
|
|
2900
|
+
background: transparent;
|
|
2600
2901
|
border-radius: 12px;
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
overflow: hidden;
|
|
2902
|
+
padding: 0;
|
|
2903
|
+
box-shadow: var(--shadow-md); /* Inherited from base static style */
|
|
2904
|
+
overflow: hidden; /* Inherited */
|
|
2604
2905
|
}
|
|
2605
2906
|
.run-info-item {
|
|
2606
2907
|
display: flex;
|
|
@@ -2609,54 +2910,61 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2609
2910
|
padding: 16px 28px;
|
|
2610
2911
|
position: relative;
|
|
2611
2912
|
flex: 1;
|
|
2612
|
-
min-width:
|
|
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);
|
|
2913
|
+
min-width: fit-content;
|
|
2625
2914
|
}
|
|
2915
|
+
|
|
2626
2916
|
.run-info-item:first-child {
|
|
2627
|
-
background: linear-gradient(135deg,
|
|
2917
|
+
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%);
|
|
2918
|
+
border: 1px solid rgba(251, 191, 36, 0.3);
|
|
2919
|
+
border-radius: var(--radius-md);
|
|
2920
|
+
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);
|
|
2921
|
+
}
|
|
2922
|
+
.run-info-item:first-child:hover {
|
|
2923
|
+
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%);
|
|
2924
|
+
border-color: rgba(251, 191, 36, 0.4);
|
|
2925
|
+
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
2926
|
}
|
|
2629
2927
|
.run-info-item:last-child {
|
|
2630
|
-
background: linear-gradient(135deg,
|
|
2928
|
+
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%);
|
|
2929
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
2930
|
+
border-radius: var(--radius-md);
|
|
2931
|
+
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);
|
|
2932
|
+
}
|
|
2933
|
+
.run-info-item:last-child:hover {
|
|
2934
|
+
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%);
|
|
2935
|
+
border-color: rgba(139, 92, 246, 0.4);
|
|
2936
|
+
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
2937
|
}
|
|
2632
2938
|
.run-info strong {
|
|
2633
2939
|
display: flex;
|
|
2634
2940
|
align-items: center;
|
|
2635
|
-
gap:
|
|
2941
|
+
gap: 8px;
|
|
2636
2942
|
font-size: 0.7em;
|
|
2637
2943
|
text-transform: uppercase;
|
|
2638
|
-
letter-spacing:
|
|
2639
|
-
color: #
|
|
2944
|
+
letter-spacing: 1.2px;
|
|
2945
|
+
color: #9ca3af;
|
|
2640
2946
|
margin: 0;
|
|
2641
2947
|
font-weight: 700;
|
|
2642
2948
|
}
|
|
2643
2949
|
.run-info strong::before {
|
|
2644
2950
|
content: '';
|
|
2645
|
-
width:
|
|
2646
|
-
height:
|
|
2951
|
+
width: 10px;
|
|
2952
|
+
height: 10px;
|
|
2647
2953
|
border-radius: 50%;
|
|
2648
2954
|
background: currentColor;
|
|
2649
|
-
opacity: 0.
|
|
2955
|
+
opacity: 0.7;
|
|
2956
|
+
box-shadow: 0 0 8px currentColor;
|
|
2650
2957
|
}
|
|
2651
2958
|
.run-info-item:first-child strong {
|
|
2652
|
-
color:
|
|
2959
|
+
color: var(--warning-light);
|
|
2653
2960
|
}
|
|
2654
2961
|
.run-info-item:last-child strong {
|
|
2655
|
-
color:
|
|
2962
|
+
color: var(--secondary-light);
|
|
2656
2963
|
}
|
|
2657
2964
|
.run-info span {
|
|
2965
|
+
font-size: 1.5em;
|
|
2658
2966
|
font-weight: 800;
|
|
2659
|
-
color: #0f172a;
|
|
2967
|
+
color: #0f172a; /* Adjusted for light theme consistency, static uses #f9fafb */
|
|
2660
2968
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
2661
2969
|
letter-spacing: -0.02em;
|
|
2662
2970
|
line-height: 1.2;
|
|
@@ -2720,12 +3028,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2720
3028
|
}
|
|
2721
3029
|
}
|
|
2722
3030
|
|
|
3031
|
+
|
|
3032
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3033
|
+
|
|
2723
3034
|
.dashboard-grid {
|
|
2724
3035
|
display: grid;
|
|
2725
3036
|
grid-template-columns: repeat(4, 1fr);
|
|
2726
3037
|
gap: 0;
|
|
2727
3038
|
margin: 0 0 40px 0;
|
|
2728
3039
|
}
|
|
3040
|
+
.stats-pill.failed { color: var(--danger-dark); }
|
|
3041
|
+
.stats-pill.flaky { color: #4b5563; }
|
|
2729
3042
|
.browser-breakdown {
|
|
2730
3043
|
display: flex;
|
|
2731
3044
|
flex-direction: column;
|
|
@@ -2766,9 +3079,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2766
3079
|
color: #0f172a;
|
|
2767
3080
|
text-transform: capitalize;
|
|
2768
3081
|
font-size: 1.05em;
|
|
3082
|
+
white-space: nowrap;
|
|
3083
|
+
overflow: hidden;
|
|
3084
|
+
text-overflow: ellipsis;
|
|
3085
|
+
flex: 1;
|
|
3086
|
+
min-width: 0;
|
|
3087
|
+
margin-right: 8px;
|
|
2769
3088
|
}
|
|
2770
3089
|
.browser-stats {
|
|
2771
3090
|
color: #64748b;
|
|
3091
|
+
white-space: nowrap;
|
|
3092
|
+
flex-shrink: 0;
|
|
2772
3093
|
font-weight: 700;
|
|
2773
3094
|
font-size: 0.95em;
|
|
2774
3095
|
}
|
|
@@ -2812,9 +3133,11 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2812
3133
|
align-items: flex-start;
|
|
2813
3134
|
}
|
|
2814
3135
|
.run-info {
|
|
3136
|
+
flex-direction: column;
|
|
3137
|
+
gap: 0;
|
|
2815
3138
|
width: 100%;
|
|
2816
|
-
|
|
2817
|
-
|
|
3139
|
+
border-radius: 14px;
|
|
3140
|
+
overflow: hidden;
|
|
2818
3141
|
}
|
|
2819
3142
|
.dashboard-grid {
|
|
2820
3143
|
grid-template-columns: repeat(2, 1fr);
|
|
@@ -2823,11 +3146,23 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2823
3146
|
.summary-card:nth-child(n+7) { border-bottom: none; }
|
|
2824
3147
|
.filters {
|
|
2825
3148
|
padding: 24px;
|
|
2826
|
-
flex-
|
|
3149
|
+
flex-wrap: wrap;
|
|
3150
|
+
gap: 12px;
|
|
3151
|
+
}
|
|
3152
|
+
.filters input {
|
|
3153
|
+
flex: 1 1 auto;
|
|
3154
|
+
min-width: 0;
|
|
3155
|
+
width: auto;
|
|
3156
|
+
}
|
|
3157
|
+
.filters select {
|
|
3158
|
+
flex: 0 0 auto;
|
|
3159
|
+
min-width: 0;
|
|
3160
|
+
width: auto;
|
|
3161
|
+
}
|
|
3162
|
+
.filters button {
|
|
3163
|
+
width: auto;
|
|
3164
|
+
flex: 0 0 auto;
|
|
2827
3165
|
}
|
|
2828
|
-
.filters input { min-width: 100%; }
|
|
2829
|
-
.filters select { min-width: 100%; }
|
|
2830
|
-
.filters button { width: 100%; }
|
|
2831
3166
|
.copy-btn {
|
|
2832
3167
|
font-size: 0.75em;
|
|
2833
3168
|
padding: 8px 16px;
|
|
@@ -3026,16 +3361,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3026
3361
|
display: none;
|
|
3027
3362
|
}
|
|
3028
3363
|
.run-info-item:not(:last-child) {
|
|
3029
|
-
border-bottom: 1px solid var(--
|
|
3364
|
+
border-bottom: 1px solid var(--border-medium);
|
|
3030
3365
|
}
|
|
3031
|
-
.run-info strong {
|
|
3032
|
-
font-size: 0.65em;
|
|
3366
|
+
.run-info strong {
|
|
3367
|
+
font-size: 0.65em;
|
|
3033
3368
|
}
|
|
3034
|
-
.run-info span {
|
|
3035
|
-
font-size: 1.1em;
|
|
3036
|
-
white-space: normal;
|
|
3037
|
-
word-break: break-word;
|
|
3038
|
-
overflow-wrap: break-word;
|
|
3369
|
+
.run-info span {
|
|
3370
|
+
font-size: 1.1em;
|
|
3039
3371
|
}
|
|
3040
3372
|
.tabs {
|
|
3041
3373
|
flex-wrap: wrap;
|
|
@@ -3151,12 +3483,19 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3151
3483
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
3152
3484
|
}
|
|
3153
3485
|
.summary-card.status-failed .value { color: #ef4444; }
|
|
3486
|
+
.summary-card.status-flaky::before { background: #00ccd3; }
|
|
3154
3487
|
.summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
|
|
3155
3488
|
.summary-card.status-skipped:hover {
|
|
3156
3489
|
background: rgba(245, 158, 11, 0.15);
|
|
3157
3490
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
|
|
3158
3491
|
}
|
|
3159
3492
|
.summary-card.status-skipped .value { color: #f59e0b; }
|
|
3493
|
+
.summary-card.flaky-status { background: rgba(0, 204, 211, 0.05); }
|
|
3494
|
+
.summary-card.flaky-status:hover {
|
|
3495
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3496
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3497
|
+
}
|
|
3498
|
+
.summary-card.flaky-status .value { color: #00ccd3; }
|
|
3160
3499
|
.summary-card:not([class*='status-']) .value { color: #0f172a; }
|
|
3161
3500
|
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
|
|
3162
3501
|
.dashboard-column {
|
|
@@ -3197,58 +3536,167 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3197
3536
|
.status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
|
|
3198
3537
|
.status-badge-small-tooltip.status-skipped { background-color: var(--warning-color); }
|
|
3199
3538
|
.status-badge-small-tooltip.status-unknown { background-color: var(--dark-gray-color); }
|
|
3200
|
-
.suites-header {
|
|
3539
|
+
.suites-header {
|
|
3540
|
+
flex-shrink: 0;
|
|
3541
|
+
display: flex;
|
|
3542
|
+
justify-content: space-between;
|
|
3543
|
+
align-items: center;
|
|
3544
|
+
margin-bottom: 20px;
|
|
3545
|
+
}
|
|
3201
3546
|
.summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
3202
3547
|
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
3203
|
-
.
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
padding: 24px;
|
|
3207
|
-
background: white;
|
|
3208
|
-
transition: all 0.15s ease;
|
|
3548
|
+
.suites-widget {
|
|
3549
|
+
display: flex;
|
|
3550
|
+
flex-direction: column;
|
|
3209
3551
|
}
|
|
3210
|
-
.
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3552
|
+
.fixed-height-widget {
|
|
3553
|
+
height: 450px;
|
|
3554
|
+
}
|
|
3555
|
+
.suites-grid-container {
|
|
3556
|
+
flex-grow: 1;
|
|
3557
|
+
overflow-y: auto;
|
|
3558
|
+
padding-right: 5px;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
@media (max-width: 768px) {
|
|
3562
|
+
.fixed-height-widget {
|
|
3563
|
+
height: auto;
|
|
3564
|
+
max-height: 600px;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
.suite-card {
|
|
3568
|
+
background: #ffffff;
|
|
3569
|
+
border: 1px solid var(--border-light);
|
|
3570
|
+
border-radius: 16px;
|
|
3571
|
+
padding: 24px;
|
|
3572
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
|
3573
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3574
|
+
display: flex;
|
|
3575
|
+
flex-direction: column;
|
|
3576
|
+
height: 100%;
|
|
3577
|
+
position: relative;
|
|
3578
|
+
overflow: hidden;
|
|
3579
|
+
}
|
|
3580
|
+
.suite-card:hover {
|
|
3581
|
+
transform: translateY(-4px);
|
|
3582
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
3583
|
+
border-color: var(--primary-light);
|
|
3584
|
+
}
|
|
3585
|
+
.suite-card::before {
|
|
3586
|
+
content: '';
|
|
3587
|
+
position: absolute;
|
|
3588
|
+
top: 0;
|
|
3589
|
+
left: 0;
|
|
3590
|
+
width: 100%;
|
|
3591
|
+
height: 4px;
|
|
3592
|
+
background: var(--neutral-200);
|
|
3593
|
+
opacity: 0.8;
|
|
3594
|
+
transition: background 0.3s ease;
|
|
3595
|
+
}
|
|
3596
|
+
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3597
|
+
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3598
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3599
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3600
|
+
|
|
3601
|
+
/* Outcome Badge */
|
|
3602
|
+
.outcome-badge {
|
|
3603
|
+
background-color: var(--secondary-color);
|
|
3604
|
+
color: #fff;
|
|
3605
|
+
padding: 2px 8px;
|
|
3606
|
+
border-radius: 4px;
|
|
3607
|
+
font-size: 0.75em;
|
|
3608
|
+
font-weight: 700;
|
|
3609
|
+
text-transform: uppercase;
|
|
3610
|
+
margin-right: 8px;
|
|
3611
|
+
letter-spacing: 0.5px;
|
|
3612
|
+
}
|
|
3613
|
+
.outcome-badge.flaky {
|
|
3614
|
+
background-color: #00ccd3;
|
|
3615
|
+
color: #fff;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
.suite-card-header {
|
|
3619
|
+
display: flex;
|
|
3620
|
+
justify-content: space-between;
|
|
3621
|
+
align-items: flex-start;
|
|
3622
|
+
margin-bottom: 16px;
|
|
3623
|
+
}
|
|
3624
|
+
.suite-name {
|
|
3625
|
+
font-size: 1.15em;
|
|
3626
|
+
font-weight: 700;
|
|
3627
|
+
color: var(--text-primary);
|
|
3628
|
+
line-height: 1.4;
|
|
3629
|
+
display: -webkit-box;
|
|
3630
|
+
-webkit-line-clamp: 2;
|
|
3631
|
+
-webkit-box-orient: vertical;
|
|
3632
|
+
overflow: hidden;
|
|
3633
|
+
margin-right: 12px;
|
|
3634
|
+
}
|
|
3635
|
+
.status-indicator-dot {
|
|
3636
|
+
width: 10px;
|
|
3637
|
+
height: 10px;
|
|
3638
|
+
border-radius: 50%;
|
|
3639
|
+
flex-shrink: 0;
|
|
3640
|
+
margin-top: 6px;
|
|
3641
|
+
}
|
|
3642
|
+
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3643
|
+
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3644
|
+
.status-indicator-dot.status-flaky { background-color: #00ccd3; box-shadow: 0 0 0 4px rgba(0, 204, 211, 0.15); }
|
|
3645
|
+
.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); }
|
|
3646
|
+
.status-flaky { background-color: rgba(0, 204, 211, 0.1); color: #00ccd3; border: 1px solid #00ccd3; }
|
|
3647
|
+
|
|
3222
3648
|
.browser-tag {
|
|
3649
|
+
font-size: 0.8em;
|
|
3650
|
+
font-weight: 600;
|
|
3651
|
+
background: var(--bg-secondary);
|
|
3652
|
+
color: var(--text-secondary);
|
|
3653
|
+
padding: 4px 10px;
|
|
3654
|
+
border-radius: 20px;
|
|
3655
|
+
border: 1px solid var(--border-light);
|
|
3656
|
+
display: inline-flex;
|
|
3657
|
+
align-items: center;
|
|
3658
|
+
gap: 6px;
|
|
3659
|
+
margin-bottom: 20px;
|
|
3660
|
+
align-self: flex-start;
|
|
3661
|
+
box-shadow: none;
|
|
3662
|
+
text-shadow: none;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
.suite-card-body {
|
|
3666
|
+
margin-top: auto;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
.test-count-label {
|
|
3223
3670
|
font-size: 0.85em;
|
|
3224
3671
|
font-weight: 600;
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
display:
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
transition: all 0.2s ease;
|
|
3672
|
+
color: var(--text-tertiary);
|
|
3673
|
+
text-transform: uppercase;
|
|
3674
|
+
letter-spacing: 0.05em;
|
|
3675
|
+
margin-bottom: 8px;
|
|
3676
|
+
display: block;
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
.suite-stats {
|
|
3680
|
+
display: flex;
|
|
3681
|
+
gap: 8px;
|
|
3682
|
+
background: var(--bg-secondary);
|
|
3683
|
+
padding: 10px 14px;
|
|
3684
|
+
border-radius: 10px;
|
|
3685
|
+
justify-content: space-between;
|
|
3240
3686
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
.
|
|
3250
|
-
.
|
|
3251
|
-
.
|
|
3687
|
+
|
|
3688
|
+
.stat-pill {
|
|
3689
|
+
display: flex;
|
|
3690
|
+
align-items: center;
|
|
3691
|
+
gap: 6px;
|
|
3692
|
+
font-size: 0.9em;
|
|
3693
|
+
font-weight: 600;
|
|
3694
|
+
}
|
|
3695
|
+
.stat-pill svg { width: 14px; height: 14px; }
|
|
3696
|
+
.stat-pill.passed { color: var(--success-dark); }
|
|
3697
|
+
.stat-pill.failed { color: var(--danger-dark); }
|
|
3698
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3699
|
+
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3252
3700
|
.filters {
|
|
3253
3701
|
display: flex;
|
|
3254
3702
|
flex-wrap: wrap;
|
|
@@ -3280,6 +3728,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3280
3728
|
min-width: 180px;
|
|
3281
3729
|
background: white;
|
|
3282
3730
|
cursor: pointer;
|
|
3731
|
+
width: 100%;
|
|
3283
3732
|
}
|
|
3284
3733
|
.filters select:focus {
|
|
3285
3734
|
outline: none;
|
|
@@ -3411,7 +3860,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3411
3860
|
border-radius: 0;
|
|
3412
3861
|
font-size: 0.7em;
|
|
3413
3862
|
font-weight: 800;
|
|
3414
|
-
color:
|
|
3863
|
+
color: black;
|
|
3415
3864
|
text-transform: uppercase;
|
|
3416
3865
|
min-width: 100px;
|
|
3417
3866
|
text-align: center;
|
|
@@ -3473,6 +3922,65 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3473
3922
|
border-color: rgba(148, 163, 184, 0.25);
|
|
3474
3923
|
}
|
|
3475
3924
|
|
|
3925
|
+
/* --- RETRY COUNT BADGE --- */
|
|
3926
|
+
.retry-badge {
|
|
3927
|
+
display: inline-flex;
|
|
3928
|
+
align-items: center;
|
|
3929
|
+
padding: 5px 12px;
|
|
3930
|
+
border-radius: 12px;
|
|
3931
|
+
font-size: 0.75rem;
|
|
3932
|
+
font-weight: 600;
|
|
3933
|
+
background: rgba(147, 51, 234, 0.15);
|
|
3934
|
+
color: #a855f7;
|
|
3935
|
+
border: 1px solid rgba(147, 51, 234, 0.3);
|
|
3936
|
+
margin-left: 8px;
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
/* --- RETRY TABS --- */
|
|
3940
|
+
.retry-tabs-container {
|
|
3941
|
+
margin-top: 16px;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
.retry-tabs-header {
|
|
3945
|
+
display: flex;
|
|
3946
|
+
gap: 8px;
|
|
3947
|
+
border-bottom: 2px solid var(--border-medium);
|
|
3948
|
+
margin-bottom: 20px;
|
|
3949
|
+
flex-wrap: wrap;
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
.retry-tab {
|
|
3953
|
+
padding: 10px 20px;
|
|
3954
|
+
background: transparent;
|
|
3955
|
+
border: none;
|
|
3956
|
+
border-bottom: 3px solid transparent;
|
|
3957
|
+
cursor: pointer;
|
|
3958
|
+
font-size: 0.95rem;
|
|
3959
|
+
font-weight: 600;
|
|
3960
|
+
color: var(--text-color-secondary);
|
|
3961
|
+
transition: all 0.2s ease;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.retry-tab:hover {
|
|
3965
|
+
color: var(--primary-color);
|
|
3966
|
+
background: rgba(147, 51, 234, 0.05);
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
.retry-tab.active {
|
|
3970
|
+
color: #a855f7;
|
|
3971
|
+
border-bottom-color: #a855f7;
|
|
3972
|
+
background: rgba(147, 51, 234, 0.1);
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
.retry-tab-content {
|
|
3976
|
+
animation: fadeIn 0.3s ease-in;
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
@keyframes fadeIn {
|
|
3980
|
+
from { opacity: 0; }
|
|
3981
|
+
to { opacity: 1; }
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3476
3984
|
.tag {
|
|
3477
3985
|
display: inline-flex;
|
|
3478
3986
|
align-items: center;
|
|
@@ -3503,7 +4011,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3503
4011
|
}
|
|
3504
4012
|
.test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
|
|
3505
4013
|
.test-case-content p { margin-bottom: 10px; font-size: 1em; }
|
|
3506
|
-
.test-error-summary {
|
|
4014
|
+
.test-error-summary {
|
|
4015
|
+
margin-bottom: 20px;
|
|
4016
|
+
padding: 14px;
|
|
4017
|
+
background-color: rgba(244,67,54,0.05);
|
|
4018
|
+
border: 1px solid rgba(244,67,54,0.2);
|
|
4019
|
+
border-left: 4px solid var(--danger-color);
|
|
4020
|
+
border-radius: 4px;
|
|
4021
|
+
display: flex;
|
|
4022
|
+
flex-direction: column;
|
|
4023
|
+
}
|
|
3507
4024
|
.test-error-summary h4 { color: var(--danger-color); margin-top:0;}
|
|
3508
4025
|
.test-error-summary pre { white-space: pre-wrap; word-break: break-all; color: var(--danger-color); font-size: 0.95em;}
|
|
3509
4026
|
.steps-list { margin: 18px 0; }
|
|
@@ -3515,10 +4032,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3515
4032
|
.step-duration { color: var(--dark-gray-color); font-size: 0.9em; }
|
|
3516
4033
|
.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
4034
|
.step-info { margin-bottom: 8px; }
|
|
4035
|
+
.code-snippet-section { margin: 12px 0; }
|
|
4036
|
+
.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
4037
|
.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
4038
|
.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
4039
|
.step-hook { background-color: rgba(33,150,243,0.04); border-left: 3px solid var(--info-color) !important; }
|
|
3521
4040
|
.step-hook .step-title { font-style: italic; color: var(--info-color)}
|
|
4041
|
+
.failed-step-highlight { border-left: 4px solid var(--danger-color) !important; background-color: rgba(244,67,54,0.03); }
|
|
4042
|
+
.failed-step-highlight .step-header { background-color: rgba(244,67,54,0.05); border-color: rgba(244,67,54,0.3); }
|
|
4043
|
+
.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
4044
|
.nested-steps { margin-top: 12px; }
|
|
3523
4045
|
.attachments-section { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--light-gray-color); }
|
|
3524
4046
|
.attachments-section h4 { margin-top: 0; margin-bottom: 20px; font-size: 1.1em; color: var(--text-color); }
|
|
@@ -3651,6 +4173,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3651
4173
|
color: var(--text-color);
|
|
3652
4174
|
pointer-events: auto;
|
|
3653
4175
|
cursor: pointer;
|
|
4176
|
+
width: 100%;
|
|
3654
4177
|
}
|
|
3655
4178
|
.filters button.clear-filters-btn:active,
|
|
3656
4179
|
.filters button.clear-filters-btn:focus {
|
|
@@ -4046,31 +4569,33 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4046
4569
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
4047
4570
|
runSummary.skipped || 0
|
|
4048
4571
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
4049
|
-
<div class="summary-card"><h3>
|
|
4050
|
-
<div class="
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4572
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
4573
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
4574
|
+
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
4575
|
+
runSummary.duration,
|
|
4576
|
+
)}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
|
|
4577
|
+
<div class="summary-card">
|
|
4578
|
+
<h3>Total Retry Count</h3>
|
|
4579
|
+
<div class="value">${totalRetried}</div>
|
|
4580
|
+
<div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
|
|
4581
|
+
</div>
|
|
4057
4582
|
<div class="summary-card">
|
|
4058
4583
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
4059
4584
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
4060
4585
|
${browserBreakdown
|
|
4061
|
-
.slice(0,
|
|
4586
|
+
.slice(0, 3)
|
|
4062
4587
|
.map(
|
|
4063
4588
|
(b) =>
|
|
4064
4589
|
`<div class="browser-item">
|
|
4065
|
-
<span class="browser-name">${sanitizeHTML(b.browser)}</span>
|
|
4590
|
+
<span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
|
|
4066
4591
|
<span class="browser-stats">${b.percentage}% (${b.count})</span>
|
|
4067
4592
|
</div>`,
|
|
4068
4593
|
)
|
|
4069
4594
|
.join("")}
|
|
4070
4595
|
${
|
|
4071
|
-
browserBreakdown.length >
|
|
4596
|
+
browserBreakdown.length > 3
|
|
4072
4597
|
? `<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 -
|
|
4598
|
+
<span>+${browserBreakdown.length - 3} more browsers</span>
|
|
4074
4599
|
</div>`
|
|
4075
4600
|
: ""
|
|
4076
4601
|
}
|
|
@@ -4083,17 +4608,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4083
4608
|
[
|
|
4084
4609
|
{ label: "Passed", value: runSummary.passed },
|
|
4085
4610
|
{ label: "Failed", value: runSummary.failed },
|
|
4611
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
4086
4612
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
4087
4613
|
],
|
|
4088
4614
|
400,
|
|
4089
4615
|
390,
|
|
4090
4616
|
)}
|
|
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
|
-
}
|
|
4617
|
+
${generateEnvironmentSection(runSummary.environment)}
|
|
4097
4618
|
</div>
|
|
4098
4619
|
|
|
4099
4620
|
<div class="dashboard-column">
|
|
@@ -4105,7 +4626,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4105
4626
|
<div id="test-runs" class="tab-content">
|
|
4106
4627
|
<div class="filters">
|
|
4107
4628
|
<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>
|
|
4629
|
+
<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
4630
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
4110
4631
|
new Set(
|
|
4111
4632
|
(results || []).map((test) => test.browser || "unknown"),
|
|
@@ -4202,6 +4723,33 @@ function generateHTML(reportData, trendData = null) {
|
|
|
4202
4723
|
});
|
|
4203
4724
|
}
|
|
4204
4725
|
|
|
4726
|
+
// --- Retry Tab Switching Function ---
|
|
4727
|
+
function switchRetryTab(event, tabId) {
|
|
4728
|
+
const tabButton = event.currentTarget;
|
|
4729
|
+
const tabsContainer = tabButton.closest('.retry-tabs-container');
|
|
4730
|
+
|
|
4731
|
+
// Hide all tab contents in this container
|
|
4732
|
+
const allTabContents = tabsContainer.querySelectorAll('.retry-tab-content');
|
|
4733
|
+
allTabContents.forEach(content => {
|
|
4734
|
+
content.style.display = 'none';
|
|
4735
|
+
content.classList.remove('active');
|
|
4736
|
+
});
|
|
4737
|
+
|
|
4738
|
+
// Remove active class from all tabs
|
|
4739
|
+
const allTabs = tabsContainer.querySelectorAll('.retry-tab');
|
|
4740
|
+
allTabs.forEach(tab => tab.classList.remove('active'));
|
|
4741
|
+
|
|
4742
|
+
// Show selected tab content
|
|
4743
|
+
const selectedContent = document.getElementById(tabId);
|
|
4744
|
+
if (selectedContent) {
|
|
4745
|
+
selectedContent.style.display = 'block';
|
|
4746
|
+
selectedContent.classList.add('active');
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
// Add active class to clicked tab
|
|
4750
|
+
tabButton.classList.add('active');
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4205
4753
|
// --- AI Failure Analyzer Functions ---
|
|
4206
4754
|
function getAIFix(button) {
|
|
4207
4755
|
const failureItem = button.closest('.compact-failure-item');
|
|
@@ -4669,6 +5217,8 @@ async function runScript(scriptPath, args = []) {
|
|
|
4669
5217
|
});
|
|
4670
5218
|
}
|
|
4671
5219
|
async function main() {
|
|
5220
|
+
await animate();
|
|
5221
|
+
|
|
4672
5222
|
const __filename = fileURLToPath(import.meta.url);
|
|
4673
5223
|
const __dirname = path.dirname(__filename);
|
|
4674
5224
|
|
|
@@ -4834,6 +5384,7 @@ async function main() {
|
|
|
4834
5384
|
passed: histRunReport.run.passed,
|
|
4835
5385
|
failed: histRunReport.run.failed,
|
|
4836
5386
|
skipped: histRunReport.run.skipped || 0,
|
|
5387
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
4837
5388
|
});
|
|
4838
5389
|
|
|
4839
5390
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|
|
@@ -4842,7 +5393,7 @@ async function main() {
|
|
|
4842
5393
|
(test) => ({
|
|
4843
5394
|
testName: test.name,
|
|
4844
5395
|
duration: test.duration,
|
|
4845
|
-
status: test.status,
|
|
5396
|
+
status: test.final_status || test.status,
|
|
4846
5397
|
timestamp: new Date(test.startTime),
|
|
4847
5398
|
}),
|
|
4848
5399
|
);
|
|
@@ -4860,7 +5411,7 @@ async function main() {
|
|
|
4860
5411
|
await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
|
|
4861
5412
|
console.log(
|
|
4862
5413
|
chalk.green.bold(
|
|
4863
|
-
|
|
5414
|
+
`Pulse report generated successfully at: ${reportHtmlPath}`,
|
|
4864
5415
|
),
|
|
4865
5416
|
);
|
|
4866
5417
|
console.log(chalk.gray(`(You can open this file in your browser)`));
|