@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
|
/**
|
|
11
40
|
* Dynamically imports the 'chalk' library for terminal string styling.
|
|
@@ -27,18 +56,8 @@ try {
|
|
|
27
56
|
};
|
|
28
57
|
}
|
|
29
58
|
|
|
30
|
-
/**
|
|
31
|
-
* @constant {string} DEFAULT_OUTPUT_DIR
|
|
32
|
-
* The default directory where the report will be generated.
|
|
33
|
-
*/
|
|
34
59
|
const DEFAULT_OUTPUT_DIR = "pulse-report";
|
|
35
60
|
|
|
36
|
-
/**
|
|
37
|
-
* @constant {string} DEFAULT_JSON_FILE
|
|
38
|
-
* The default name for the JSON file containing the test data.
|
|
39
|
-
*/
|
|
40
|
-
const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
|
|
41
|
-
|
|
42
61
|
/**
|
|
43
62
|
* @constant {string} DEFAULT_HTML_FILE
|
|
44
63
|
* The default name for the generated HTML report file.
|
|
@@ -338,6 +357,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
338
357
|
color: "var(--warning-color)",
|
|
339
358
|
marker: { symbol: "circle" },
|
|
340
359
|
},
|
|
360
|
+
{
|
|
361
|
+
name: "Flaky",
|
|
362
|
+
data: runs.map((r) => r.flaky || 0),
|
|
363
|
+
color: "#00ccd3",
|
|
364
|
+
marker: { symbol: "circle" },
|
|
365
|
+
},
|
|
341
366
|
];
|
|
342
367
|
const runsForTooltip = runs.map((r) => ({
|
|
343
368
|
runId: r.runId,
|
|
@@ -541,6 +566,9 @@ function generateTestHistoryChart(history) {
|
|
|
541
566
|
case "skipped":
|
|
542
567
|
color = "var(--warning-color)";
|
|
543
568
|
break;
|
|
569
|
+
case "flaky":
|
|
570
|
+
color = "var(--neutral-500)";
|
|
571
|
+
break;
|
|
544
572
|
default:
|
|
545
573
|
color = "var(--dark-gray-color)";
|
|
546
574
|
}
|
|
@@ -657,6 +685,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
657
685
|
case "Failed":
|
|
658
686
|
color = "var(--danger-color)";
|
|
659
687
|
break;
|
|
688
|
+
case "Flaky":
|
|
689
|
+
color = "#00ccd3";
|
|
690
|
+
break;
|
|
660
691
|
case "Skipped":
|
|
661
692
|
color = "var(--warning-color)";
|
|
662
693
|
break;
|
|
@@ -764,62 +795,264 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
764
795
|
* @param {number} [dashboardHeight=600] The height of the dashboard.
|
|
765
796
|
* @returns {string} The HTML string for the environment dashboard.
|
|
766
797
|
*/
|
|
767
|
-
function
|
|
768
|
-
|
|
769
|
-
|
|
798
|
+
function generateEnvironmentSection(environmentData) {
|
|
799
|
+
if (!environmentData) {
|
|
800
|
+
return '<div class="no-data">Environment data not available.</div>';
|
|
801
|
+
}
|
|
770
802
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
803
|
+
if (Array.isArray(environmentData)) {
|
|
804
|
+
return `
|
|
805
|
+
<div class="sharded-env-section">
|
|
806
|
+
<div class="sharded-env-header">
|
|
807
|
+
<div class="sharded-env-title-row">
|
|
808
|
+
<div>
|
|
809
|
+
<div class="sharded-env-title">
|
|
810
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
811
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
812
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
813
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
814
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
815
|
+
</svg>
|
|
816
|
+
System Information
|
|
817
|
+
</div>
|
|
818
|
+
<div class="sharded-env-subtitle">Test execution environment details - ${environmentData.length} shard${environmentData.length > 1 ? "s" : ""}</div>
|
|
819
|
+
</div>
|
|
820
|
+
<div class="env-icon-badge">
|
|
821
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
822
|
+
<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>
|
|
823
|
+
</svg>
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
<div class="sharded-environments-container">
|
|
828
|
+
<div class="sharded-environments-wrapper">
|
|
829
|
+
${environmentData
|
|
830
|
+
.map(
|
|
831
|
+
(env, index) => `
|
|
832
|
+
<div class="env-card-wrapper">
|
|
833
|
+
<div class="env-card-badge">Shard ${index + 1}</div>
|
|
834
|
+
${generateEnvironmentDashboard(env, true)}
|
|
835
|
+
</div>
|
|
836
|
+
`,
|
|
837
|
+
)
|
|
838
|
+
.join("")}
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
<style>
|
|
843
|
+
.sharded-env-section {
|
|
844
|
+
border: 1px solid var(--border-light);
|
|
845
|
+
border-radius: 12px;
|
|
846
|
+
background: var(--bg-secondary);
|
|
847
|
+
overflow: hidden;
|
|
848
|
+
}
|
|
849
|
+
.sharded-env-header {
|
|
850
|
+
position: sticky;
|
|
851
|
+
top: 0;
|
|
852
|
+
z-index: 20;
|
|
853
|
+
background: linear-gradient(to bottom right, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
|
854
|
+
border-bottom: 1px solid var(--border-light);
|
|
855
|
+
padding: 24px 24px 16px;
|
|
856
|
+
}
|
|
857
|
+
.sharded-env-title-row {
|
|
858
|
+
display: flex;
|
|
859
|
+
justify-content: space-between;
|
|
860
|
+
align-items: center;
|
|
861
|
+
}
|
|
862
|
+
.sharded-env-title {
|
|
863
|
+
display: flex;
|
|
864
|
+
align-items: center;
|
|
865
|
+
font-size: 18px;
|
|
866
|
+
font-weight: 600;
|
|
867
|
+
color: var(--text-primary);
|
|
868
|
+
}
|
|
869
|
+
.sharded-env-title svg {
|
|
870
|
+
width: 18px;
|
|
871
|
+
height: 18px;
|
|
872
|
+
margin-right: 8px;
|
|
873
|
+
stroke: currentColor;
|
|
874
|
+
fill: none;
|
|
875
|
+
}
|
|
876
|
+
.sharded-env-subtitle {
|
|
877
|
+
font-size: 13px;
|
|
878
|
+
color: var(--text-secondary);
|
|
879
|
+
margin-top: 4px;
|
|
880
|
+
}
|
|
881
|
+
.sharded-environments-container {
|
|
882
|
+
max-height: 520px;
|
|
883
|
+
overflow-y: auto;
|
|
884
|
+
overflow-x: hidden;
|
|
885
|
+
padding: 16px;
|
|
886
|
+
}
|
|
887
|
+
.sharded-environments-container::-webkit-scrollbar {
|
|
888
|
+
width: 8px;
|
|
889
|
+
}
|
|
890
|
+
.sharded-environments-container::-webkit-scrollbar-track {
|
|
891
|
+
background: var(--bg-tertiary);
|
|
892
|
+
border-radius: 4px;
|
|
893
|
+
}
|
|
894
|
+
.sharded-environments-container::-webkit-scrollbar-thumb {
|
|
895
|
+
background: var(--border-medium);
|
|
896
|
+
border-radius: 4px;
|
|
897
|
+
}
|
|
898
|
+
.sharded-environments-container::-webkit-scrollbar-thumb:hover {
|
|
899
|
+
background: var(--border-color);
|
|
900
|
+
}
|
|
901
|
+
.sharded-environments-wrapper {
|
|
902
|
+
display: grid;
|
|
903
|
+
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
|
904
|
+
gap: 24px;
|
|
905
|
+
}
|
|
906
|
+
@media (max-width: 768px) {
|
|
907
|
+
.sharded-environments-wrapper {
|
|
908
|
+
grid-template-columns: 1fr;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
.env-card-wrapper {
|
|
912
|
+
position: relative;
|
|
913
|
+
}
|
|
914
|
+
.env-card-badge {
|
|
915
|
+
position: absolute;
|
|
916
|
+
top: -10px;
|
|
917
|
+
right: 16px;
|
|
918
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
919
|
+
color: white;
|
|
920
|
+
padding: 6px 14px;
|
|
921
|
+
border-radius: 20px;
|
|
922
|
+
font-size: 0.75em;
|
|
923
|
+
font-weight: 700;
|
|
924
|
+
text-transform: uppercase;
|
|
925
|
+
letter-spacing: 0.5px;
|
|
926
|
+
z-index: 10;
|
|
927
|
+
box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
|
|
928
|
+
}
|
|
929
|
+
</style>
|
|
930
|
+
`;
|
|
931
|
+
}
|
|
775
932
|
|
|
776
|
-
|
|
777
|
-
|
|
933
|
+
return generateEnvironmentDashboard(environmentData);
|
|
934
|
+
}
|
|
778
935
|
|
|
779
|
-
|
|
780
|
-
const
|
|
936
|
+
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
937
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
938
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
939
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
940
|
+
const cwdInfo = environment.cwd || "N/A";
|
|
781
941
|
|
|
782
942
|
return `
|
|
783
|
-
<div class="
|
|
943
|
+
<div class="env-modern-card${hideHeader ? " no-header" : ""}">
|
|
784
944
|
<style>
|
|
785
|
-
.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
--primary-color: var(--primary-dark, #6366f1);
|
|
793
|
-
--success-color: var(--success-dark, #10b981);
|
|
794
|
-
--warning-color: var(--warning-dark, #f59e0b);
|
|
795
|
-
|
|
796
|
-
background-color: var(--bg-tertiary);
|
|
797
|
-
padding: 48px;
|
|
798
|
-
border-bottom: 1px solid var(--border-light);
|
|
945
|
+
.env-modern-card {
|
|
946
|
+
background: linear-gradient(to bottom right, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
|
947
|
+
border: 0;
|
|
948
|
+
border-radius: 12px;
|
|
949
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
950
|
+
margin-top: 24px;
|
|
951
|
+
transition: all 0.3s ease;
|
|
799
952
|
font-family: var(--font-family);
|
|
953
|
+
overflow: hidden;
|
|
954
|
+
}
|
|
955
|
+
.env-modern-card:hover {
|
|
956
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
957
|
+
}
|
|
958
|
+
.env-modern-card {
|
|
959
|
+
margin-bottom: 0;
|
|
960
|
+
}
|
|
961
|
+
.env-card-header {
|
|
962
|
+
display: flex;
|
|
963
|
+
flex-direction: column;
|
|
964
|
+
padding: 24px 24px 12px;
|
|
965
|
+
}
|
|
966
|
+
.env-modern-card.no-header .env-card-header {
|
|
967
|
+
display: none;
|
|
968
|
+
}
|
|
969
|
+
.env-modern-card.no-header {
|
|
970
|
+
margin-top: 0;
|
|
971
|
+
}
|
|
972
|
+
.env-modern-card.no-header .env-card-content {
|
|
973
|
+
padding-top: 24px;
|
|
974
|
+
}
|
|
975
|
+
.env-card-title-row {
|
|
976
|
+
display: flex;
|
|
977
|
+
justify-content: space-between;
|
|
978
|
+
align-items: center;
|
|
979
|
+
}
|
|
980
|
+
.env-card-title {
|
|
981
|
+
display: flex;
|
|
982
|
+
align-items: center;
|
|
983
|
+
font-size: 16px;
|
|
984
|
+
font-weight: 600;
|
|
800
985
|
color: var(--text-primary);
|
|
986
|
+
transition: color 0.3s;
|
|
987
|
+
}
|
|
988
|
+
.env-modern-card:hover .env-card-title {
|
|
989
|
+
color: var(--primary-dark, #6366f1);
|
|
990
|
+
}
|
|
991
|
+
.env-card-title svg {
|
|
992
|
+
width: 16px;
|
|
993
|
+
height: 16px;
|
|
994
|
+
margin-right: 8px;
|
|
995
|
+
stroke: currentColor;
|
|
996
|
+
fill: none;
|
|
997
|
+
}
|
|
998
|
+
.env-card-subtitle {
|
|
999
|
+
font-size: 12px;
|
|
1000
|
+
color: var(--text-secondary);
|
|
1001
|
+
margin-top: 4px;
|
|
1002
|
+
}
|
|
1003
|
+
.env-card-content {
|
|
1004
|
+
padding: 0 24px 24px;
|
|
1005
|
+
}
|
|
1006
|
+
.env-items-grid {
|
|
801
1007
|
display: grid;
|
|
802
1008
|
grid-template-columns: repeat(2, 1fr);
|
|
803
|
-
gap:
|
|
804
|
-
font-size: 15px;
|
|
805
|
-
transform: translateZ(0);
|
|
1009
|
+
gap: 10px;
|
|
806
1010
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
padding: 32px 24px;
|
|
812
|
-
}
|
|
1011
|
+
@media (min-width: 768px) {
|
|
1012
|
+
.env-items-grid {
|
|
1013
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1014
|
+
}
|
|
813
1015
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1016
|
+
.env-item {
|
|
1017
|
+
display: flex;
|
|
1018
|
+
align-items: flex-start;
|
|
1019
|
+
gap: 8px;
|
|
1020
|
+
padding: 8px;
|
|
1021
|
+
border-radius: 8px;
|
|
1022
|
+
transition: background-color 0.2s;
|
|
1023
|
+
min-height: 48px;
|
|
818
1024
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1025
|
+
.env-item:hover {
|
|
1026
|
+
background-color: var(--bg-hover);
|
|
1027
|
+
}
|
|
1028
|
+
.env-item-icon {
|
|
1029
|
+
flex-shrink: 0;
|
|
1030
|
+
}
|
|
1031
|
+
.env-item-icon svg {
|
|
1032
|
+
width: 16px;
|
|
1033
|
+
height: 16px;
|
|
1034
|
+
stroke: var(--primary-dark, #6366f1);
|
|
1035
|
+
fill: none;
|
|
1036
|
+
}
|
|
1037
|
+
.env-item-content {
|
|
1038
|
+
flex-grow: 1;
|
|
1039
|
+
min-width: 0;
|
|
1040
|
+
}
|
|
1041
|
+
.env-item-label {
|
|
1042
|
+
font-size: 12px;
|
|
1043
|
+
font-weight: 500;
|
|
1044
|
+
color: var(--text-secondary);
|
|
1045
|
+
white-space: nowrap;
|
|
1046
|
+
overflow: hidden;
|
|
1047
|
+
text-overflow: ellipsis;
|
|
1048
|
+
}
|
|
1049
|
+
.env-item-value {
|
|
1050
|
+
font-size: 12px;
|
|
1051
|
+
font-weight: 600;
|
|
1052
|
+
color: var(--text-primary);
|
|
1053
|
+
word-wrap: break-word;
|
|
1054
|
+
overflow-wrap: break-word;
|
|
1055
|
+
line-height: 1.4;
|
|
823
1056
|
}
|
|
824
1057
|
|
|
825
1058
|
.env-dashboard-title {
|
|
@@ -951,133 +1184,151 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
951
1184
|
}
|
|
952
1185
|
</style>
|
|
953
1186
|
|
|
954
|
-
<div class="env-
|
|
955
|
-
<div>
|
|
956
|
-
<
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
<div class="env-card-content">
|
|
968
|
-
<div class="env-detail-row">
|
|
969
|
-
<span class="env-detail-label">CPU Model</span>
|
|
970
|
-
<span class="env-detail-value">${environment.cpu.model}</span>
|
|
971
|
-
</div>
|
|
972
|
-
<div class="env-detail-row">
|
|
973
|
-
<span class="env-detail-label">CPU Cores</span>
|
|
974
|
-
<span class="env-detail-value">
|
|
975
|
-
<div class="env-cpu-cores">
|
|
976
|
-
<span>${environment.cpu.cores || "N/A"} core${environment.cpu.cores !== 1 ? "s" : ""}</span>
|
|
977
|
-
</div>
|
|
978
|
-
</span>
|
|
979
|
-
</div>
|
|
980
|
-
<div class="env-detail-row">
|
|
981
|
-
<span class="env-detail-label">Memory</span>
|
|
982
|
-
<span class="env-detail-value">${formattedMemory}</span>
|
|
1187
|
+
<div class="env-card-header">
|
|
1188
|
+
<div class="env-card-title-row">
|
|
1189
|
+
<div>
|
|
1190
|
+
<div class="env-card-title">
|
|
1191
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1192
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
1193
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
1194
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
1195
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
1196
|
+
</svg>
|
|
1197
|
+
System Information
|
|
1198
|
+
</div>
|
|
1199
|
+
<div class="env-card-subtitle">Test execution environment details</div>
|
|
983
1200
|
</div>
|
|
984
1201
|
</div>
|
|
985
1202
|
</div>
|
|
986
1203
|
|
|
987
|
-
<div class="env-card">
|
|
988
|
-
<div class="env-
|
|
989
|
-
<
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
<
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}</span>
|
|
1000
|
-
</div>
|
|
1001
|
-
<div class="env-detail-row">
|
|
1002
|
-
<span class="env-detail-label">OS Version</span>
|
|
1003
|
-
<span class="env-detail-value">${
|
|
1004
|
-
environment.os.split(" ")[1] || "N/A"
|
|
1005
|
-
}</span>
|
|
1006
|
-
</div>
|
|
1007
|
-
<div class="env-detail-row">
|
|
1008
|
-
<span class="env-detail-label">Hostname</span>
|
|
1009
|
-
<span class="env-detail-value" title="${environment.host}">${
|
|
1010
|
-
environment.host
|
|
1011
|
-
}</span>
|
|
1204
|
+
<div class="env-card-content">
|
|
1205
|
+
<div class="env-items-grid">
|
|
1206
|
+
<div class="env-item">
|
|
1207
|
+
<div class="env-item-icon">
|
|
1208
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1209
|
+
<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>
|
|
1210
|
+
</svg>
|
|
1211
|
+
</div>
|
|
1212
|
+
<div class="env-item-content">
|
|
1213
|
+
<p class="env-item-label">Host</p>
|
|
1214
|
+
<div class="env-item-value" title="${environment.host}">${environment.host}</div>
|
|
1215
|
+
</div>
|
|
1012
1216
|
</div>
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
<span class="env-detail-value">${environment.node}</span>
|
|
1217
|
+
|
|
1218
|
+
<div class="env-item">
|
|
1219
|
+
<div class="env-item-icon">
|
|
1220
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1221
|
+
<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>
|
|
1222
|
+
</svg>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div class="env-item-content">
|
|
1225
|
+
<p class="env-item-label">Os</p>
|
|
1226
|
+
<div class="env-item-value" title="${environment.os}">${environment.os}</div>
|
|
1227
|
+
</div>
|
|
1025
1228
|
</div>
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
<
|
|
1229
|
+
|
|
1230
|
+
<div class="env-item">
|
|
1231
|
+
<div class="env-item-icon">
|
|
1232
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1233
|
+
<rect width="16" height="16" x="4" y="4" rx="2"></rect>
|
|
1234
|
+
<rect width="6" height="6" x="9" y="9" rx="1"></rect>
|
|
1235
|
+
<path d="M15 2v2"></path>
|
|
1236
|
+
<path d="M15 20v2"></path>
|
|
1237
|
+
<path d="M2 15h2"></path>
|
|
1238
|
+
<path d="M2 9h2"></path>
|
|
1239
|
+
<path d="M20 15h2"></path>
|
|
1240
|
+
<path d="M20 9h2"></path>
|
|
1241
|
+
<path d="M9 2v2"></path>
|
|
1242
|
+
<path d="M9 20v2"></path>
|
|
1243
|
+
</svg>
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="env-item-content">
|
|
1246
|
+
<p class="env-item-label">Cpu</p>
|
|
1247
|
+
<div class="env-item-value" title='${JSON.stringify(environment.cpu)}'>${cpuInfo}</div>
|
|
1248
|
+
</div>
|
|
1029
1249
|
</div>
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
<
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1250
|
+
|
|
1251
|
+
<div class="env-item">
|
|
1252
|
+
<div class="env-item-icon">
|
|
1253
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1254
|
+
<path d="M6 19v-3"></path>
|
|
1255
|
+
<path d="M10 19v-3"></path>
|
|
1256
|
+
<path d="M14 19v-3"></path>
|
|
1257
|
+
<path d="M18 19v-3"></path>
|
|
1258
|
+
<path d="M8 11V9"></path>
|
|
1259
|
+
<path d="M16 11V9"></path>
|
|
1260
|
+
<path d="M12 11V9"></path>
|
|
1261
|
+
<path d="M2 15h20"></path>
|
|
1262
|
+
<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>
|
|
1263
|
+
</svg>
|
|
1264
|
+
</div>
|
|
1265
|
+
<div class="env-item-content">
|
|
1266
|
+
<p class="env-item-label">Memory</p>
|
|
1267
|
+
<div class="env-item-value" title="${environment.memory}">${environment.memory}</div>
|
|
1268
|
+
</div>
|
|
1037
1269
|
</div>
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
? "ARM-based"
|
|
1063
|
-
: "x86/Other"
|
|
1064
|
-
}
|
|
1065
|
-
</span>
|
|
1066
|
-
</span>
|
|
1270
|
+
|
|
1271
|
+
<div class="env-item">
|
|
1272
|
+
<div class="env-item-icon">
|
|
1273
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1274
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1275
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1276
|
+
<path d="M12 2v2"></path>
|
|
1277
|
+
<path d="M12 22v-2"></path>
|
|
1278
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1279
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1280
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1281
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1282
|
+
<path d="M14 12h8"></path>
|
|
1283
|
+
<path d="M2 12h2"></path>
|
|
1284
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1285
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1286
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1287
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1288
|
+
</svg>
|
|
1289
|
+
</div>
|
|
1290
|
+
<div class="env-item-content">
|
|
1291
|
+
<p class="env-item-label">Node</p>
|
|
1292
|
+
<div class="env-item-value" title="${environment.node}">${environment.node}</div>
|
|
1293
|
+
</div>
|
|
1067
1294
|
</div>
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
<
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1295
|
+
|
|
1296
|
+
<div class="env-item">
|
|
1297
|
+
<div class="env-item-icon">
|
|
1298
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1299
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1300
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1301
|
+
<path d="M12 2v2"></path>
|
|
1302
|
+
<path d="M12 22v-2"></path>
|
|
1303
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1304
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1305
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1306
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1307
|
+
<path d="M14 12h8"></path>
|
|
1308
|
+
<path d="M2 12h2"></path>
|
|
1309
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1310
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1311
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1312
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1313
|
+
</svg>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div class="env-item-content">
|
|
1316
|
+
<p class="env-item-label">V8</p>
|
|
1317
|
+
<div class="env-item-value" title="${environment.v8}">${environment.v8}</div>
|
|
1318
|
+
</div>
|
|
1077
1319
|
</div>
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
<
|
|
1320
|
+
|
|
1321
|
+
<div class="env-item">
|
|
1322
|
+
<div class="env-item-icon">
|
|
1323
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1324
|
+
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
1325
|
+
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
1326
|
+
</svg>
|
|
1327
|
+
</div>
|
|
1328
|
+
<div class="env-item-content">
|
|
1329
|
+
<p class="env-item-label">Working Dir</p>
|
|
1330
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1331
|
+
</div>
|
|
1081
1332
|
</div>
|
|
1082
1333
|
</div>
|
|
1083
1334
|
</div>
|
|
@@ -1105,11 +1356,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1105
1356
|
const workerId =
|
|
1106
1357
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1107
1358
|
if (!acc[workerId]) {
|
|
1108
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1359
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1109
1360
|
}
|
|
1110
1361
|
|
|
1111
1362
|
const status = String(test.status).toLowerCase();
|
|
1112
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1363
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1113
1364
|
acc[workerId][status]++;
|
|
1114
1365
|
}
|
|
1115
1366
|
|
|
@@ -1154,6 +1405,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1154
1405
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1155
1406
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1156
1407
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1408
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1157
1409
|
|
|
1158
1410
|
const categoriesString = JSON.stringify(categories);
|
|
1159
1411
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
@@ -1161,6 +1413,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1161
1413
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1162
1414
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1163
1415
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1416
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1164
1417
|
]);
|
|
1165
1418
|
|
|
1166
1419
|
// The HTML now includes the chart container, the modal, and styles for the modal
|
|
@@ -1168,33 +1421,39 @@ function generateWorkerDistributionChart(results) {
|
|
|
1168
1421
|
<style>
|
|
1169
1422
|
.worker-modal-overlay {
|
|
1170
1423
|
position: fixed; z-index: 1050; left: 0; top: 0; width: 100%; height: 100%;
|
|
1171
|
-
overflow: auto; background-color: rgba(0,0,0,0.
|
|
1424
|
+
overflow: auto; background-color: rgba(0,0,0,0.85);
|
|
1172
1425
|
display: none; align-items: center; justify-content: center;
|
|
1426
|
+
backdrop-filter: blur(4px);
|
|
1427
|
+
-webkit-backdrop-filter: blur(4px);
|
|
1173
1428
|
}
|
|
1174
1429
|
.worker-modal-content {
|
|
1175
|
-
background-color: var(--bg-card);
|
|
1176
|
-
color: var(--
|
|
1177
|
-
|
|
1178
|
-
width: 80%; max-width: 700px; border-radius:
|
|
1179
|
-
position: relative; box-shadow:
|
|
1430
|
+
background-color: var(--bg-card, #ffffff);
|
|
1431
|
+
color: var(--text-color, #1f2937);
|
|
1432
|
+
padding: 30px; border: 1px solid var(--border-color, #e5e7eb);
|
|
1433
|
+
width: 80%; max-width: 700px; border-radius: 12px;
|
|
1434
|
+
position: relative; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
|
|
1435
|
+
flex-shrink: 0; margin: 20px;
|
|
1436
|
+
z-index: 1051;
|
|
1437
|
+
transform: translateZ(0); /* Force hardware acceleration/composite layer in safari */
|
|
1438
|
+
-webkit-transform: translateZ(0);
|
|
1180
1439
|
}
|
|
1181
1440
|
.worker-modal-close {
|
|
1182
1441
|
position: absolute;
|
|
1183
|
-
top:
|
|
1442
|
+
top: 20px;
|
|
1184
1443
|
right: 25px;
|
|
1185
|
-
font-size:
|
|
1186
|
-
font-weight:
|
|
1444
|
+
font-size: 28px;
|
|
1445
|
+
font-weight: 400;
|
|
1187
1446
|
cursor: pointer;
|
|
1188
1447
|
line-height: 1;
|
|
1189
1448
|
z-index: 10;
|
|
1190
|
-
color: #
|
|
1191
|
-
transition: color 0.2s ease;
|
|
1449
|
+
color: var(--text-color-secondary, #6b7280);
|
|
1450
|
+
transition: color 0.2s ease, transform 0.2s ease;
|
|
1192
1451
|
user-select: none;
|
|
1193
1452
|
-webkit-user-select: none;
|
|
1194
1453
|
}
|
|
1195
1454
|
.worker-modal-close:hover, .worker-modal-close:focus {
|
|
1196
|
-
color: var(--danger-color);
|
|
1197
|
-
transform: scale(1.
|
|
1455
|
+
color: var(--danger-color, #ef4444);
|
|
1456
|
+
transform: scale(1.15);
|
|
1198
1457
|
}
|
|
1199
1458
|
#worker-modal-body-${chartId} ul {
|
|
1200
1459
|
list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
|
|
@@ -1222,19 +1481,22 @@ function generateWorkerDistributionChart(results) {
|
|
|
1222
1481
|
<div id="worker-modal-${chartId}" class="worker-modal-overlay">
|
|
1223
1482
|
<div class="worker-modal-content">
|
|
1224
1483
|
<span class="worker-modal-close" onclick="window.${modalJsNamespace}.close()">×</span>
|
|
1225
|
-
<h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: #
|
|
1484
|
+
<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>
|
|
1226
1485
|
<div id="worker-modal-body-${chartId}"></div>
|
|
1227
1486
|
</div>
|
|
1228
1487
|
</div>
|
|
1229
1488
|
|
|
1230
1489
|
<script>
|
|
1231
1490
|
// Namespace for modal functions to avoid global scope pollution
|
|
1232
|
-
window.${modalJsNamespace} = {};
|
|
1491
|
+
if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
|
|
1233
1492
|
|
|
1234
1493
|
window.${renderFunctionName} = function() {
|
|
1235
1494
|
const chartContainer = document.getElementById('${chartId}');
|
|
1236
1495
|
if (!chartContainer) { console.error("Chart container ${chartId} not found."); return; }
|
|
1237
1496
|
|
|
1497
|
+
// Ensure namespace exists when rendering
|
|
1498
|
+
if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
|
|
1499
|
+
|
|
1238
1500
|
// --- Modal Setup ---
|
|
1239
1501
|
const modal = document.getElementById('worker-modal-${chartId}');
|
|
1240
1502
|
const modalTitle = document.getElementById('worker-modal-title-${chartId}');
|
|
@@ -1252,6 +1514,7 @@ window.${renderFunctionName} = function() {
|
|
|
1252
1514
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1253
1515
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1254
1516
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1517
|
+
else if (test.status === 'flaky') color = '#00ccd3';
|
|
1255
1518
|
|
|
1256
1519
|
// Updated escaping logic
|
|
1257
1520
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -1345,20 +1608,31 @@ window.${renderFunctionName} = function() {
|
|
|
1345
1608
|
* @type {string}
|
|
1346
1609
|
*/
|
|
1347
1610
|
const infoTooltip = `
|
|
1348
|
-
<span class="info-tooltip" style="display: inline-
|
|
1611
|
+
<span class="info-tooltip" style="display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; vertical-align: middle;">
|
|
1349
1612
|
<span class="info-icon"
|
|
1350
|
-
style="cursor: pointer;
|
|
1351
|
-
|
|
1613
|
+
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;"
|
|
1614
|
+
onmouseover="this.style.color='var(--accent-color, #764ba2)'; this.style.transform='scale(1.1)';"
|
|
1615
|
+
onmouseout="this.style.color='var(--text-color-secondary, #6b7280)'; this.style.transform='scale(1)';"
|
|
1616
|
+
onclick="window.workerInfoPrompt()"
|
|
1617
|
+
title="Click to understand Worker -1">
|
|
1618
|
+
<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">
|
|
1619
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
1620
|
+
<line x1="12" y1="16" x2="12" y2="12"></line>
|
|
1621
|
+
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
|
1622
|
+
</svg>
|
|
1623
|
+
</span>
|
|
1352
1624
|
</span>
|
|
1353
1625
|
<script>
|
|
1354
|
-
window.workerInfoPrompt
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1626
|
+
if (!window.workerInfoPrompt) {
|
|
1627
|
+
window.workerInfoPrompt = function() {
|
|
1628
|
+
const message = 'Why is worker -1 special?\\n\\n' +
|
|
1629
|
+
'Playwright assigns all pre-skipped tests/test.skip() to worker -1 because:\\n' +
|
|
1630
|
+
'1. They don\\'t require browser execution\\n' +
|
|
1631
|
+
'2. This keeps real workers focused on actual tests\\n' +
|
|
1632
|
+
'3. Maintains clean reporting\\n\\n' +
|
|
1633
|
+
'This is an intentional optimization by Playwright.';
|
|
1634
|
+
alert(message);
|
|
1635
|
+
}
|
|
1362
1636
|
}
|
|
1363
1637
|
</script>
|
|
1364
1638
|
`;
|
|
@@ -1427,6 +1701,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1427
1701
|
<option value="">All Statuses</option>
|
|
1428
1702
|
<option value="passed">Passed</option>
|
|
1429
1703
|
<option value="failed">Failed</option>
|
|
1704
|
+
<option value="flaky">Flaky</option>
|
|
1430
1705
|
<option value="skipped">Skipped</option>
|
|
1431
1706
|
</select>
|
|
1432
1707
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1499,6 +1774,8 @@ function getStatusClass(status) {
|
|
|
1499
1774
|
return "status-failed";
|
|
1500
1775
|
case "skipped":
|
|
1501
1776
|
return "status-skipped";
|
|
1777
|
+
case "flaky":
|
|
1778
|
+
return "status-flaky";
|
|
1502
1779
|
default:
|
|
1503
1780
|
return "status-unknown";
|
|
1504
1781
|
}
|
|
@@ -1516,6 +1793,8 @@ function getStatusIcon(status) {
|
|
|
1516
1793
|
return "❌";
|
|
1517
1794
|
case "skipped":
|
|
1518
1795
|
return "⏭️";
|
|
1796
|
+
case "flaky":
|
|
1797
|
+
return "⚠️";
|
|
1519
1798
|
default:
|
|
1520
1799
|
return "❓";
|
|
1521
1800
|
}
|
|
@@ -1556,6 +1835,7 @@ function getSuitesData(results) {
|
|
|
1556
1835
|
browser: browser,
|
|
1557
1836
|
passed: 0,
|
|
1558
1837
|
failed: 0,
|
|
1838
|
+
flaky: 0,
|
|
1559
1839
|
skipped: 0,
|
|
1560
1840
|
count: 0,
|
|
1561
1841
|
statusOverall: "passed",
|
|
@@ -1563,12 +1843,15 @@ function getSuitesData(results) {
|
|
|
1563
1843
|
}
|
|
1564
1844
|
const suite = suitesMap.get(key);
|
|
1565
1845
|
suite.count++;
|
|
1566
|
-
|
|
1846
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1847
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1567
1848
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1568
1849
|
suite[currentStatus]++;
|
|
1569
1850
|
}
|
|
1570
1851
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1571
|
-
else if (currentStatus === "
|
|
1852
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1853
|
+
suite.statusOverall = "flaky";
|
|
1854
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1572
1855
|
suite.statusOverall = "skipped";
|
|
1573
1856
|
});
|
|
1574
1857
|
return Array.from(suitesMap.values());
|
|
@@ -1614,8 +1897,8 @@ function generateSuitesWidget(suitesData) {
|
|
|
1614
1897
|
|
|
1615
1898
|
// Added inline styles for height consistency with Pie Chart (approx 450px) and scrolling
|
|
1616
1899
|
return `
|
|
1617
|
-
<div class="suites-widget
|
|
1618
|
-
<div class="suites-header"
|
|
1900
|
+
<div class="suites-widget fixed-height-widget">
|
|
1901
|
+
<div class="suites-header">
|
|
1619
1902
|
<h2>Test Suites</h2>
|
|
1620
1903
|
<span class="summary-badge">${
|
|
1621
1904
|
suitesData.length
|
|
@@ -1625,40 +1908,40 @@ function generateSuitesWidget(suitesData) {
|
|
|
1625
1908
|
)} tests</span>
|
|
1626
1909
|
</div>
|
|
1627
1910
|
|
|
1628
|
-
<div class="suites-grid-container"
|
|
1911
|
+
<div class="suites-grid-container">
|
|
1629
1912
|
<div class="suites-grid">
|
|
1630
1913
|
${suitesData
|
|
1631
1914
|
.map(
|
|
1632
1915
|
(suite) => `
|
|
1633
1916
|
<div class="suite-card status-${suite.statusOverall}">
|
|
1634
1917
|
<div class="suite-card-header">
|
|
1635
|
-
<h3 class="suite-name" title="${sanitizeHTML(
|
|
1636
|
-
|
|
1637
|
-
|
|
1918
|
+
<h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1919
|
+
<div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
|
|
1920
|
+
</div>
|
|
1921
|
+
|
|
1922
|
+
<div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
|
|
1923
|
+
<span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
|
|
1638
1924
|
</div>
|
|
1639
|
-
|
|
1640
|
-
suite.browser,
|
|
1641
|
-
)}</span></div>
|
|
1925
|
+
|
|
1642
1926
|
<div class="suite-card-body">
|
|
1643
|
-
<span class="test-count">${suite.count}
|
|
1644
|
-
suite.count !== 1 ? "s" : ""
|
|
1645
|
-
}</span>
|
|
1927
|
+
<span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
|
|
1646
1928
|
<div class="suite-stats">
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1929
|
+
<span class="stat-pill passed" title="Passed">
|
|
1930
|
+
<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>
|
|
1931
|
+
${suite.passed}
|
|
1932
|
+
</span>
|
|
1933
|
+
<span class="stat-pill failed" title="Failed">
|
|
1934
|
+
<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>
|
|
1935
|
+
${suite.failed}
|
|
1936
|
+
</span>
|
|
1937
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1938
|
+
<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>
|
|
1939
|
+
${suite.flaky || 0}
|
|
1940
|
+
</span>
|
|
1941
|
+
<span class="stat-pill skipped" title="Skipped">
|
|
1942
|
+
<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>
|
|
1943
|
+
${suite.skipped}
|
|
1944
|
+
</span>
|
|
1662
1945
|
</div>
|
|
1663
1946
|
</div>
|
|
1664
1947
|
</div>`,
|
|
@@ -2037,6 +2320,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2037
2320
|
const data = {
|
|
2038
2321
|
passed: [0, 0, 0, 0, 0],
|
|
2039
2322
|
failed: [0, 0, 0, 0, 0],
|
|
2323
|
+
flaky: [0, 0, 0, 0, 0],
|
|
2040
2324
|
skipped: [0, 0, 0, 0, 0],
|
|
2041
2325
|
};
|
|
2042
2326
|
|
|
@@ -2055,6 +2339,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
2055
2339
|
status === "interrupted"
|
|
2056
2340
|
) {
|
|
2057
2341
|
data.failed[index]++;
|
|
2342
|
+
} else if (status === "flaky") {
|
|
2343
|
+
data.flaky[index]++;
|
|
2058
2344
|
} else {
|
|
2059
2345
|
data.skipped[index]++;
|
|
2060
2346
|
}
|
|
@@ -2068,6 +2354,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
2068
2354
|
const seriesData = [
|
|
2069
2355
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
2070
2356
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2357
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
2071
2358
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
2072
2359
|
];
|
|
2073
2360
|
|
|
@@ -2211,32 +2498,85 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2211
2498
|
duration: 0,
|
|
2212
2499
|
timestamp: new Date().toISOString(),
|
|
2213
2500
|
};
|
|
2214
|
-
|
|
2215
|
-
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2216
|
-
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2217
|
-
const skipPercentage = Math.round(
|
|
2218
|
-
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2219
|
-
);
|
|
2501
|
+
|
|
2220
2502
|
const avgTestDuration =
|
|
2221
2503
|
runSummary.totalTests > 0
|
|
2222
2504
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2223
2505
|
: "0.0s";
|
|
2224
2506
|
|
|
2507
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2508
|
+
|
|
2225
2509
|
// Calculate retry statistics
|
|
2510
|
+
let retriedTestsCount = 0;
|
|
2226
2511
|
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2227
|
-
if (test.
|
|
2228
|
-
|
|
2512
|
+
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2513
|
+
// Filter out any "passed" or "skipped" entries in the history
|
|
2514
|
+
// We only count attempts that actually failed or timed out, triggering a retry.
|
|
2515
|
+
const unsuccessfulRetries = test.retryHistory.filter(attempt =>
|
|
2516
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
2517
|
+
);
|
|
2518
|
+
if (unsuccessfulRetries.length > 0) {
|
|
2519
|
+
retriedTestsCount++;
|
|
2520
|
+
}
|
|
2521
|
+
return acc + unsuccessfulRetries.length;
|
|
2229
2522
|
}
|
|
2230
2523
|
return acc;
|
|
2231
2524
|
}, 0);
|
|
2232
2525
|
|
|
2526
|
+
// --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
|
|
2527
|
+
let calculatedPassed = 0;
|
|
2528
|
+
let calculatedFailed = 0;
|
|
2529
|
+
let calculatedSkipped = 0;
|
|
2530
|
+
let calculatedFlaky = 0;
|
|
2531
|
+
let calculatedTotal = 0;
|
|
2532
|
+
|
|
2533
|
+
(results || []).forEach(test => {
|
|
2534
|
+
calculatedTotal++;
|
|
2535
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2536
|
+
let statusToUse = test.status;
|
|
2537
|
+
if (test.outcome === 'flaky') {
|
|
2538
|
+
statusToUse = 'flaky';
|
|
2539
|
+
} else if (test.status === 'flaky') {
|
|
2540
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2541
|
+
statusToUse = 'flaky';
|
|
2542
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2543
|
+
statusToUse = test.final_status;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// Update test status in place for charts
|
|
2547
|
+
test.status = statusToUse;
|
|
2548
|
+
|
|
2549
|
+
const s = String(statusToUse).toLowerCase();
|
|
2550
|
+
if (s === 'passed') calculatedPassed++;
|
|
2551
|
+
else if (s === 'skipped') calculatedSkipped++;
|
|
2552
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2553
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2554
|
+
});
|
|
2555
|
+
|
|
2556
|
+
// Override runSummary counts with our calculated ones if results exist
|
|
2557
|
+
if (results && results.length > 0) {
|
|
2558
|
+
runSummary.passed = calculatedPassed;
|
|
2559
|
+
runSummary.failed = calculatedFailed;
|
|
2560
|
+
runSummary.skipped = calculatedSkipped;
|
|
2561
|
+
runSummary.flaky = calculatedFlaky;
|
|
2562
|
+
runSummary.totalTests = calculatedTotal;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2566
|
+
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2567
|
+
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2568
|
+
const skipPercentage = Math.round(
|
|
2569
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2570
|
+
);
|
|
2571
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2572
|
+
|
|
2573
|
+
|
|
2233
2574
|
// Calculate browser distribution
|
|
2234
2575
|
const browserStats = (results || []).reduce((acc, test) => {
|
|
2235
2576
|
let browserName = "unknown";
|
|
2236
2577
|
if (test.browser) {
|
|
2237
|
-
//
|
|
2238
|
-
|
|
2239
|
-
browserName = match ? match[1] : test.browser;
|
|
2578
|
+
// Use full browser name
|
|
2579
|
+
browserName = test.browser;
|
|
2240
2580
|
}
|
|
2241
2581
|
acc[browserName] = (acc[browserName] || 0) + 1;
|
|
2242
2582
|
return acc;
|
|
@@ -2277,6 +2617,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2277
2617
|
const severity = test.severity || "Medium";
|
|
2278
2618
|
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2279
2619
|
|
|
2620
|
+
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2621
|
+
const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
|
|
2622
|
+
const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2623
|
+
|
|
2280
2624
|
// --- Step Generation ---
|
|
2281
2625
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2282
2626
|
if (!steps || steps.length === 0)
|
|
@@ -2285,17 +2629,20 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2285
2629
|
.map((step) => {
|
|
2286
2630
|
const hasNestedSteps = step.steps && step.steps.length > 0;
|
|
2287
2631
|
const isHook = step.hookType;
|
|
2632
|
+
const isFailedStep = step.isFailedStep === true;
|
|
2288
2633
|
const stepClass = isHook
|
|
2289
2634
|
? `step-hook step-hook-${step.hookType}`
|
|
2290
2635
|
: "";
|
|
2636
|
+
const failedStepClass = isFailedStep ? " failed-step-highlight" : "";
|
|
2291
2637
|
const hookIndicator = isHook ? ` (${step.hookType} hook)` : "";
|
|
2638
|
+
const failedStepIndicator = isFailedStep ? ` <span class="failed-step-marker">⚠️ Failed at this step</span>` : "";
|
|
2292
2639
|
return `
|
|
2293
|
-
<div class="step-item" style="--depth: ${depth};">
|
|
2640
|
+
<div class="step-item${failedStepClass}" style="--depth: ${depth};">
|
|
2294
2641
|
<div class="step-header ${stepClass}" role="button" aria-expanded="false">
|
|
2295
2642
|
<span class="step-icon">${getStatusIcon(step.status)}</span>
|
|
2296
2643
|
<span class="step-title">${sanitizeHTML(
|
|
2297
2644
|
step.title,
|
|
2298
|
-
)}${hookIndicator}</span>
|
|
2645
|
+
)}${hookIndicator}${failedStepIndicator}</span>
|
|
2299
2646
|
<span class="step-duration">${formatDuration(
|
|
2300
2647
|
step.duration,
|
|
2301
2648
|
)}</span>
|
|
@@ -2308,6 +2655,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2308
2655
|
)}</div>`
|
|
2309
2656
|
: ""
|
|
2310
2657
|
}
|
|
2658
|
+
${
|
|
2659
|
+
step.codeSnippet
|
|
2660
|
+
? `<div class="code-snippet-section"><pre class="code-snippet">${sanitizeHTML(
|
|
2661
|
+
step.codeSnippet,
|
|
2662
|
+
)}</pre></div>`
|
|
2663
|
+
: ""
|
|
2664
|
+
}
|
|
2311
2665
|
${
|
|
2312
2666
|
step.errorMessage
|
|
2313
2667
|
? `<div class="test-error-summary">
|
|
@@ -2318,7 +2672,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2318
2672
|
)}</div>`
|
|
2319
2673
|
: ""
|
|
2320
2674
|
}
|
|
2321
|
-
<button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
|
|
2675
|
+
<button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s; align-self: flex-end; width: auto;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
|
|
2322
2676
|
</div>`
|
|
2323
2677
|
: ""
|
|
2324
2678
|
}
|
|
@@ -2336,43 +2690,36 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2336
2690
|
.join("");
|
|
2337
2691
|
};
|
|
2338
2692
|
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
test.status,
|
|
2365
|
-
).toUpperCase()}</span>
|
|
2366
|
-
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2367
|
-
</div>
|
|
2368
|
-
</div>
|
|
2369
|
-
<div class="test-case-content" style="display: none;">
|
|
2370
|
-
<p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
|
|
2693
|
+
// Helper for Tab Badges
|
|
2694
|
+
const getSmallStatusBadge = (status) => {
|
|
2695
|
+
const s = String(status).toLowerCase();
|
|
2696
|
+
let colorVar = 'var(--text-tertiary)';
|
|
2697
|
+
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2698
|
+
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2699
|
+
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2700
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2701
|
+
|
|
2702
|
+
return `<span style="
|
|
2703
|
+
display: inline-block;
|
|
2704
|
+
width: 8px;
|
|
2705
|
+
height: 8px;
|
|
2706
|
+
border-radius: 50%;
|
|
2707
|
+
background-color: ${colorVar};
|
|
2708
|
+
margin-left: 6px;
|
|
2709
|
+
vertical-align: middle;
|
|
2710
|
+
" title="${s}"></span>`;
|
|
2711
|
+
};
|
|
2712
|
+
|
|
2713
|
+
// Function to generate test content HTML (used for base run and retry tabs)
|
|
2714
|
+
const getTestContentHTML = (testData, runSuffix) => {
|
|
2715
|
+
const logId = `stdout-log-${test.id || testIndex}-${runSuffix}`;
|
|
2716
|
+
return `
|
|
2717
|
+
<p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
|
|
2371
2718
|
${
|
|
2372
|
-
|
|
2373
|
-
? `<div class="annotations-section">
|
|
2374
|
-
<h4 style="margin-top: 0; margin-bottom: 10px; font-size: 1.1em;">📌 Annotations</h4>
|
|
2375
|
-
${
|
|
2719
|
+
testData.annotations && testData.annotations.length > 0
|
|
2720
|
+
? `<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;">
|
|
2721
|
+
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
2722
|
+
${testData.annotations
|
|
2376
2723
|
.map((annotation, idx) => {
|
|
2377
2724
|
const isIssueOrBug =
|
|
2378
2725
|
annotation.type === "issue" ||
|
|
@@ -2380,176 +2727,312 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2380
2727
|
const descriptionText = annotation.description || "";
|
|
2381
2728
|
const typeLabel = sanitizeHTML(annotation.type);
|
|
2382
2729
|
const descriptionHtml =
|
|
2383
|
-
isIssueOrBug && descriptionText.match(/^[A-Z]
|
|
2730
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
|
|
2384
2731
|
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
2385
2732
|
descriptionText,
|
|
2386
|
-
)}" style="color:
|
|
2733
|
+
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
2387
2734
|
descriptionText,
|
|
2388
2735
|
)}</a>`
|
|
2389
2736
|
: sanitizeHTML(descriptionText);
|
|
2390
2737
|
const locationText = annotation.location
|
|
2391
|
-
? `<div style="font-size: 0.85em; color:
|
|
2738
|
+
? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
|
|
2392
2739
|
annotation.location.file,
|
|
2393
2740
|
)}:${annotation.location.line}:${
|
|
2394
2741
|
annotation.location.column
|
|
2395
2742
|
}</div>`
|
|
2396
2743
|
: "";
|
|
2397
2744
|
return `<div style="margin-bottom: ${
|
|
2398
|
-
idx <
|
|
2399
|
-
};"
|
|
2745
|
+
idx < testData.annotations.length - 1 ? "10px" : "0"
|
|
2746
|
+
};">
|
|
2747
|
+
<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>
|
|
2748
|
+
${
|
|
2400
2749
|
descriptionText
|
|
2401
|
-
? `<br><strong>Description:</strong> ${descriptionHtml}`
|
|
2750
|
+
? `<br><strong style="color: #8b5cf6;">Description:</strong> ${descriptionHtml}`
|
|
2402
2751
|
: ""
|
|
2403
|
-
}
|
|
2752
|
+
}
|
|
2753
|
+
${locationText}
|
|
2754
|
+
</div>`;
|
|
2404
2755
|
})
|
|
2405
2756
|
.join("")}
|
|
2406
2757
|
</div>`
|
|
2407
2758
|
: ""
|
|
2408
2759
|
}
|
|
2409
2760
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
2410
|
-
|
|
2761
|
+
testData.workerId,
|
|
2411
2762
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2412
|
-
|
|
2763
|
+
testData.totalWorkers,
|
|
2413
2764
|
)}]</p>
|
|
2414
2765
|
${
|
|
2415
|
-
|
|
2416
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2417
|
-
|
|
2418
|
-
)}
|
|
2766
|
+
testData.errorMessage
|
|
2767
|
+
? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
|
|
2768
|
+
testData.errorMessage,
|
|
2769
|
+
)}</div>
|
|
2770
|
+
<button
|
|
2771
|
+
class="copy-error-btn"
|
|
2772
|
+
onclick="copyErrorToClipboard(this)"
|
|
2773
|
+
style="
|
|
2774
|
+
margin-top: 8px;
|
|
2775
|
+
padding: 6px 12px;
|
|
2776
|
+
background: rgba(248, 113, 113, 0.15);
|
|
2777
|
+
border: 2px solid var(--danger-color);
|
|
2778
|
+
border-radius: 6px;
|
|
2779
|
+
cursor: pointer;
|
|
2780
|
+
font-size: 12px;
|
|
2781
|
+
color: var(--danger-color);
|
|
2782
|
+
font-weight: 600;
|
|
2783
|
+
transition: 0.2s;
|
|
2784
|
+
align-self: flex-end;
|
|
2785
|
+
width: auto;
|
|
2786
|
+
"
|
|
2787
|
+
onmouseover="this.style.background='#e0e0e0'"
|
|
2788
|
+
onmouseout="this.style.background='#f0f0f0'"
|
|
2789
|
+
>
|
|
2790
|
+
Copy Error Prompt
|
|
2791
|
+
</button>
|
|
2792
|
+
</div>`
|
|
2419
2793
|
: ""
|
|
2420
2794
|
}
|
|
2421
2795
|
${
|
|
2422
|
-
|
|
2796
|
+
testData.snippet
|
|
2423
2797
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2424
|
-
|
|
2798
|
+
testData.snippet,
|
|
2425
2799
|
)}</code></pre></div>`
|
|
2426
2800
|
: ""
|
|
2427
2801
|
}
|
|
2428
2802
|
<h4>Steps</h4>
|
|
2429
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2430
|
-
|
|
2431
|
-
${(() => {
|
|
2432
|
-
if (!test.stdout || test.stdout.length === 0) return "";
|
|
2433
|
-
// FIXED: Now using 'testIndex' which is guaranteed to be defined
|
|
2434
|
-
const logId = `stdout-log-${test.id || testIndex}`;
|
|
2435
|
-
return `<div class="console-output-section"><h4>Console Output (stdout) <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button></h4><div class="log-wrapper"><pre id="${logId}" class="console-log stdout-log" style="background-color: var(--bg-tertiary); color: #f3e8c3; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
|
|
2436
|
-
test.stdout.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2437
|
-
)}</pre></div></div>`;
|
|
2438
|
-
})()}
|
|
2439
|
-
|
|
2440
|
-
${(() => {
|
|
2441
|
-
if (!test.stderr || test.stderr.length === 0) return "";
|
|
2442
|
-
// FIXED: Using 'testIndex'
|
|
2443
|
-
const logId = `stderr-log-${test.id || testIndex}`;
|
|
2444
|
-
return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: var(--bg-tertiary); color: #f87171; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
|
|
2445
|
-
test.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2446
|
-
)}</pre></div>`;
|
|
2447
|
-
})()}
|
|
2448
|
-
|
|
2803
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2449
2804
|
${(() => {
|
|
2450
|
-
if (!
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
return createLazyMedia(
|
|
2464
|
-
base64ImageData,
|
|
2465
|
-
"image/png",
|
|
2466
|
-
"image",
|
|
2467
|
-
sIndex + 1,
|
|
2468
|
-
`screenshot-${testIndex}-${sIndex}.png`,
|
|
2469
|
-
);
|
|
2470
|
-
} catch (e) {
|
|
2471
|
-
return `<div class="attachment-item error">Error loading screenshot</div>`;
|
|
2472
|
-
}
|
|
2473
|
-
})
|
|
2474
|
-
.join("");
|
|
2475
|
-
return `<div class="attachments-section"><h4>Screenshots</h4><div class="attachments-grid">${screenshotsHTML}</div></div>`;
|
|
2476
|
-
})()}
|
|
2477
|
-
|
|
2478
|
-
${(() => {
|
|
2479
|
-
if (!test.videoPath || test.videoPath.length === 0) return "";
|
|
2480
|
-
const videosHTML = test.videoPath
|
|
2481
|
-
.map((videoPath, vIndex) => {
|
|
2482
|
-
try {
|
|
2483
|
-
const videoFilePath = path.resolve(
|
|
2484
|
-
DEFAULT_OUTPUT_DIR,
|
|
2485
|
-
videoPath,
|
|
2486
|
-
);
|
|
2487
|
-
if (!fsExistsSync(videoFilePath))
|
|
2488
|
-
return `<div class="attachment-item error">Video not found</div>`;
|
|
2489
|
-
const videoBase64 =
|
|
2490
|
-
readFileSync(videoFilePath).toString("base64");
|
|
2491
|
-
const ext = path.extname(videoPath).slice(1).toLowerCase();
|
|
2492
|
-
const mime =
|
|
2493
|
-
{ mp4: "video/mp4", webm: "video/webm" }[ext] ||
|
|
2494
|
-
"video/mp4";
|
|
2495
|
-
// LAZY LOAD: Using helper with unique ID
|
|
2496
|
-
return createLazyMedia(
|
|
2497
|
-
videoBase64,
|
|
2498
|
-
mime,
|
|
2499
|
-
"video",
|
|
2500
|
-
vIndex + 1,
|
|
2501
|
-
`video-${testIndex}-${vIndex}.${ext}`,
|
|
2502
|
-
);
|
|
2503
|
-
} catch (e) {
|
|
2504
|
-
return `<div class="attachment-item error">Error loading video</div>`;
|
|
2505
|
-
}
|
|
2506
|
-
})
|
|
2507
|
-
.join("");
|
|
2508
|
-
return `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${videosHTML}</div></div>`;
|
|
2805
|
+
if (!testData.stdout || testData.stdout.length === 0) return "";
|
|
2806
|
+
return `<div class="console-output-section">
|
|
2807
|
+
<h4>Console Output (stdout)
|
|
2808
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
|
|
2809
|
+
</h4>
|
|
2810
|
+
<div class="log-wrapper">
|
|
2811
|
+
<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(
|
|
2812
|
+
testData.stdout
|
|
2813
|
+
.map((line) => sanitizeHTML(line))
|
|
2814
|
+
.join("\n"),
|
|
2815
|
+
)}</pre>
|
|
2816
|
+
</div>
|
|
2817
|
+
</div>`;
|
|
2509
2818
|
})()}
|
|
2510
|
-
|
|
2511
2819
|
${
|
|
2512
|
-
|
|
2513
|
-
? `<div class="
|
|
2514
|
-
|
|
2515
|
-
)}</
|
|
2516
|
-
test.tracePath,
|
|
2517
|
-
)}" target="_blank" download="${sanitizeHTML(
|
|
2518
|
-
path.basename(test.tracePath),
|
|
2519
|
-
)}" class="download-trace">Download Trace</a></div></div></div></div></div>`
|
|
2820
|
+
testData.stderr && testData.stderr.length > 0
|
|
2821
|
+
? `<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(
|
|
2822
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2823
|
+
)}</pre></div>`
|
|
2520
2824
|
: ""
|
|
2521
2825
|
}
|
|
2522
2826
|
${
|
|
2523
|
-
|
|
2524
|
-
?
|
|
2827
|
+
testData.screenshots && testData.screenshots.length > 0
|
|
2828
|
+
? `
|
|
2829
|
+
<div class="attachments-section">
|
|
2830
|
+
<h4>Screenshots</h4>
|
|
2831
|
+
<div class="attachments-grid">
|
|
2832
|
+
${testData.screenshots
|
|
2525
2833
|
.map(
|
|
2526
|
-
(
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2834
|
+
(screenshot, screenshotIndex) => `
|
|
2835
|
+
<div class="attachment-item">
|
|
2836
|
+
<img src="${sanitizeHTML(screenshot)}" alt="Screenshot ${
|
|
2837
|
+
screenshotIndex + 1
|
|
2838
|
+
}">
|
|
2839
|
+
<div class="attachment-info">
|
|
2840
|
+
<div class="trace-actions">
|
|
2841
|
+
<a href="${sanitizeHTML(
|
|
2842
|
+
screenshot,
|
|
2843
|
+
)}" target="_blank" class="view-full">View Full Image</a>
|
|
2844
|
+
<a href="${sanitizeHTML(
|
|
2845
|
+
screenshot,
|
|
2846
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
|
|
2847
|
+
</div>
|
|
2848
|
+
</div>
|
|
2849
|
+
</div>
|
|
2850
|
+
`,
|
|
2542
2851
|
)
|
|
2852
|
+
.join("")}
|
|
2853
|
+
</div>
|
|
2854
|
+
</div>
|
|
2855
|
+
`
|
|
2856
|
+
: ""
|
|
2857
|
+
}
|
|
2858
|
+
${
|
|
2859
|
+
testData.videoPath && testData.videoPath.length > 0
|
|
2860
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
|
|
2861
|
+
.map((videoUrl, videoIndex) => {
|
|
2862
|
+
const fixedVideoUrl = sanitizeHTML(videoUrl);
|
|
2863
|
+
const fileExtension = String(fixedVideoUrl)
|
|
2864
|
+
.split(".")
|
|
2865
|
+
.pop()
|
|
2866
|
+
.toLowerCase();
|
|
2867
|
+
const mimeType =
|
|
2868
|
+
{
|
|
2869
|
+
mp4: "video/mp4",
|
|
2870
|
+
webm: "video/webm",
|
|
2871
|
+
ogg: "video/ogg",
|
|
2872
|
+
mov: "video/quicktime",
|
|
2873
|
+
avi: "video/x-msvideo",
|
|
2874
|
+
}[fileExtension] || "video/mp4";
|
|
2875
|
+
return `<div class="attachment-item video-item">
|
|
2876
|
+
<video controls width="100%" height="auto" title="Video ${
|
|
2877
|
+
videoIndex + 1
|
|
2878
|
+
}">
|
|
2879
|
+
<source src="${sanitizeHTML(
|
|
2880
|
+
fixedVideoUrl,
|
|
2881
|
+
)}" type="${mimeType}">
|
|
2882
|
+
Your browser does not support the video tag.
|
|
2883
|
+
</video>
|
|
2884
|
+
<div class="attachment-info">
|
|
2885
|
+
<div class="trace-actions">
|
|
2886
|
+
<a href="${sanitizeHTML(
|
|
2887
|
+
fixedVideoUrl,
|
|
2888
|
+
)}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
|
|
2889
|
+
</div>
|
|
2890
|
+
</div>
|
|
2891
|
+
</div>`;
|
|
2892
|
+
})
|
|
2543
2893
|
.join("")}</div></div>`
|
|
2544
2894
|
: ""
|
|
2545
2895
|
}
|
|
2546
2896
|
${
|
|
2547
|
-
|
|
2548
|
-
?
|
|
2549
|
-
|
|
2550
|
-
|
|
2897
|
+
testData.tracePath
|
|
2898
|
+
? `
|
|
2899
|
+
<div class="attachments-section">
|
|
2900
|
+
<h4>Trace Files</h4>
|
|
2901
|
+
<div class="attachments-grid">
|
|
2902
|
+
<div class="attachment-item trace-item">
|
|
2903
|
+
<div class="trace-preview">
|
|
2904
|
+
<span class="trace-icon">📄</span>
|
|
2905
|
+
<span class="trace-name">${sanitizeHTML(
|
|
2906
|
+
path.basename(testData.tracePath),
|
|
2907
|
+
)}</span>
|
|
2908
|
+
</div>
|
|
2909
|
+
<div class="attachment-info">
|
|
2910
|
+
<div class="trace-actions">
|
|
2911
|
+
<a href="${sanitizeHTML(
|
|
2912
|
+
sanitizeHTML(testData.tracePath),
|
|
2913
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
2914
|
+
path.basename(testData.tracePath),
|
|
2915
|
+
)}" class="download-trace">Download Trace</a>
|
|
2916
|
+
</div>
|
|
2917
|
+
</div>
|
|
2918
|
+
</div>
|
|
2919
|
+
</div>
|
|
2920
|
+
</div>
|
|
2921
|
+
`
|
|
2922
|
+
: ""
|
|
2923
|
+
}
|
|
2924
|
+
${
|
|
2925
|
+
testData.attachments && testData.attachments.length > 0
|
|
2926
|
+
? `
|
|
2927
|
+
<div class="attachments-section">
|
|
2928
|
+
<h4>Other Attachments</h4>
|
|
2929
|
+
<div class="attachments-grid">
|
|
2930
|
+
${testData.attachments
|
|
2931
|
+
.map(
|
|
2932
|
+
(attachment) => `
|
|
2933
|
+
<div class="attachment-item generic-attachment">
|
|
2934
|
+
<div class="attachment-icon">${getAttachmentIcon(
|
|
2935
|
+
attachment.contentType,
|
|
2936
|
+
)}</div>
|
|
2937
|
+
<div class="attachment-caption">
|
|
2938
|
+
<span class="attachment-name" title="${sanitizeHTML(
|
|
2939
|
+
attachment.name,
|
|
2940
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
2941
|
+
<span class="attachment-type">${sanitizeHTML(
|
|
2942
|
+
attachment.contentType,
|
|
2943
|
+
)}</span>
|
|
2944
|
+
</div>
|
|
2945
|
+
<div class="attachment-info">
|
|
2946
|
+
<div class="trace-actions">
|
|
2947
|
+
<a href="${sanitizeHTML(
|
|
2948
|
+
sanitizeHTML(attachment.path),
|
|
2949
|
+
)}" target="_blank" class="view-full">View</a>
|
|
2950
|
+
<a href="${sanitizeHTML(
|
|
2951
|
+
sanitizeHTML(attachment.path),
|
|
2952
|
+
)}" target="_blank" download="${sanitizeHTML(
|
|
2953
|
+
attachment.name,
|
|
2954
|
+
)}" class="download-trace">Download</a>
|
|
2955
|
+
</div>
|
|
2956
|
+
</div>
|
|
2957
|
+
</div>
|
|
2958
|
+
`,
|
|
2959
|
+
)
|
|
2960
|
+
.join("")}
|
|
2961
|
+
</div>
|
|
2962
|
+
</div>
|
|
2963
|
+
`
|
|
2551
2964
|
: ""
|
|
2552
2965
|
}
|
|
2966
|
+
`;
|
|
2967
|
+
};
|
|
2968
|
+
|
|
2969
|
+
|
|
2970
|
+
// Determine header status: use final_status if retried, else normal status
|
|
2971
|
+
const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
|
|
2972
|
+
? test.final_status
|
|
2973
|
+
: test.status;
|
|
2974
|
+
|
|
2975
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2976
|
+
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2977
|
+
: '';
|
|
2978
|
+
|
|
2979
|
+
return `
|
|
2980
|
+
<div class="test-case" data-status="${
|
|
2981
|
+
headerStatus
|
|
2982
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
2983
|
+
.join(",")
|
|
2984
|
+
.toLowerCase()}">
|
|
2985
|
+
<div class="test-case-header" role="button" aria-expanded="false">
|
|
2986
|
+
<div class="test-case-summary">
|
|
2987
|
+
<span class="test-case-title" title="${sanitizeHTML(
|
|
2988
|
+
test.name,
|
|
2989
|
+
)}">${sanitizeHTML(testTitle)}</span>
|
|
2990
|
+
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
2991
|
+
</div>
|
|
2992
|
+
<div class="test-case-meta">
|
|
2993
|
+
${severityBadge}
|
|
2994
|
+
${retryBadge}
|
|
2995
|
+
${outcomeBadge}
|
|
2996
|
+
${
|
|
2997
|
+
test.tags && test.tags.length > 0
|
|
2998
|
+
? test.tags
|
|
2999
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
3000
|
+
.join(" ")
|
|
3001
|
+
: ""
|
|
3002
|
+
}
|
|
3003
|
+
</div>
|
|
3004
|
+
<div class="test-case-status-duration">
|
|
3005
|
+
<span class="status-badge ${getStatusClass(headerStatus)}">${String(
|
|
3006
|
+
headerStatus,
|
|
3007
|
+
).toUpperCase()}</span>
|
|
3008
|
+
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
3009
|
+
</div>
|
|
3010
|
+
</div>
|
|
3011
|
+
<div class="test-case-content" style="display: none;">
|
|
3012
|
+
${test.retryHistory && test.retryHistory.length > 0 ? `
|
|
3013
|
+
<div class="retry-tabs-container">
|
|
3014
|
+
<div class="retry-tabs-header">
|
|
3015
|
+
<button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
|
|
3016
|
+
Base Run ${getSmallStatusBadge(test.final_status || test.status)}
|
|
3017
|
+
</button>
|
|
3018
|
+
${test.retryHistory.map((retry, idx) => `
|
|
3019
|
+
<button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
|
|
3020
|
+
Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
|
|
3021
|
+
</button>
|
|
3022
|
+
`).join('')}
|
|
3023
|
+
</div>
|
|
3024
|
+
|
|
3025
|
+
<div id="base-run-${test.id}" class="retry-tab-content active">
|
|
3026
|
+
${getTestContentHTML(test, 'base')}
|
|
3027
|
+
</div>
|
|
3028
|
+
|
|
3029
|
+
${test.retryHistory.map((retry, idx) => `
|
|
3030
|
+
<div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
|
|
3031
|
+
${getTestContentHTML(retry, `retry-${idx + 1}`)}
|
|
3032
|
+
</div>
|
|
3033
|
+
`).join('')}
|
|
3034
|
+
</div>
|
|
3035
|
+
` : getTestContentHTML(test, 'single')}
|
|
2553
3036
|
</div>
|
|
2554
3037
|
</div>`;
|
|
2555
3038
|
})
|
|
@@ -2561,18 +3044,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2561
3044
|
<head>
|
|
2562
3045
|
<meta charset="UTF-8">
|
|
2563
3046
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2564
|
-
<link rel="icon" type="image/png" href
|
|
2565
|
-
<link rel="apple-touch-icon" href
|
|
3047
|
+
<link rel="icon" type="image/png" href=${logo}>
|
|
3048
|
+
<link rel="apple-touch-icon" href=${logo}>
|
|
2566
3049
|
<!-- Preconnect to external domains -->
|
|
2567
3050
|
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
|
|
2568
3051
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2569
|
-
<link rel="preconnect" href="https://code.highcharts.com">
|
|
2570
3052
|
|
|
2571
3053
|
<!-- Preload critical font -->
|
|
3054
|
+
|
|
2572
3055
|
<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'">
|
|
2573
3056
|
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
|
|
2574
3057
|
|
|
2575
|
-
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
3058
|
+
${highchartsContent ? `<script>${highchartsContent}</script>` : '<script src="https://code.highcharts.com/highcharts.js" defer></script>'}
|
|
2576
3059
|
<title>Pulse Static Report</title>
|
|
2577
3060
|
|
|
2578
3061
|
<style>
|
|
@@ -2583,7 +3066,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2583
3066
|
--success-color: #34d399; --success-dark: #10b981; --success-light: #6ee7b7;
|
|
2584
3067
|
--danger-color: #f87171; --danger-dark: #ef4444; --danger-light: #fca5a5;
|
|
2585
3068
|
--warning-color: #fbbf24; --warning-dark: #f59e0b; --warning-light: #fcd34d;
|
|
2586
|
-
--info-color: #9ca3af;
|
|
3069
|
+
--info-color: #9ca3af;
|
|
3070
|
+
--flaky-color: #00ccd3;
|
|
2587
3071
|
--text-primary: #f9fafb; --text-secondary: #e5e7eb; --text-tertiary: #d1d5db;
|
|
2588
3072
|
--bg-primary: #000000; --bg-secondary: #0a0a0a; --bg-tertiary: #050505;
|
|
2589
3073
|
--bg-card: #0d0d0d; --bg-card-hover: #121212;
|
|
@@ -2591,6 +3075,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2591
3075
|
--light-gray-color: #262626; --medium-gray-color: #333333; --dark-gray-color: #a3a3a3;
|
|
2592
3076
|
--text-color: #f9fafb; --text-color-secondary: #e5e7eb; --border-color: #262626;
|
|
2593
3077
|
--card-background-color: #0d0d0d;
|
|
3078
|
+
--neutral-100: #171717; --neutral-200: #262626; --neutral-300: #404040;
|
|
3079
|
+
--bg-hover: #171717;
|
|
2594
3080
|
--font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
2595
3081
|
--radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-2xl: 24px;
|
|
2596
3082
|
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.8);
|
|
@@ -2682,7 +3168,6 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2682
3168
|
background: var(--gradient-card);
|
|
2683
3169
|
border-radius: 14px;
|
|
2684
3170
|
box-shadow: var(--shadow-md);
|
|
2685
|
-
border: 1px solid var(--border-light);
|
|
2686
3171
|
overflow: hidden;
|
|
2687
3172
|
}
|
|
2688
3173
|
.run-info-item {
|
|
@@ -2856,8 +3341,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2856
3341
|
color: #f9fafb;
|
|
2857
3342
|
text-transform: capitalize;
|
|
2858
3343
|
font-size: 1.05em;
|
|
3344
|
+
white-space: nowrap;
|
|
3345
|
+
overflow: hidden;
|
|
3346
|
+
text-overflow: ellipsis;
|
|
3347
|
+
flex: 1;
|
|
3348
|
+
min-width: 0;
|
|
3349
|
+
margin-right: 8px;
|
|
2859
3350
|
}
|
|
2860
3351
|
.browser-stats {
|
|
3352
|
+
color: #9ca3af;
|
|
3353
|
+
white-space: nowrap;
|
|
3354
|
+
flex-shrink: 0;
|
|
2861
3355
|
color: #9ca3af;
|
|
2862
3356
|
font-weight: 700;
|
|
2863
3357
|
font-size: 0.95em;
|
|
@@ -2893,57 +3387,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2893
3387
|
padding: 10px !important;
|
|
2894
3388
|
border-radius: 6px !important;
|
|
2895
3389
|
}
|
|
2896
|
-
|
|
2897
|
-
background: var(--gradient-card);
|
|
2898
|
-
border: 1px solid var(--border-light);
|
|
2899
|
-
border-radius: 12px;
|
|
2900
|
-
padding: 24px;
|
|
2901
|
-
margin-top: 20px;
|
|
2902
|
-
box-shadow: var(--shadow-md);
|
|
2903
|
-
}
|
|
2904
|
-
.env-dashboard-title {
|
|
2905
|
-
font-size: 1.2em;
|
|
2906
|
-
font-weight: 700;
|
|
2907
|
-
color: #f9fafb;
|
|
2908
|
-
margin-bottom: 8px;
|
|
2909
|
-
}
|
|
2910
|
-
.env-dashboard-subtitle {
|
|
2911
|
-
font-size: 0.9em;
|
|
2912
|
-
color: #9ca3af;
|
|
2913
|
-
margin-bottom: 16px;
|
|
2914
|
-
}
|
|
2915
|
-
.env-grid {
|
|
2916
|
-
display: grid;
|
|
2917
|
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
2918
|
-
gap: 12px;
|
|
2919
|
-
}
|
|
2920
|
-
.env-card {
|
|
2921
|
-
background: var(--bg-secondary);
|
|
2922
|
-
border: 1px solid var(--border-light);
|
|
2923
|
-
border-radius: 8px;
|
|
2924
|
-
padding: 14px;
|
|
2925
|
-
transition: all 0.3s ease;
|
|
2926
|
-
}
|
|
2927
|
-
.env-card:hover {
|
|
2928
|
-
transform: translateY(-3px);
|
|
2929
|
-
border-color: var(--primary-color);
|
|
2930
|
-
box-shadow: var(--shadow-lg), var(--glow-primary);
|
|
2931
|
-
background: var(--bg-card);
|
|
2932
|
-
}
|
|
2933
|
-
.env-card-header {
|
|
2934
|
-
font-size: 0.85em;
|
|
2935
|
-
color: #9ca3af;
|
|
2936
|
-
margin-bottom: 6px;
|
|
2937
|
-
text-transform: uppercase;
|
|
2938
|
-
letter-spacing: 0.5px;
|
|
2939
|
-
font-weight: 600;
|
|
2940
|
-
}
|
|
2941
|
-
.env-card-value {
|
|
2942
|
-
font-size: 1.1em;
|
|
2943
|
-
color: #f9fafb;
|
|
2944
|
-
font-weight: 700;
|
|
2945
|
-
word-break: break-word;
|
|
2946
|
-
}
|
|
3390
|
+
|
|
2947
3391
|
.suites-widget {
|
|
2948
3392
|
background: var(--bg-card);
|
|
2949
3393
|
border: 1px solid var(--border-light);
|
|
@@ -2961,17 +3405,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2961
3405
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
2962
3406
|
gap: 20px;
|
|
2963
3407
|
}
|
|
3408
|
+
/* Updated Suite Cards in Main Block */
|
|
2964
3409
|
.suite-card {
|
|
2965
|
-
|
|
2966
|
-
border: 1px solid var(--border-light);
|
|
2967
|
-
border-radius: 8px;
|
|
2968
|
-
padding: 20px;
|
|
2969
|
-
transition: all 0.2s ease;
|
|
2970
|
-
}
|
|
2971
|
-
.suite-card:hover {
|
|
2972
|
-
transform: translateY(-2px);
|
|
2973
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
2974
|
-
border-color: var(--primary-color);
|
|
3410
|
+
/* See line ~3455 for main definition */
|
|
2975
3411
|
}
|
|
2976
3412
|
.suite-name {
|
|
2977
3413
|
font-size: 1.1em;
|
|
@@ -3084,6 +3520,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3084
3520
|
.summary-card.status-skipped .value {
|
|
3085
3521
|
color: #f59e0b;
|
|
3086
3522
|
}
|
|
3523
|
+
.summary-card.flaky-status {
|
|
3524
|
+
background: rgba(0, 204, 211, 0.05);
|
|
3525
|
+
}
|
|
3526
|
+
.summary-card.flaky-status:hover {
|
|
3527
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3528
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3529
|
+
}
|
|
3530
|
+
.summary-card.flaky-status .value {
|
|
3531
|
+
color: #00ccd3;
|
|
3532
|
+
}
|
|
3087
3533
|
.summary-card:not([class*='status-']) .value {
|
|
3088
3534
|
color: #f9fafb;
|
|
3089
3535
|
}
|
|
@@ -3146,70 +3592,171 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3146
3592
|
.status-badge-small-tooltip.status-unknown {
|
|
3147
3593
|
background-color: #9ca3af;
|
|
3148
3594
|
}
|
|
3149
|
-
.suites-header {
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
.summary-badge {
|
|
3156
|
-
background-color: var(--border-light);
|
|
3157
|
-
color: var(--text-secondary);
|
|
3158
|
-
padding: 7px 14px;
|
|
3159
|
-
border-radius: 16px;
|
|
3160
|
-
font-size: 0.9em;
|
|
3595
|
+
.suites-header {
|
|
3596
|
+
flex-shrink: 0;
|
|
3597
|
+
display: flex;
|
|
3598
|
+
justify-content: space-between;
|
|
3599
|
+
align-items: center;
|
|
3600
|
+
margin-bottom: 20px;
|
|
3161
3601
|
}
|
|
3162
|
-
.
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3602
|
+
.summary-badge { background-color: var(--border-light); color: var(--text-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
3603
|
+
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
3604
|
+
.suites-widget {
|
|
3605
|
+
display: flex;
|
|
3606
|
+
flex-direction: column;
|
|
3166
3607
|
}
|
|
3167
|
-
.
|
|
3168
|
-
|
|
3169
|
-
border-left: 4px solid var(--border-light);
|
|
3170
|
-
padding: 24px;
|
|
3171
|
-
background-color: var(--bg-card);
|
|
3172
|
-
transition: all 0.15s ease;
|
|
3608
|
+
.fixed-height-widget {
|
|
3609
|
+
height: 450px;
|
|
3173
3610
|
}
|
|
3174
|
-
.
|
|
3175
|
-
|
|
3176
|
-
|
|
3611
|
+
.suites-grid-container {
|
|
3612
|
+
flex-grow: 1;
|
|
3613
|
+
overflow-y: auto;
|
|
3614
|
+
padding-right: 5px;
|
|
3177
3615
|
}
|
|
3178
|
-
|
|
3179
|
-
|
|
3616
|
+
|
|
3617
|
+
@media (max-width: 768px) {
|
|
3618
|
+
.fixed-height-widget {
|
|
3619
|
+
height: auto;
|
|
3620
|
+
max-height: 600px;
|
|
3621
|
+
}
|
|
3180
3622
|
}
|
|
3181
|
-
.suite-card
|
|
3182
|
-
background:
|
|
3623
|
+
.suite-card {
|
|
3624
|
+
background: var(--bg-card); /* Changed from #ffffff */
|
|
3625
|
+
border: 1px solid var(--border-medium); /* Changed from border-light for better contrast */
|
|
3626
|
+
border-radius: 16px;
|
|
3627
|
+
padding: 24px;
|
|
3628
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); /* Darker shadow */
|
|
3629
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3630
|
+
display: flex;
|
|
3631
|
+
flex-direction: column;
|
|
3632
|
+
height: 100%;
|
|
3633
|
+
position: relative;
|
|
3634
|
+
overflow: hidden;
|
|
3183
3635
|
}
|
|
3184
|
-
.suite-card
|
|
3185
|
-
|
|
3636
|
+
.suite-card:hover {
|
|
3637
|
+
transform: translateY(-4px);
|
|
3638
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
|
|
3639
|
+
background: var(--bg-card-hover);
|
|
3640
|
+
border-color: var(--primary-dark);
|
|
3186
3641
|
}
|
|
3187
|
-
.suite-card
|
|
3188
|
-
|
|
3642
|
+
.suite-card::before {
|
|
3643
|
+
content: '';
|
|
3644
|
+
position: absolute;
|
|
3645
|
+
top: 0;
|
|
3646
|
+
left: 0;
|
|
3647
|
+
width: 100%;
|
|
3648
|
+
height: 4px;
|
|
3649
|
+
background: var(--neutral-200);
|
|
3650
|
+
opacity: 0.8;
|
|
3651
|
+
transition: background 0.3s ease;
|
|
3652
|
+
}
|
|
3653
|
+
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3654
|
+
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3655
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3656
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3657
|
+
|
|
3658
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3659
|
+
|
|
3660
|
+
/* Outcome Badge */
|
|
3661
|
+
.outcome-badge {
|
|
3662
|
+
background-color: var(--secondary-color);
|
|
3663
|
+
color: #000;
|
|
3664
|
+
padding: 2px 8px;
|
|
3665
|
+
border-radius: 4px;
|
|
3666
|
+
font-size: 0.75em;
|
|
3667
|
+
font-weight: 700;
|
|
3668
|
+
text-transform: uppercase;
|
|
3669
|
+
margin-right: 8px;
|
|
3670
|
+
letter-spacing: 0.5px;
|
|
3189
3671
|
}
|
|
3190
|
-
.
|
|
3191
|
-
|
|
3672
|
+
.outcome-badge.flaky {
|
|
3673
|
+
background-color: #00ccd3;
|
|
3674
|
+
color: #fff;
|
|
3192
3675
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3676
|
+
|
|
3677
|
+
.suite-card-header {
|
|
3678
|
+
display: flex;
|
|
3679
|
+
justify-content: space-between;
|
|
3680
|
+
align-items: flex-start;
|
|
3681
|
+
margin-bottom: 16px;
|
|
3195
3682
|
}
|
|
3196
|
-
.suite-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3683
|
+
.suite-name {
|
|
3684
|
+
font-size: 1.15em;
|
|
3685
|
+
font-weight: 700;
|
|
3686
|
+
color: var(--text-primary);
|
|
3687
|
+
line-height: 1.4;
|
|
3688
|
+
display: -webkit-box;
|
|
3689
|
+
-webkit-line-clamp: 2;
|
|
3690
|
+
-webkit-box-orient: vertical;
|
|
3691
|
+
overflow: hidden;
|
|
3692
|
+
margin-right: 12px;
|
|
3201
3693
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3694
|
+
.status-indicator-dot {
|
|
3695
|
+
width: 10px;
|
|
3696
|
+
height: 10px;
|
|
3697
|
+
border-radius: 50%;
|
|
3698
|
+
flex-shrink: 0;
|
|
3699
|
+
margin-top: 6px;
|
|
3208
3700
|
}
|
|
3701
|
+
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3702
|
+
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3703
|
+
.status-indicator-dot.status-skipped { background-color: var(--warning-color); box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15); }
|
|
3704
|
+
|
|
3209
3705
|
.browser-tag {
|
|
3706
|
+
font-size: 0.8em;
|
|
3707
|
+
font-weight: 600;
|
|
3708
|
+
background: var(--bg-secondary);
|
|
3709
|
+
color: var(--text-secondary);
|
|
3710
|
+
padding: 4px 10px;
|
|
3711
|
+
border-radius: 20px;
|
|
3712
|
+
border: 1px solid var(--border-light);
|
|
3713
|
+
display: inline-flex;
|
|
3714
|
+
align-items: center;
|
|
3715
|
+
gap: 6px;
|
|
3716
|
+
margin-bottom: 20px;
|
|
3717
|
+
align-self: flex-start;
|
|
3718
|
+
box-shadow: none;
|
|
3719
|
+
text-shadow: none;
|
|
3720
|
+
}
|
|
3721
|
+
.browser-tag:hover {
|
|
3722
|
+
/* Remove hover effect from previous */
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
.suite-card-body {
|
|
3726
|
+
margin-top: auto;
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
.test-count-label {
|
|
3210
3730
|
font-size: 0.85em;
|
|
3211
3731
|
font-weight: 600;
|
|
3212
|
-
|
|
3732
|
+
color: var(--text-tertiary);
|
|
3733
|
+
text-transform: uppercase;
|
|
3734
|
+
letter-spacing: 0.05em;
|
|
3735
|
+
margin-bottom: 8px;
|
|
3736
|
+
display: block;
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
.suite-stats {
|
|
3740
|
+
display: flex;
|
|
3741
|
+
gap: 8px;
|
|
3742
|
+
background: var(--bg-secondary);
|
|
3743
|
+
padding: 10px 14px;
|
|
3744
|
+
border-radius: 10px;
|
|
3745
|
+
justify-content: space-between;
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
.stat-pill {
|
|
3749
|
+
display: flex;
|
|
3750
|
+
align-items: center;
|
|
3751
|
+
gap: 6px;
|
|
3752
|
+
font-size: 0.9em;
|
|
3753
|
+
font-weight: 600;
|
|
3754
|
+
}
|
|
3755
|
+
.stat-pill svg { width: 14px; height: 14px; }
|
|
3756
|
+
.stat-pill.passed { color: var(--success-dark); }
|
|
3757
|
+
.stat-pill.failed { color: var(--danger-dark); }
|
|
3758
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3759
|
+
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3213
3760
|
color: #93c5fd;
|
|
3214
3761
|
padding: 6px 12px;
|
|
3215
3762
|
border-radius: var(--radius-sm);
|
|
@@ -3403,6 +3950,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3403
3950
|
.status-badge.status-skipped {
|
|
3404
3951
|
background: var(--warning-color);
|
|
3405
3952
|
}
|
|
3953
|
+
.status-badge.status-flaky {
|
|
3954
|
+
background-color: #00ccd3;
|
|
3955
|
+
color: #fff;
|
|
3956
|
+
}
|
|
3406
3957
|
.status-badge.status-unknown {
|
|
3407
3958
|
background: var(--dark-gray-color);
|
|
3408
3959
|
}
|
|
@@ -3458,6 +4009,65 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3458
4009
|
border-color: rgba(148, 163, 184, 0.25);
|
|
3459
4010
|
}
|
|
3460
4011
|
|
|
4012
|
+
/* --- RETRY COUNT BADGE --- */
|
|
4013
|
+
.retry-badge {
|
|
4014
|
+
display: inline-flex;
|
|
4015
|
+
align-items: center;
|
|
4016
|
+
padding: 5px 12px;
|
|
4017
|
+
border-radius: 12px;
|
|
4018
|
+
font-size: 0.75rem;
|
|
4019
|
+
font-weight: 600;
|
|
4020
|
+
background: rgba(147, 51, 234, 0.15);
|
|
4021
|
+
color: #a855f7;
|
|
4022
|
+
border: 1px solid rgba(147, 51, 234, 0.3);
|
|
4023
|
+
margin-left: 8px;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
/* --- RETRY TABS --- */
|
|
4027
|
+
.retry-tabs-container {
|
|
4028
|
+
margin-top: 16px;
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
.retry-tabs-header {
|
|
4032
|
+
display: flex;
|
|
4033
|
+
gap: 8px;
|
|
4034
|
+
border-bottom: 2px solid var(--border-medium);
|
|
4035
|
+
margin-bottom: 20px;
|
|
4036
|
+
flex-wrap: wrap;
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
.retry-tab {
|
|
4040
|
+
padding: 10px 20px;
|
|
4041
|
+
background: transparent;
|
|
4042
|
+
border: none;
|
|
4043
|
+
border-bottom: 3px solid transparent;
|
|
4044
|
+
cursor: pointer;
|
|
4045
|
+
font-size: 0.95rem;
|
|
4046
|
+
font-weight: 600;
|
|
4047
|
+
color: var(--text-color-secondary);
|
|
4048
|
+
transition: all 0.2s ease;
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
.retry-tab:hover {
|
|
4052
|
+
color: var(--primary-color);
|
|
4053
|
+
background: rgba(147, 51, 234, 0.05);
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
.retry-tab.active {
|
|
4057
|
+
color: #a855f7;
|
|
4058
|
+
border-bottom-color: #a855f7;
|
|
4059
|
+
background: rgba(147, 51, 234, 0.1);
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
.retry-tab-content {
|
|
4063
|
+
animation: fadeIn 0.3s ease-in;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
@keyframes fadeIn {
|
|
4067
|
+
from { opacity: 0; }
|
|
4068
|
+
to { opacity: 1; }
|
|
4069
|
+
}
|
|
4070
|
+
|
|
3461
4071
|
.tag {
|
|
3462
4072
|
display: inline-flex;
|
|
3463
4073
|
align-items: center;
|
|
@@ -3507,7 +4117,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3507
4117
|
background-color: rgba(248,113,113,0.08);
|
|
3508
4118
|
border: 1px solid rgba(248,113,113,0.25);
|
|
3509
4119
|
border-left: 4px solid var(--danger-color);
|
|
3510
|
-
border-radius: 4px;
|
|
4120
|
+
border-radius: 4px;
|
|
4121
|
+
display: flex;
|
|
4122
|
+
flex-direction: column;
|
|
3511
4123
|
}
|
|
3512
4124
|
.test-error-summary h4 {
|
|
3513
4125
|
color: #ef4444;
|
|
@@ -3758,6 +4370,40 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3758
4370
|
font-size: 0.9em;
|
|
3759
4371
|
border: 1px solid var(--border-light);
|
|
3760
4372
|
}
|
|
4373
|
+
.failed-step-highlight {
|
|
4374
|
+
border-left: 4px solid var(--danger-color) !important;
|
|
4375
|
+
background-color: rgba(244,67,54,0.03);
|
|
4376
|
+
}
|
|
4377
|
+
.failed-step-highlight .step-header {
|
|
4378
|
+
background-color: rgba(244,67,54,0.05);
|
|
4379
|
+
border-color: rgba(244,67,54,0.3);
|
|
4380
|
+
}
|
|
4381
|
+
.failed-step-marker {
|
|
4382
|
+
display: inline-block;
|
|
4383
|
+
margin-left: 10px;
|
|
4384
|
+
padding: 2px 8px;
|
|
4385
|
+
background-color: var(--danger-color);
|
|
4386
|
+
color: white;
|
|
4387
|
+
border-radius: 4px;
|
|
4388
|
+
font-size: 0.85em;
|
|
4389
|
+
font-weight: 600;
|
|
4390
|
+
}
|
|
4391
|
+
.code-snippet-section {
|
|
4392
|
+
margin: 12px 0;
|
|
4393
|
+
}
|
|
4394
|
+
.code-snippet {
|
|
4395
|
+
background-color: #f8f9fa;
|
|
4396
|
+
border: 1px solid #e1e4e8;
|
|
4397
|
+
border-radius: 6px;
|
|
4398
|
+
padding: 12px;
|
|
4399
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
4400
|
+
font-size: 0.9em;
|
|
4401
|
+
line-height: 1.5;
|
|
4402
|
+
overflow-x: auto;
|
|
4403
|
+
color: #24292e;
|
|
4404
|
+
margin: 0;
|
|
4405
|
+
white-space: pre;
|
|
4406
|
+
}
|
|
3761
4407
|
.nested-steps {
|
|
3762
4408
|
margin-top: 12px;
|
|
3763
4409
|
}
|
|
@@ -3941,6 +4587,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3941
4587
|
.status-badge-small.status-skipped {
|
|
3942
4588
|
background-color: #f59e0b;
|
|
3943
4589
|
}
|
|
4590
|
+
.status-badge-small.status-flaky {
|
|
4591
|
+
background-color: #00ccd3;
|
|
4592
|
+
color: #fff;
|
|
4593
|
+
}
|
|
3944
4594
|
.status-badge-small.status-unknown {
|
|
3945
4595
|
background-color: var(--dark-gray-color);
|
|
3946
4596
|
}
|
|
@@ -5166,8 +5816,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5166
5816
|
color: #f9fafb;
|
|
5167
5817
|
text-transform: capitalize;
|
|
5168
5818
|
font-size: 1.05em;
|
|
5819
|
+
white-space: nowrap;
|
|
5820
|
+
overflow: hidden;
|
|
5821
|
+
text-overflow: ellipsis;
|
|
5822
|
+
flex: 1;
|
|
5823
|
+
min-width: 0;
|
|
5824
|
+
margin-right: 8px;
|
|
5169
5825
|
}
|
|
5170
5826
|
.browser-stats {
|
|
5827
|
+
color: #9ca3af;
|
|
5828
|
+
white-space: nowrap;
|
|
5829
|
+
flex-shrink: 0;
|
|
5171
5830
|
color: #9ca3af;
|
|
5172
5831
|
font-weight: 700;
|
|
5173
5832
|
font-size: 0.95em;
|
|
@@ -5598,7 +6257,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5598
6257
|
<div class="container">
|
|
5599
6258
|
<header class="header">
|
|
5600
6259
|
<div class="header-title">
|
|
5601
|
-
<img id="report-logo" src
|
|
6260
|
+
<img id="report-logo" src=${logo} alt="Report Logo">
|
|
5602
6261
|
<h1>Pulse Static Report</h1>
|
|
5603
6262
|
</div>
|
|
5604
6263
|
<div class="run-info">
|
|
@@ -5612,6 +6271,17 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5612
6271
|
</div>
|
|
5613
6272
|
</div>
|
|
5614
6273
|
</header>
|
|
6274
|
+
${
|
|
6275
|
+
reportData.metadata?.reportDescription
|
|
6276
|
+
? `<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);">
|
|
6277
|
+
<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>
|
|
6278
|
+
<div style="flex: 1; min-width: 0;">
|
|
6279
|
+
<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>
|
|
6280
|
+
<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>
|
|
6281
|
+
</div>
|
|
6282
|
+
</div>`
|
|
6283
|
+
: ""
|
|
6284
|
+
}
|
|
5615
6285
|
<div class="tabs">
|
|
5616
6286
|
<button class="tab-button active" data-tab="dashboard">Dashboard</button>
|
|
5617
6287
|
<button class="tab-button" data-tab="test-runs">Test Run Summary</button>
|
|
@@ -5632,31 +6302,33 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5632
6302
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
5633
6303
|
runSummary.skipped || 0
|
|
5634
6304
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
5635
|
-
<div class="summary-card"><h3>
|
|
6305
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
6306
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
5636
6307
|
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
5637
6308
|
runSummary.duration,
|
|
5638
|
-
)}</div></div>
|
|
6309
|
+
)}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
|
|
5639
6310
|
<div class="summary-card">
|
|
5640
|
-
<h3
|
|
6311
|
+
<h3>Total Retry Count</h3>
|
|
5641
6312
|
<div class="value">${totalRetried}</div>
|
|
6313
|
+
<div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
|
|
5642
6314
|
</div>
|
|
5643
6315
|
<div class="summary-card">
|
|
5644
6316
|
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
5645
6317
|
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
5646
6318
|
${browserBreakdown
|
|
5647
|
-
.slice(0,
|
|
6319
|
+
.slice(0, 3)
|
|
5648
6320
|
.map(
|
|
5649
6321
|
(b) =>
|
|
5650
6322
|
`<div class="browser-item">
|
|
5651
|
-
<span class="browser-name">${sanitizeHTML(b.browser)}</span>
|
|
6323
|
+
<span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
|
|
5652
6324
|
<span class="browser-stats">${b.percentage}% (${b.count})</span>
|
|
5653
6325
|
</div>`,
|
|
5654
6326
|
)
|
|
5655
6327
|
.join("")}
|
|
5656
6328
|
${
|
|
5657
|
-
browserBreakdown.length >
|
|
6329
|
+
browserBreakdown.length > 3
|
|
5658
6330
|
? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid var(--border-light); margin-top: 8px; padding-top: 8px;">
|
|
5659
|
-
<span>+${browserBreakdown.length -
|
|
6331
|
+
<span>+${browserBreakdown.length - 3} more browsers</span>
|
|
5660
6332
|
</div>`
|
|
5661
6333
|
: ""
|
|
5662
6334
|
}
|
|
@@ -5669,17 +6341,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5669
6341
|
[
|
|
5670
6342
|
{ label: "Passed", value: runSummary.passed },
|
|
5671
6343
|
{ label: "Failed", value: runSummary.failed },
|
|
6344
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
5672
6345
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
5673
6346
|
],
|
|
5674
6347
|
400,
|
|
5675
6348
|
390,
|
|
5676
6349
|
)}
|
|
5677
|
-
${
|
|
5678
|
-
runSummary.environment &&
|
|
5679
|
-
Object.keys(runSummary.environment).length > 0
|
|
5680
|
-
? generateEnvironmentDashboard(runSummary.environment)
|
|
5681
|
-
: '<div class="no-data">Environment data not available.</div>'
|
|
5682
|
-
}
|
|
6350
|
+
${generateEnvironmentSection(runSummary.environment)}
|
|
5683
6351
|
</div>
|
|
5684
6352
|
<div style="display: flex; flex-direction: column; gap: 28px;">
|
|
5685
6353
|
${generateSuitesWidget(suitesData)}
|
|
@@ -5690,7 +6358,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5690
6358
|
<div id="test-runs" class="tab-content">
|
|
5691
6359
|
<div class="filters" style="border-color: black; border-style: groove;">
|
|
5692
6360
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
5693
|
-
<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>
|
|
6361
|
+
<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>
|
|
5694
6362
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
5695
6363
|
new Set(
|
|
5696
6364
|
(results || []).map((test) => test.browser || "unknown"),
|
|
@@ -5751,7 +6419,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5751
6419
|
${generateWorkerDistributionChart(results)}
|
|
5752
6420
|
</div>
|
|
5753
6421
|
</div>
|
|
5754
|
-
<div class="trend-chart test-history-trend-section" style="border-bottom: none;">
|
|
6422
|
+
<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;">
|
|
5755
6423
|
<h3 class="chart-title-header">Individual Test History</h3>
|
|
5756
6424
|
</div>
|
|
5757
6425
|
${
|
|
@@ -5768,6 +6436,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5768
6436
|
<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;">
|
|
5769
6437
|
<div style="display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
|
|
5770
6438
|
<span>Created by</span>
|
|
6439
|
+
<img id="report-logo" src=${logo} alt="Pulse Report Logo" style="height: 20px;">
|
|
5771
6440
|
<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>
|
|
5772
6441
|
</div>
|
|
5773
6442
|
<div style="margin-top: 0.5rem; font-size: 0.75rem; color: #666;">Crafted with precision</div>
|
|
@@ -5781,6 +6450,33 @@ function generateHTML(reportData, trendData = null) {
|
|
|
5781
6450
|
return (ms / 1000).toFixed(1) + "s";
|
|
5782
6451
|
}
|
|
5783
6452
|
}
|
|
6453
|
+
function switchRetryTab(event, tabId) {
|
|
6454
|
+
// Find container
|
|
6455
|
+
const container = event.target.closest('.retry-tabs-container');
|
|
6456
|
+
if (!container) return;
|
|
6457
|
+
|
|
6458
|
+
// Update tab buttons
|
|
6459
|
+
const buttons = container.querySelectorAll('.retry-tab');
|
|
6460
|
+
buttons.forEach(btn => btn.classList.remove('active'));
|
|
6461
|
+
const activeBtn = event.target.closest('.retry-tab') || event.target;
|
|
6462
|
+
activeBtn.classList.add('active');
|
|
6463
|
+
|
|
6464
|
+
// Update content
|
|
6465
|
+
const contents = container.querySelectorAll('.retry-tab-content');
|
|
6466
|
+
contents.forEach(content => {
|
|
6467
|
+
content.style.display = 'none';
|
|
6468
|
+
content.classList.remove('active');
|
|
6469
|
+
});
|
|
6470
|
+
|
|
6471
|
+
const activeContent = document.getElementById(tabId);
|
|
6472
|
+
if (activeContent) {
|
|
6473
|
+
activeContent.style.display = 'block';
|
|
6474
|
+
activeContent.classList.add('active');
|
|
6475
|
+
} else {
|
|
6476
|
+
console.error('Failed to find retry tab content for id:', tabId);
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
|
|
5784
6480
|
function copyLogContent(elementId, button) {
|
|
5785
6481
|
const logElement = document.getElementById(elementId);
|
|
5786
6482
|
if (!logElement) {
|
|
@@ -6437,6 +7133,8 @@ async function runScript(scriptPath, args = []) {
|
|
|
6437
7133
|
* prepares the data, and then generates and writes the final HTML report file.
|
|
6438
7134
|
*/
|
|
6439
7135
|
async function main() {
|
|
7136
|
+
await animate();
|
|
7137
|
+
|
|
6440
7138
|
const __filename = fileURLToPath(import.meta.url);
|
|
6441
7139
|
const __dirname = path.dirname(__filename);
|
|
6442
7140
|
|
|
@@ -6455,8 +7153,12 @@ async function main() {
|
|
|
6455
7153
|
"generate-trend.mjs", // Keeping the filename as per your request
|
|
6456
7154
|
);
|
|
6457
7155
|
|
|
6458
|
-
const
|
|
6459
|
-
const
|
|
7156
|
+
const config = await getReporterConfig(customOutputDir);
|
|
7157
|
+
const outputDir = config.outputDir;
|
|
7158
|
+
const outputFile = config.outputFile;
|
|
7159
|
+
|
|
7160
|
+
await mergeSequentialReportsIfNeeded(outputDir);
|
|
7161
|
+
const reportJsonPath = path.resolve(outputDir, outputFile); // Current run's main JSON
|
|
6460
7162
|
const reportHtmlPath = path.resolve(outputDir, DEFAULT_HTML_FILE);
|
|
6461
7163
|
|
|
6462
7164
|
const historyDir = path.join(outputDir, "history"); // Directory for historical JSON files
|
|
@@ -6497,6 +7199,32 @@ async function main() {
|
|
|
6497
7199
|
try {
|
|
6498
7200
|
const jsonData = await fs.readFile(reportJsonPath, "utf-8");
|
|
6499
7201
|
currentRunReportData = JSON.parse(jsonData);
|
|
7202
|
+
|
|
7203
|
+
// Process custom logo if provided in metadata
|
|
7204
|
+
if (currentRunReportData.metadata?.logo) {
|
|
7205
|
+
const logoPath = path.resolve(
|
|
7206
|
+
process.cwd(),
|
|
7207
|
+
currentRunReportData.metadata.logo,
|
|
7208
|
+
);
|
|
7209
|
+
try {
|
|
7210
|
+
const ext = path.extname(logoPath).toLowerCase();
|
|
7211
|
+
let mimeType = "image/png";
|
|
7212
|
+
if (ext === ".svg") mimeType = "image/svg+xml";
|
|
7213
|
+
else if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
|
|
7214
|
+
else if (ext === ".gif") mimeType = "image/gif";
|
|
7215
|
+
else if (ext === ".webp") mimeType = "image/webp";
|
|
7216
|
+
|
|
7217
|
+
const logoData = await fs.readFile(logoPath, "base64");
|
|
7218
|
+
logo = `data:${mimeType};base64,${logoData}`;
|
|
7219
|
+
} catch (error) {
|
|
7220
|
+
console.warn(
|
|
7221
|
+
chalk.yellow(
|
|
7222
|
+
`Warning: Could not read custom logo file at ${logoPath}. Falling back to default logo. Error: ${error.message}`,
|
|
7223
|
+
),
|
|
7224
|
+
);
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
|
|
6500
7228
|
if (
|
|
6501
7229
|
!currentRunReportData ||
|
|
6502
7230
|
typeof currentRunReportData !== "object" ||
|
|
@@ -6605,6 +7333,7 @@ async function main() {
|
|
|
6605
7333
|
passed: histRunReport.run.passed,
|
|
6606
7334
|
failed: histRunReport.run.failed,
|
|
6607
7335
|
skipped: histRunReport.run.skipped || 0,
|
|
7336
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
6608
7337
|
});
|
|
6609
7338
|
|
|
6610
7339
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|
|
@@ -6613,7 +7342,7 @@ async function main() {
|
|
|
6613
7342
|
(test) => ({
|
|
6614
7343
|
testName: test.name,
|
|
6615
7344
|
duration: test.duration,
|
|
6616
|
-
status: test.status,
|
|
7345
|
+
status: test.final_status || test.status,
|
|
6617
7346
|
timestamp: new Date(test.startTime),
|
|
6618
7347
|
}),
|
|
6619
7348
|
);
|
|
@@ -6631,7 +7360,7 @@ async function main() {
|
|
|
6631
7360
|
await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
|
|
6632
7361
|
console.log(
|
|
6633
7362
|
chalk.green.bold(
|
|
6634
|
-
|
|
7363
|
+
`Pulse report generated successfully at: ${reportHtmlPath}`,
|
|
6635
7364
|
),
|
|
6636
7365
|
);
|
|
6637
7366
|
console.log(chalk.gray(`(You can open this file in your browser)`));
|