@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.
- package/README.md +95 -85
- package/dist/lib/report-types.d.ts +2 -0
- package/dist/reporter/playwright-pulse-reporter.d.ts +18 -1
- package/dist/reporter/playwright-pulse-reporter.js +157 -129
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +53 -9
- package/dist/utils/compression-utils.d.ts +19 -0
- package/dist/utils/compression-utils.js +112 -0
- package/package.json +18 -9
- package/scripts/config-reader.mjs +114 -124
- package/scripts/generate-email-report.mjs +96 -21
- package/scripts/generate-report.mjs +1172 -531
- package/scripts/generate-static-report.mjs +1269 -540
- package/scripts/generate-trend.mjs +8 -4
- package/scripts/{merge-pulse-report.js → merge-pulse-report.mjs} +64 -35
- package/scripts/merge-sequential-reports.mjs +172 -0
- package/scripts/sendReport.mjs +156 -202
- 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
|
@@ -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 {
|
|
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
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
923
|
+
.env-card-header {
|
|
924
|
+
display: flex;
|
|
925
|
+
flex-direction: column;
|
|
926
|
+
padding: 24px 24px 12px;
|
|
734
927
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
padding: 24px;
|
|
738
|
-
}
|
|
928
|
+
.env-modern-card.no-header .env-card-header {
|
|
929
|
+
display: none;
|
|
739
930
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
grid-column: 1 / -1;
|
|
743
|
-
margin-bottom: 24px;
|
|
931
|
+
.env-modern-card.no-header {
|
|
932
|
+
margin-top: 0;
|
|
744
933
|
}
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
944
|
+
align-items: center;
|
|
945
|
+
font-size: 16px;
|
|
946
|
+
font-weight: 600;
|
|
947
|
+
color: #0f172a;
|
|
948
|
+
transition: color 0.3s;
|
|
771
949
|
}
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
785
|
-
text-transform: uppercase;
|
|
786
|
-
letter-spacing: 0.5px;
|
|
972
|
+
justify-content: center;
|
|
787
973
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
fill:
|
|
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
|
-
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
padding:
|
|
832
|
-
border-radius:
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
840
|
-
background-color: #ede9fe;
|
|
841
|
-
color: #6366f1;
|
|
1002
|
+
.env-item:hover {
|
|
1003
|
+
background-color: rgba(100, 116, 139, 0.05);
|
|
842
1004
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
background-color: #d1fae5;
|
|
846
|
-
color: #10b981;
|
|
1005
|
+
.env-item-icon {
|
|
1006
|
+
flex-shrink: 0;
|
|
847
1007
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1008
|
+
.env-item-icon svg {
|
|
1009
|
+
width: 16px;
|
|
1010
|
+
height: 16px;
|
|
1011
|
+
stroke: #6366f1;
|
|
1012
|
+
fill: none;
|
|
852
1013
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
align-items: center;
|
|
857
|
-
gap: 6px;
|
|
1014
|
+
.env-item-content {
|
|
1015
|
+
flex-grow: 1;
|
|
1016
|
+
min-width: 0;
|
|
858
1017
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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-
|
|
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>
|
|
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-
|
|
902
|
-
<
|
|
903
|
-
|
|
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-
|
|
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>
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
948
|
-
|
|
949
|
-
<
|
|
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
|
-
|
|
952
|
-
|
|
953
|
-
<
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
990
|
-
|
|
991
|
-
<
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
<
|
|
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.
|
|
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: #
|
|
1092
|
-
color: var(--
|
|
1093
|
-
|
|
1094
|
-
width: 80%; max-width: 700px; border-radius:
|
|
1095
|
-
position: relative; box-shadow: 0 5px
|
|
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:
|
|
1291
|
+
top: 20px;
|
|
1100
1292
|
right: 25px;
|
|
1101
|
-
font-size:
|
|
1102
|
-
font-weight:
|
|
1293
|
+
font-size: 28px;
|
|
1294
|
+
font-weight: 400;
|
|
1103
1295
|
cursor: pointer;
|
|
1104
1296
|
line-height: 1;
|
|
1105
1297
|
z-index: 10;
|
|
1106
|
-
color: #
|
|
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.
|
|
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: #
|
|
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
|
-
|
|
1368
|
+
// Updated escaping logic
|
|
1369
|
+
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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-
|
|
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;
|
|
1262
|
-
|
|
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
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
-
|
|
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 === "
|
|
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
|
-
//
|
|
1690
|
+
// Uses CSS classes for responsiveness instead of inline styles
|
|
1474
1691
|
return `
|
|
1475
|
-
<div class="suites-widget
|
|
1476
|
-
<div class="suites-header"
|
|
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"
|
|
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
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
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.
|
|
2078
|
-
|
|
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
|
-
//
|
|
2088
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
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
|
-
${
|
|
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]
|
|
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 <
|
|
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
|
-
|
|
2549
|
+
testData.workerId,
|
|
2267
2550
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2268
|
-
|
|
2551
|
+
testData.totalWorkers,
|
|
2269
2552
|
)}]</p>
|
|
2270
2553
|
${
|
|
2271
|
-
|
|
2272
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2273
|
-
|
|
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:
|
|
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
|
-
|
|
2583
|
+
testData.snippet
|
|
2299
2584
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2300
|
-
|
|
2585
|
+
testData.snippet,
|
|
2301
2586
|
)}</code></pre></div>`
|
|
2302
2587
|
: ""
|
|
2303
2588
|
}
|
|
2304
2589
|
<h4>Steps</h4>
|
|
2305
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2590
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2306
2591
|
${(() => {
|
|
2307
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2609
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2327
2610
|
)}</pre></div>`
|
|
2328
2611
|
: ""
|
|
2329
2612
|
}
|
|
2330
2613
|
${
|
|
2331
|
-
|
|
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
|
-
${
|
|
2619
|
+
${testData.screenshots
|
|
2337
2620
|
.map(
|
|
2338
|
-
(screenshot,
|
|
2621
|
+
(screenshot, screenshotIndex) => `
|
|
2339
2622
|
<div class="attachment-item">
|
|
2340
2623
|
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
2341
|
-
|
|
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()}-${
|
|
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
|
-
|
|
2364
|
-
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${
|
|
2365
|
-
.map((videoUrl,
|
|
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
|
-
|
|
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()}-${
|
|
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
|
-
|
|
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(
|
|
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(
|
|
2699
|
+
fixPath(testData.tracePath),
|
|
2417
2700
|
)}" target="_blank" download="${sanitizeHTML(
|
|
2418
|
-
path.basename(
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
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
|
|
2489
|
-
<link rel="apple-touch-icon" href
|
|
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:
|
|
2948
|
+
background: transparent;
|
|
2600
2949
|
border-radius: 12px;
|
|
2601
|
-
|
|
2602
|
-
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
2989
|
+
gap: 8px;
|
|
2636
2990
|
font-size: 0.7em;
|
|
2637
2991
|
text-transform: uppercase;
|
|
2638
|
-
letter-spacing:
|
|
2639
|
-
color: #
|
|
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:
|
|
2646
|
-
height:
|
|
2999
|
+
width: 10px;
|
|
3000
|
+
height: 10px;
|
|
2647
3001
|
border-radius: 50%;
|
|
2648
3002
|
background: currentColor;
|
|
2649
|
-
opacity: 0.
|
|
3003
|
+
opacity: 0.7;
|
|
3004
|
+
box-shadow: 0 0 8px currentColor;
|
|
2650
3005
|
}
|
|
2651
3006
|
.run-info-item:first-child strong {
|
|
2652
|
-
color:
|
|
3007
|
+
color: var(--warning-light);
|
|
2653
3008
|
}
|
|
2654
3009
|
.run-info-item:last-child strong {
|
|
2655
|
-
color:
|
|
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
|
-
|
|
2817
|
-
|
|
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-
|
|
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(--
|
|
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 {
|
|
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
|
-
.
|
|
3204
|
-
|
|
3205
|
-
|
|
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
|
-
.
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
display:
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
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
|
-
.
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
}
|
|
3245
|
-
.
|
|
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:
|
|
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 {
|
|
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
|
|
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>
|
|
4050
|
-
<div class="
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
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,
|
|
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 >
|
|
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 -
|
|
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
|
|
4691
|
-
const
|
|
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
|
-
|
|
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)`));
|