@arghajit/playwright-pulse-report 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -88
- package/dist/reporter/playwright-pulse-reporter.d.ts +1 -0
- package/dist/reporter/playwright-pulse-reporter.js +85 -25
- package/dist/types/index.d.ts +8 -2
- package/dist/utils/compression-utils.d.ts +19 -0
- package/dist/utils/compression-utils.js +112 -0
- package/package.json +12 -6
- package/scripts/generate-email-report.mjs +42 -12
- package/scripts/generate-report.mjs +2367 -661
- package/scripts/generate-static-report.mjs +4566 -1065
- package/scripts/generate-trend.mjs +0 -0
- package/scripts/merge-pulse-report.js +160 -36
- package/scripts/sendReport.mjs +153 -65
- 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/tsconfig.reporter.tsbuildinfo +0 -1
- package/dist/reporter/types/index.d.ts +0 -52
- package/dist/reporter/types/index.js +0 -2
|
@@ -6,6 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { fork } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { getOutputDir } from "./config-reader.mjs";
|
|
9
|
+
import { animate } from "./terminal-logo.mjs";
|
|
9
10
|
|
|
10
11
|
// Use dynamic import for chalk as it's ESM only
|
|
11
12
|
let chalk;
|
|
@@ -98,12 +99,12 @@ export function ansiToHtml(text) {
|
|
|
98
99
|
if (codes[code]) {
|
|
99
100
|
if (code === "39") {
|
|
100
101
|
currentStylesArray = currentStylesArray.filter(
|
|
101
|
-
(s) => !s.startsWith("color:")
|
|
102
|
+
(s) => !s.startsWith("color:"),
|
|
102
103
|
);
|
|
103
104
|
currentStylesArray.push("color:inherit");
|
|
104
105
|
} else if (code === "49") {
|
|
105
106
|
currentStylesArray = currentStylesArray.filter(
|
|
106
|
-
(s) => !s.startsWith("background-color:")
|
|
107
|
+
(s) => !s.startsWith("background-color:"),
|
|
107
108
|
);
|
|
108
109
|
currentStylesArray.push("background-color:inherit");
|
|
109
110
|
} else {
|
|
@@ -114,10 +115,10 @@ export function ansiToHtml(text) {
|
|
|
114
115
|
const type = parts[0] === "38" ? "color" : "background-color";
|
|
115
116
|
if (parts.length === 5) {
|
|
116
117
|
currentStylesArray = currentStylesArray.filter(
|
|
117
|
-
(s) => !s.startsWith(type + ":")
|
|
118
|
+
(s) => !s.startsWith(type + ":"),
|
|
118
119
|
);
|
|
119
120
|
currentStylesArray.push(
|
|
120
|
-
`${type}:rgb(${parts[2]},${parts[3]},${parts[4]})
|
|
121
|
+
`${type}:rgb(${parts[2]},${parts[3]},${parts[4]})`,
|
|
121
122
|
);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
@@ -175,7 +176,7 @@ function convertPlaywrightErrorToHTML(str) {
|
|
|
175
176
|
if (!str) return "";
|
|
176
177
|
return str
|
|
177
178
|
.replace(/^(\s+)/gm, (match) =>
|
|
178
|
-
match.replace(/ /g, " ").replace(/\t/g, " ")
|
|
179
|
+
match.replace(/ /g, " ").replace(/\t/g, " "),
|
|
179
180
|
)
|
|
180
181
|
.replace(/<red>/g, '<span style="color: red;">')
|
|
181
182
|
.replace(/<green>/g, '<span style="color: green;">')
|
|
@@ -265,7 +266,7 @@ function generateTestTrendsChart(trendData) {
|
|
|
265
266
|
.substring(2, 7)}`;
|
|
266
267
|
const renderFunctionName = `renderTestTrendsChart_${chartId.replace(
|
|
267
268
|
/-/g,
|
|
268
|
-
"_"
|
|
269
|
+
"_",
|
|
269
270
|
)}`;
|
|
270
271
|
const runs = trendData.overall;
|
|
271
272
|
|
|
@@ -294,6 +295,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
294
295
|
color: "var(--warning-color)",
|
|
295
296
|
marker: { symbol: "circle" },
|
|
296
297
|
},
|
|
298
|
+
{
|
|
299
|
+
name: "Flaky",
|
|
300
|
+
data: runs.map((r) => r.flaky || 0),
|
|
301
|
+
color: "#00ccd3",
|
|
302
|
+
marker: { symbol: "circle" },
|
|
303
|
+
},
|
|
297
304
|
];
|
|
298
305
|
const runsForTooltip = runs.map((r) => ({
|
|
299
306
|
runId: r.runId,
|
|
@@ -360,7 +367,7 @@ function generateDurationTrendChart(trendData) {
|
|
|
360
367
|
.substring(2, 7)}`;
|
|
361
368
|
const renderFunctionName = `renderDurationTrendChart_${chartId.replace(
|
|
362
369
|
/-/g,
|
|
363
|
-
"_"
|
|
370
|
+
"_",
|
|
364
371
|
)}`;
|
|
365
372
|
const runs = trendData.overall;
|
|
366
373
|
|
|
@@ -455,7 +462,7 @@ function generateTestHistoryChart(history) {
|
|
|
455
462
|
if (!history || history.length === 0)
|
|
456
463
|
return '<div class="no-data-chart">No data for chart</div>';
|
|
457
464
|
const validHistory = history.filter(
|
|
458
|
-
(h) => h && typeof h.duration === "number" && h.duration >= 0
|
|
465
|
+
(h) => h && typeof h.duration === "number" && h.duration >= 0,
|
|
459
466
|
);
|
|
460
467
|
if (validHistory.length === 0)
|
|
461
468
|
return '<div class="no-data-chart">No valid data for chart</div>';
|
|
@@ -465,7 +472,7 @@ function generateTestHistoryChart(history) {
|
|
|
465
472
|
.substring(2, 7)}`;
|
|
466
473
|
const renderFunctionName = `renderTestHistoryChart_${chartId.replace(
|
|
467
474
|
/-/g,
|
|
468
|
-
"_"
|
|
475
|
+
"_",
|
|
469
476
|
)}`;
|
|
470
477
|
|
|
471
478
|
const seriesDataPoints = validHistory.map((run) => {
|
|
@@ -480,6 +487,9 @@ function generateTestHistoryChart(history) {
|
|
|
480
487
|
case "skipped":
|
|
481
488
|
color = "var(--warning-color)";
|
|
482
489
|
break;
|
|
490
|
+
case "flaky":
|
|
491
|
+
color = "var(--neutral-500)";
|
|
492
|
+
break;
|
|
483
493
|
default:
|
|
484
494
|
color = "var(--dark-gray-color)";
|
|
485
495
|
}
|
|
@@ -499,12 +509,12 @@ function generateTestHistoryChart(history) {
|
|
|
499
509
|
const accentColorRGB = "103, 58, 183"; // Assuming var(--accent-color) is Deep Purple #673ab7
|
|
500
510
|
|
|
501
511
|
const categoriesString = JSON.stringify(
|
|
502
|
-
validHistory.map((_, i) => `R${i + 1}`)
|
|
512
|
+
validHistory.map((_, i) => `R${i + 1}`),
|
|
503
513
|
);
|
|
504
514
|
const seriesDataPointsString = JSON.stringify(seriesDataPoints);
|
|
505
515
|
|
|
506
516
|
return `
|
|
507
|
-
<div id="${chartId}" style="width: 320px; height: 100px;" class="lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
517
|
+
<div id="${chartId}" style="width: 100%; max-width: 320px; height: 100px;" class="lazy-load-chart" data-render-function-name="${renderFunctionName}">
|
|
508
518
|
<div class="no-data-chart">Loading History...</div>
|
|
509
519
|
</div>
|
|
510
520
|
<script>
|
|
@@ -568,7 +578,7 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
568
578
|
}
|
|
569
579
|
const passedEntry = data.find((d) => d.label === "Passed");
|
|
570
580
|
const passedPercentage = Math.round(
|
|
571
|
-
((passedEntry ? passedEntry.value : 0) / total) * 100
|
|
581
|
+
((passedEntry ? passedEntry.value : 0) / total) * 100,
|
|
572
582
|
);
|
|
573
583
|
|
|
574
584
|
const chartId = `pieChart-${Date.now()}-${Math.random()
|
|
@@ -589,6 +599,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
589
599
|
case "Failed":
|
|
590
600
|
color = "var(--danger-color)";
|
|
591
601
|
break;
|
|
602
|
+
case "Flaky":
|
|
603
|
+
color = "#00ccd3";
|
|
604
|
+
break;
|
|
592
605
|
case "Skipped":
|
|
593
606
|
color = "var(--warning-color)";
|
|
594
607
|
break;
|
|
@@ -614,13 +627,11 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
614
627
|
{
|
|
615
628
|
chart: {
|
|
616
629
|
type: 'pie',
|
|
617
|
-
width:
|
|
618
|
-
height: ${
|
|
619
|
-
chartHeight - 40
|
|
620
|
-
}, // Adjusted height to make space for legend if chartHeight is for the whole wrapper
|
|
630
|
+
width: null,
|
|
631
|
+
height: ${chartHeight - 40},
|
|
621
632
|
backgroundColor: 'transparent',
|
|
622
633
|
plotShadow: false,
|
|
623
|
-
spacingBottom: 40
|
|
634
|
+
spacingBottom: 40
|
|
624
635
|
},
|
|
625
636
|
title: {
|
|
626
637
|
text: '${passedPercentage}%',
|
|
@@ -670,8 +681,8 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
670
681
|
<div class="pie-chart-wrapper" style="align-items: center; max-height: 450px">
|
|
671
682
|
<div style="display: flex; align-items: start; width: 100%;"><h3>Test Distribution</h3></div>
|
|
672
683
|
<div id="${chartId}" style="width: ${chartWidth}px; height: ${
|
|
673
|
-
|
|
674
|
-
|
|
684
|
+
chartHeight - 40
|
|
685
|
+
}px;"></div>
|
|
675
686
|
<script>
|
|
676
687
|
document.addEventListener('DOMContentLoaded', function() {
|
|
677
688
|
if (typeof Highcharts !== 'undefined') {
|
|
@@ -690,24 +701,175 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
690
701
|
</div>
|
|
691
702
|
`;
|
|
692
703
|
}
|
|
693
|
-
function
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
+
function generateEnvironmentSection(environmentData) {
|
|
705
|
+
if (!environmentData) {
|
|
706
|
+
return '<div class="no-data">Environment data not available.</div>';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (Array.isArray(environmentData)) {
|
|
710
|
+
return `
|
|
711
|
+
<div class="sharded-env-section">
|
|
712
|
+
<div class="sharded-env-header">
|
|
713
|
+
<div class="sharded-env-title-row">
|
|
714
|
+
<div>
|
|
715
|
+
<div class="sharded-env-title">
|
|
716
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
717
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
718
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
719
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
720
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
721
|
+
</svg>
|
|
722
|
+
System Information
|
|
723
|
+
</div>
|
|
724
|
+
<div class="sharded-env-subtitle">Test execution environment details - ${environmentData.length} shard${environmentData.length > 1 ? "s" : ""}</div>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="env-icon-badge">
|
|
727
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
728
|
+
<path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
|
|
729
|
+
</svg>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="sharded-environments-container">
|
|
734
|
+
<div class="sharded-environments-wrapper">
|
|
735
|
+
${environmentData
|
|
736
|
+
.map(
|
|
737
|
+
(env, index) => `
|
|
738
|
+
<div class="env-card-wrapper">
|
|
739
|
+
<div class="env-card-badge">Shard ${index + 1}</div>
|
|
740
|
+
${generateEnvironmentDashboard(env, true)}
|
|
741
|
+
</div>
|
|
742
|
+
`,
|
|
743
|
+
)
|
|
744
|
+
.join("")}
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
<style>
|
|
749
|
+
.sharded-env-section {
|
|
750
|
+
border: 1px solid #e2e8f0;
|
|
751
|
+
border-radius: 12px;
|
|
752
|
+
background: #fafbfc;
|
|
753
|
+
overflow: hidden;
|
|
754
|
+
}
|
|
755
|
+
.sharded-env-header {
|
|
756
|
+
position: sticky;
|
|
757
|
+
top: 0;
|
|
758
|
+
z-index: 20;
|
|
759
|
+
background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
|
|
760
|
+
border-bottom: 1px solid #e2e8f0;
|
|
761
|
+
padding: 24px 24px 16px;
|
|
762
|
+
}
|
|
763
|
+
.sharded-env-title-row {
|
|
764
|
+
display: flex;
|
|
765
|
+
justify-content: space-between;
|
|
766
|
+
align-items: center;
|
|
767
|
+
}
|
|
768
|
+
.sharded-env-title {
|
|
769
|
+
display: flex;
|
|
770
|
+
align-items: center;
|
|
771
|
+
font-size: 18px;
|
|
772
|
+
font-weight: 600;
|
|
773
|
+
color: #0f172a;
|
|
774
|
+
}
|
|
775
|
+
.sharded-env-title svg {
|
|
776
|
+
width: 18px;
|
|
777
|
+
height: 18px;
|
|
778
|
+
margin-right: 8px;
|
|
779
|
+
stroke: currentColor;
|
|
780
|
+
fill: none;
|
|
781
|
+
}
|
|
782
|
+
.sharded-env-subtitle {
|
|
783
|
+
font-size: 13px;
|
|
784
|
+
color: #64748b;
|
|
785
|
+
margin-top: 4px;
|
|
786
|
+
}
|
|
787
|
+
.sharded-environments-container {
|
|
788
|
+
max-height: 520px;
|
|
789
|
+
overflow-y: auto;
|
|
790
|
+
overflow-x: hidden;
|
|
791
|
+
padding: 16px;
|
|
792
|
+
}
|
|
793
|
+
.sharded-environments-container::-webkit-scrollbar {
|
|
794
|
+
width: 8px;
|
|
795
|
+
}
|
|
796
|
+
.sharded-environments-container::-webkit-scrollbar-track {
|
|
797
|
+
background: #f1f1f1;
|
|
798
|
+
border-radius: 4px;
|
|
799
|
+
}
|
|
800
|
+
.sharded-environments-container::-webkit-scrollbar-thumb {
|
|
801
|
+
background: #cbd5e0;
|
|
802
|
+
border-radius: 4px;
|
|
803
|
+
}
|
|
804
|
+
.sharded-environments-container::-webkit-scrollbar-thumb:hover {
|
|
805
|
+
background: #a0aec0;
|
|
806
|
+
}
|
|
807
|
+
.sharded-environments-wrapper {
|
|
808
|
+
display: grid;
|
|
809
|
+
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
|
810
|
+
gap: 24px;
|
|
811
|
+
}
|
|
812
|
+
@media (max-width: 768px) {
|
|
813
|
+
.sharded-environments-wrapper {
|
|
814
|
+
grid-template-columns: 1fr;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
.env-card-wrapper {
|
|
818
|
+
position: relative;
|
|
819
|
+
}
|
|
820
|
+
.env-card-badge {
|
|
821
|
+
position: absolute;
|
|
822
|
+
top: -10px;
|
|
823
|
+
right: 16px;
|
|
824
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
825
|
+
color: white;
|
|
826
|
+
padding: 6px 14px;
|
|
827
|
+
border-radius: 20px;
|
|
828
|
+
font-size: 0.75em;
|
|
829
|
+
font-weight: 700;
|
|
830
|
+
text-transform: uppercase;
|
|
831
|
+
letter-spacing: 0.5px;
|
|
832
|
+
z-index: 10;
|
|
833
|
+
box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
|
|
834
|
+
}
|
|
835
|
+
</style>
|
|
836
|
+
`;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return generateEnvironmentDashboard(environmentData);
|
|
840
|
+
}
|
|
704
841
|
|
|
705
|
-
|
|
842
|
+
function generateEnvironmentDashboard(environment, hideHeader = false) {
|
|
843
|
+
const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
|
|
844
|
+
const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
|
|
845
|
+
const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
|
|
846
|
+
const osInfo = environment.os || "N/A";
|
|
847
|
+
const nodeInfo = environment.node || "N/A";
|
|
848
|
+
const v8Info = environment.v8 || "N/A";
|
|
849
|
+
const cwdInfo = environment.cwd || "N/A";
|
|
850
|
+
const formattedMemory = environment.memory || "N/A";
|
|
706
851
|
const runContext = process.env.CI ? "CI" : "Local Test";
|
|
707
852
|
|
|
708
853
|
return `
|
|
709
|
-
<div class="
|
|
854
|
+
<div class="env-modern-card${hideHeader ? " no-header" : ""}">
|
|
710
855
|
<style>
|
|
856
|
+
.env-modern-card {
|
|
857
|
+
background: linear-gradient(to bottom right, #ffffff 0%, #fafafa 100%);
|
|
858
|
+
border: 0;
|
|
859
|
+
border-radius: 12px;
|
|
860
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
861
|
+
margin-top: 24px;
|
|
862
|
+
transition: all 0.3s ease;
|
|
863
|
+
font-family: var(--font-family);
|
|
864
|
+
overflow: hidden;
|
|
865
|
+
}
|
|
866
|
+
.env-modern-card:hover {
|
|
867
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
868
|
+
}
|
|
869
|
+
.env-modern-card {
|
|
870
|
+
margin-bottom: 0;
|
|
871
|
+
}
|
|
872
|
+
|
|
711
873
|
.environment-dashboard-wrapper *,
|
|
712
874
|
.environment-dashboard-wrapper *::before,
|
|
713
875
|
.environment-dashboard-wrapper *::after {
|
|
@@ -715,326 +877,285 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
715
877
|
}
|
|
716
878
|
|
|
717
879
|
.environment-dashboard-wrapper {
|
|
718
|
-
--primary-color: #
|
|
719
|
-
--
|
|
720
|
-
--
|
|
721
|
-
--success-color: #28a745;
|
|
722
|
-
--success-light-color: #eaf6ec;
|
|
723
|
-
--warning-color: #ffc107;
|
|
724
|
-
--warning-light-color: #fff9e6;
|
|
725
|
-
--danger-color: #dc3545;
|
|
880
|
+
--primary-color: #6366f1;
|
|
881
|
+
--success-color: #10b981;
|
|
882
|
+
--warning-color: #f59e0b;
|
|
726
883
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
--border-light-color: #f1f3f5;
|
|
733
|
-
--icon-color: #495057;
|
|
734
|
-
--chip-background: #e9ecef;
|
|
735
|
-
--chip-text: #495057;
|
|
736
|
-
--shadow-color: rgba(0, 0, 0, 0.075);
|
|
737
|
-
|
|
738
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
739
|
-
background-color: var(--background-color);
|
|
740
|
-
border-radius: 12px;
|
|
741
|
-
box-shadow: 0 6px 12px var(--shadow-color);
|
|
742
|
-
padding: 24px;
|
|
743
|
-
color: var(--text-color);
|
|
884
|
+
background-color: white;
|
|
885
|
+
padding: 48px;
|
|
886
|
+
border-bottom: 1px solid #e2e8f0;
|
|
887
|
+
font-family: var(--font-family);
|
|
888
|
+
color: #0f172a;
|
|
744
889
|
display: grid;
|
|
745
|
-
grid-template-columns:
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
890
|
+
grid-template-columns: repeat(2, 1fr);
|
|
891
|
+
gap: 32px;
|
|
892
|
+
font-size: 15px;
|
|
893
|
+
transform: translateZ(0);
|
|
749
894
|
}
|
|
750
895
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
grid-template-rows: auto;
|
|
756
|
-
padding: 16px;
|
|
757
|
-
height: auto !important; /* Allow height to grow */
|
|
758
|
-
}
|
|
759
|
-
.env-card {
|
|
760
|
-
height: auto !important; /* Allow cards to grow based on content */
|
|
761
|
-
min-height: 200px;
|
|
762
|
-
}
|
|
896
|
+
.env-card-header {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-direction: column;
|
|
899
|
+
padding: 24px 24px 12px;
|
|
763
900
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
901
|
+
.env-modern-card.no-header .env-card-header {
|
|
902
|
+
display: none;
|
|
903
|
+
}
|
|
904
|
+
.env-modern-card.no-header {
|
|
905
|
+
margin-top: 0;
|
|
906
|
+
}
|
|
907
|
+
.env-modern-card.no-header .env-card-content {
|
|
908
|
+
padding-top: 24px;
|
|
909
|
+
}
|
|
910
|
+
.env-card-title-row {
|
|
767
911
|
display: flex;
|
|
768
912
|
justify-content: space-between;
|
|
769
913
|
align-items: center;
|
|
770
|
-
border-bottom: 1px solid var(--border-color);
|
|
771
|
-
padding-bottom: 16px;
|
|
772
|
-
margin-bottom: 8px;
|
|
773
|
-
flex-wrap: wrap; /* Allow wrapping header items */
|
|
774
|
-
gap: 10px;
|
|
775
914
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
915
|
+
.env-card-title {
|
|
916
|
+
display: flex;
|
|
917
|
+
align-items: center;
|
|
918
|
+
font-size: 16px;
|
|
779
919
|
font-weight: 600;
|
|
780
|
-
color:
|
|
781
|
-
|
|
920
|
+
color: #0f172a;
|
|
921
|
+
transition: color 0.3s;
|
|
782
922
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
font-size: 0.875rem;
|
|
786
|
-
color: var(--text-color-secondary);
|
|
787
|
-
margin-top: 4px;
|
|
923
|
+
.env-modern-card:hover .env-card-title {
|
|
924
|
+
color: #6366f1;
|
|
788
925
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
height: ${cardHeight}px;
|
|
796
|
-
display: flex;
|
|
797
|
-
flex-direction: column;
|
|
798
|
-
overflow: hidden;
|
|
926
|
+
.env-card-title svg {
|
|
927
|
+
width: 16px;
|
|
928
|
+
height: 16px;
|
|
929
|
+
margin-right: 8px;
|
|
930
|
+
stroke: currentColor;
|
|
931
|
+
fill: none;
|
|
799
932
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
933
|
+
.env-card-subtitle {
|
|
934
|
+
font-size: 12px;
|
|
935
|
+
color: #64748b;
|
|
936
|
+
margin-top: 4px;
|
|
937
|
+
}
|
|
938
|
+
.env-icon-badge {
|
|
939
|
+
width: 36px;
|
|
940
|
+
height: 36px;
|
|
941
|
+
border-radius: 50%;
|
|
942
|
+
background: linear-gradient(to bottom right, rgba(99, 102, 241, 0.1), rgba(99, 102, 241, 0.05));
|
|
806
943
|
display: flex;
|
|
807
944
|
align-items: center;
|
|
808
|
-
|
|
809
|
-
border-bottom: 1px solid var(--border-light-color);
|
|
945
|
+
justify-content: center;
|
|
810
946
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
fill: var(--icon-color);
|
|
947
|
+
.env-icon-badge svg {
|
|
948
|
+
width: 16px;
|
|
949
|
+
height: 16px;
|
|
950
|
+
stroke: #6366f1;
|
|
951
|
+
fill: none;
|
|
817
952
|
}
|
|
818
|
-
|
|
819
953
|
.env-card-content {
|
|
820
|
-
|
|
821
|
-
overflow-y: auto;
|
|
822
|
-
padding-right: 5px;
|
|
954
|
+
padding: 0 24px 24px;
|
|
823
955
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
align-items: center;
|
|
829
|
-
padding: 10px 0;
|
|
830
|
-
border-bottom: 1px solid var(--border-light-color);
|
|
831
|
-
font-size: 0.875rem;
|
|
832
|
-
flex-wrap: wrap; /* Allow details to wrap on very small screens */
|
|
833
|
-
gap: 8px;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.env-detail-row:last-child {
|
|
837
|
-
border-bottom: none;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
.env-detail-label {
|
|
841
|
-
color: var(--text-color-secondary);
|
|
842
|
-
font-weight: 500;
|
|
843
|
-
margin-right: 10px;
|
|
956
|
+
.env-items-grid {
|
|
957
|
+
display: grid;
|
|
958
|
+
grid-template-columns: repeat(2, 1fr);
|
|
959
|
+
gap: 10px;
|
|
844
960
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
text-align: right;
|
|
850
|
-
word-break: break-all;
|
|
851
|
-
margin-left: auto; /* Push to right */
|
|
961
|
+
@media (min-width: 768px) {
|
|
962
|
+
.env-items-grid {
|
|
963
|
+
grid-template-columns: repeat(4, 1fr);
|
|
964
|
+
}
|
|
852
965
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
background-color: var(--chip-background);
|
|
862
|
-
color: var(--chip-text);
|
|
966
|
+
.env-item {
|
|
967
|
+
display: flex;
|
|
968
|
+
align-items: flex-start;
|
|
969
|
+
gap: 8px;
|
|
970
|
+
padding: 8px;
|
|
971
|
+
border-radius: 8px;
|
|
972
|
+
transition: background-color 0.2s;
|
|
973
|
+
min-height: 48px;
|
|
863
974
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
background-color: var(--primary-light-color);
|
|
867
|
-
color: var(--primary-color);
|
|
975
|
+
.env-item:hover {
|
|
976
|
+
background-color: rgba(100, 116, 139, 0.05);
|
|
868
977
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
background-color: var(--success-light-color);
|
|
872
|
-
color: var(--success-color);
|
|
978
|
+
.env-item-icon {
|
|
979
|
+
flex-shrink: 0;
|
|
873
980
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
981
|
+
.env-item-icon svg {
|
|
982
|
+
width: 16px;
|
|
983
|
+
height: 16px;
|
|
984
|
+
stroke: #6366f1;
|
|
985
|
+
fill: none;
|
|
878
986
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
align-items: center;
|
|
883
|
-
gap: 6px;
|
|
987
|
+
.env-item-content {
|
|
988
|
+
flex-grow: 1;
|
|
989
|
+
min-width: 0;
|
|
884
990
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
991
|
+
.env-item-label {
|
|
992
|
+
font-size: 12px;
|
|
993
|
+
font-weight: 500;
|
|
994
|
+
color: #64748b;
|
|
995
|
+
white-space: nowrap;
|
|
996
|
+
overflow: hidden;
|
|
997
|
+
text-overflow: ellipsis;
|
|
892
998
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
999
|
+
.env-item-value {
|
|
1000
|
+
font-size: 12px;
|
|
1001
|
+
font-weight: 600;
|
|
1002
|
+
color: #0f172a;
|
|
1003
|
+
word-wrap: break-word;
|
|
1004
|
+
overflow-wrap: break-word;
|
|
1005
|
+
line-height: 1.4;
|
|
898
1006
|
}
|
|
899
1007
|
</style>
|
|
900
1008
|
|
|
901
|
-
<div class="env-
|
|
902
|
-
<div>
|
|
903
|
-
<
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
<div class="env-card-content">
|
|
915
|
-
<div class="env-detail-row">
|
|
916
|
-
<span class="env-detail-label">CPU Model</span>
|
|
917
|
-
<span class="env-detail-value">${environment.cpu.model}</span>
|
|
918
|
-
</div>
|
|
919
|
-
<div class="env-detail-row">
|
|
920
|
-
<span class="env-detail-label">CPU Cores</span>
|
|
921
|
-
<span class="env-detail-value">
|
|
922
|
-
<div class="env-cpu-cores">
|
|
923
|
-
${Array.from(
|
|
924
|
-
{ length: Math.max(0, environment.cpu.cores || 0) },
|
|
925
|
-
(_, i) =>
|
|
926
|
-
`<div class="env-core-indicator ${
|
|
927
|
-
i >=
|
|
928
|
-
(environment.cpu.cores >= 8 ? 8 : environment.cpu.cores)
|
|
929
|
-
? "inactive"
|
|
930
|
-
: ""
|
|
931
|
-
}" title="Core ${i + 1}"></div>`
|
|
932
|
-
).join("")}
|
|
933
|
-
<span>${environment.cpu.cores || "N/A"} cores</span>
|
|
934
|
-
</div>
|
|
935
|
-
</span>
|
|
1009
|
+
<div class="env-card-header">
|
|
1010
|
+
<div class="env-card-title-row">
|
|
1011
|
+
<div>
|
|
1012
|
+
<div class="env-card-title">
|
|
1013
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1014
|
+
<rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
|
|
1015
|
+
<rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
|
|
1016
|
+
<line x1="6" x2="6.01" y1="6" y2="6"></line>
|
|
1017
|
+
<line x1="6" x2="6.01" y1="18" y2="18"></line>
|
|
1018
|
+
</svg>
|
|
1019
|
+
System Information
|
|
1020
|
+
</div>
|
|
1021
|
+
<div class="env-card-subtitle">Test execution environment details</div>
|
|
936
1022
|
</div>
|
|
937
|
-
<div class="env-
|
|
938
|
-
<
|
|
939
|
-
|
|
1023
|
+
<div class="env-icon-badge">
|
|
1024
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1025
|
+
<path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
|
|
1026
|
+
</svg>
|
|
940
1027
|
</div>
|
|
941
1028
|
</div>
|
|
942
1029
|
</div>
|
|
943
1030
|
|
|
944
|
-
<div class="env-card">
|
|
945
|
-
<div class="env-
|
|
946
|
-
<
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
<
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}</span>
|
|
957
|
-
</div>
|
|
958
|
-
<div class="env-detail-row">
|
|
959
|
-
<span class="env-detail-label">OS Version</span>
|
|
960
|
-
<span class="env-detail-value">${
|
|
961
|
-
environment.os.split(" ")[1] || "N/A"
|
|
962
|
-
}</span>
|
|
963
|
-
</div>
|
|
964
|
-
<div class="env-detail-row">
|
|
965
|
-
<span class="env-detail-label">Hostname</span>
|
|
966
|
-
<span class="env-detail-value" title="${environment.host}">${
|
|
967
|
-
environment.host
|
|
968
|
-
}</span>
|
|
1031
|
+
<div class="env-card-content">
|
|
1032
|
+
<div class="env-items-grid">
|
|
1033
|
+
<div class="env-item">
|
|
1034
|
+
<div class="env-item-icon">
|
|
1035
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1036
|
+
<path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
|
|
1037
|
+
</svg>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div class="env-item-content">
|
|
1040
|
+
<p class="env-item-label">Host</p>
|
|
1041
|
+
<div class="env-item-value" title="${environment.host}">${environment.host}</div>
|
|
1042
|
+
</div>
|
|
969
1043
|
</div>
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
<span class="env-detail-value">${environment.node}</span>
|
|
1044
|
+
|
|
1045
|
+
<div class="env-item">
|
|
1046
|
+
<div class="env-item-icon">
|
|
1047
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1048
|
+
<path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
|
|
1049
|
+
</svg>
|
|
1050
|
+
</div>
|
|
1051
|
+
<div class="env-item-content">
|
|
1052
|
+
<p class="env-item-label">Os</p>
|
|
1053
|
+
<div class="env-item-value" title="${environment.os}">${environment.os}</div>
|
|
1054
|
+
</div>
|
|
982
1055
|
</div>
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
<
|
|
1056
|
+
|
|
1057
|
+
<div class="env-item">
|
|
1058
|
+
<div class="env-item-icon">
|
|
1059
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1060
|
+
<rect width="16" height="16" x="4" y="4" rx="2"></rect>
|
|
1061
|
+
<rect width="6" height="6" x="9" y="9" rx="1"></rect>
|
|
1062
|
+
<path d="M15 2v2"></path>
|
|
1063
|
+
<path d="M15 20v2"></path>
|
|
1064
|
+
<path d="M2 15h2"></path>
|
|
1065
|
+
<path d="M2 9h2"></path>
|
|
1066
|
+
<path d="M20 15h2"></path>
|
|
1067
|
+
<path d="M20 9h2"></path>
|
|
1068
|
+
<path d="M9 2v2"></path>
|
|
1069
|
+
<path d="M9 20v2"></path>
|
|
1070
|
+
</svg>
|
|
1071
|
+
</div>
|
|
1072
|
+
<div class="env-item-content">
|
|
1073
|
+
<p class="env-item-label">Cpu</p>
|
|
1074
|
+
<div class="env-item-value" title='${JSON.stringify(environment.cpu)}'>${cpuInfo}</div>
|
|
1075
|
+
</div>
|
|
986
1076
|
</div>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
<
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1077
|
+
|
|
1078
|
+
<div class="env-item">
|
|
1079
|
+
<div class="env-item-icon">
|
|
1080
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1081
|
+
<path d="M6 19v-3"></path>
|
|
1082
|
+
<path d="M10 19v-3"></path>
|
|
1083
|
+
<path d="M14 19v-3"></path>
|
|
1084
|
+
<path d="M18 19v-3"></path>
|
|
1085
|
+
<path d="M8 11V9"></path>
|
|
1086
|
+
<path d="M16 11V9"></path>
|
|
1087
|
+
<path d="M12 11V9"></path>
|
|
1088
|
+
<path d="M2 15h20"></path>
|
|
1089
|
+
<path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z"></path>
|
|
1090
|
+
</svg>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div class="env-item-content">
|
|
1093
|
+
<p class="env-item-label">Memory</p>
|
|
1094
|
+
<div class="env-item-value" title="${environment.memory}">${environment.memory}</div>
|
|
1095
|
+
</div>
|
|
994
1096
|
</div>
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
? "ARM-based"
|
|
1020
|
-
: "x86/Other"
|
|
1021
|
-
}
|
|
1022
|
-
</span>
|
|
1023
|
-
</span>
|
|
1097
|
+
|
|
1098
|
+
<div class="env-item">
|
|
1099
|
+
<div class="env-item-icon">
|
|
1100
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1101
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1102
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1103
|
+
<path d="M12 2v2"></path>
|
|
1104
|
+
<path d="M12 22v-2"></path>
|
|
1105
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1106
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1107
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1108
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1109
|
+
<path d="M14 12h8"></path>
|
|
1110
|
+
<path d="M2 12h2"></path>
|
|
1111
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1112
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1113
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1114
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1115
|
+
</svg>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="env-item-content">
|
|
1118
|
+
<p class="env-item-label">Node</p>
|
|
1119
|
+
<div class="env-item-value" title="${environment.node}">${environment.node}</div>
|
|
1120
|
+
</div>
|
|
1024
1121
|
</div>
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
<
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1122
|
+
|
|
1123
|
+
<div class="env-item">
|
|
1124
|
+
<div class="env-item-icon">
|
|
1125
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1126
|
+
<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
|
|
1127
|
+
<path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
|
|
1128
|
+
<path d="M12 2v2"></path>
|
|
1129
|
+
<path d="M12 22v-2"></path>
|
|
1130
|
+
<path d="m17 20.66-1-1.73"></path>
|
|
1131
|
+
<path d="M11 10.27 7 3.34"></path>
|
|
1132
|
+
<path d="m20.66 17-1.73-1"></path>
|
|
1133
|
+
<path d="m3.34 7 1.73 1"></path>
|
|
1134
|
+
<path d="M14 12h8"></path>
|
|
1135
|
+
<path d="M2 12h2"></path>
|
|
1136
|
+
<path d="m20.66 7-1.73 1"></path>
|
|
1137
|
+
<path d="m3.34 17 1.73-1"></path>
|
|
1138
|
+
<path d="m17 3.34-1 1.73"></path>
|
|
1139
|
+
<path d="m11 13.73-4 6.93"></path>
|
|
1140
|
+
</svg>
|
|
1141
|
+
</div>
|
|
1142
|
+
<div class="env-item-content">
|
|
1143
|
+
<p class="env-item-label">V8</p>
|
|
1144
|
+
<div class="env-item-value" title="${environment.v8}">${environment.v8}</div>
|
|
1145
|
+
</div>
|
|
1034
1146
|
</div>
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
<
|
|
1147
|
+
|
|
1148
|
+
<div class="env-item">
|
|
1149
|
+
<div class="env-item-icon">
|
|
1150
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1151
|
+
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
1152
|
+
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
1153
|
+
</svg>
|
|
1154
|
+
</div>
|
|
1155
|
+
<div class="env-item-content">
|
|
1156
|
+
<p class="env-item-label">Working Dir</p>
|
|
1157
|
+
<div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
|
|
1158
|
+
</div>
|
|
1038
1159
|
</div>
|
|
1039
1160
|
</div>
|
|
1040
1161
|
</div>
|
|
@@ -1057,11 +1178,11 @@ function generateWorkerDistributionChart(results) {
|
|
|
1057
1178
|
const workerId =
|
|
1058
1179
|
typeof test.workerId !== "undefined" ? test.workerId : "N/A";
|
|
1059
1180
|
if (!acc[workerId]) {
|
|
1060
|
-
acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
1181
|
+
acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
|
|
1061
1182
|
}
|
|
1062
1183
|
|
|
1063
1184
|
const status = String(test.status).toLowerCase();
|
|
1064
|
-
if (status === "passed" || status === "failed" || status === "skipped") {
|
|
1185
|
+
if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
|
|
1065
1186
|
acc[workerId][status]++;
|
|
1066
1187
|
}
|
|
1067
1188
|
|
|
@@ -1089,7 +1210,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1089
1210
|
.substring(2, 7)}`;
|
|
1090
1211
|
const renderFunctionName = `renderWorkerDistChart_${chartId.replace(
|
|
1091
1212
|
/-/g,
|
|
1092
|
-
"_"
|
|
1213
|
+
"_",
|
|
1093
1214
|
)}`;
|
|
1094
1215
|
const modalJsNamespace = `modal_funcs_${chartId.replace(/-/g, "_")}`;
|
|
1095
1216
|
|
|
@@ -1106,12 +1227,14 @@ function generateWorkerDistributionChart(results) {
|
|
|
1106
1227
|
const passedData = workerIds.map((id) => workerData[id].passed);
|
|
1107
1228
|
const failedData = workerIds.map((id) => workerData[id].failed);
|
|
1108
1229
|
const skippedData = workerIds.map((id) => workerData[id].skipped);
|
|
1230
|
+
const flakyData = workerIds.map((id) => workerData[id].flaky);
|
|
1109
1231
|
|
|
1110
1232
|
const categoriesString = JSON.stringify(categories);
|
|
1111
1233
|
const fullDataString = JSON.stringify(fullWorkerData);
|
|
1112
1234
|
const seriesString = JSON.stringify([
|
|
1113
1235
|
{ name: "Passed", data: passedData, color: "var(--success-color)" },
|
|
1114
1236
|
{ name: "Failed", data: failedData, color: "var(--danger-color)" },
|
|
1237
|
+
{ name: "Flaky", data: flakyData, color: "#00ccd3" },
|
|
1115
1238
|
{ name: "Skipped", data: skippedData, color: "var(--warning-color)" },
|
|
1116
1239
|
]);
|
|
1117
1240
|
|
|
@@ -1131,12 +1254,22 @@ function generateWorkerDistributionChart(results) {
|
|
|
1131
1254
|
position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
|
1132
1255
|
}
|
|
1133
1256
|
.worker-modal-close {
|
|
1134
|
-
position: absolute;
|
|
1135
|
-
|
|
1257
|
+
position: absolute;
|
|
1258
|
+
top: 15px;
|
|
1259
|
+
right: 25px;
|
|
1260
|
+
font-size: 32px;
|
|
1261
|
+
font-weight: bold;
|
|
1262
|
+
cursor: pointer;
|
|
1136
1263
|
line-height: 1;
|
|
1264
|
+
z-index: 10;
|
|
1265
|
+
color: #fff;
|
|
1266
|
+
transition: color 0.2s ease;
|
|
1267
|
+
user-select: none;
|
|
1268
|
+
-webkit-user-select: none;
|
|
1137
1269
|
}
|
|
1138
1270
|
.worker-modal-close:hover, .worker-modal-close:focus {
|
|
1139
|
-
color:
|
|
1271
|
+
color: #ef4444;
|
|
1272
|
+
transform: scale(1.1);
|
|
1140
1273
|
}
|
|
1141
1274
|
#worker-modal-body-${chartId} ul {
|
|
1142
1275
|
list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
|
|
@@ -1163,7 +1296,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1163
1296
|
|
|
1164
1297
|
<div id="worker-modal-${chartId}" class="worker-modal-overlay">
|
|
1165
1298
|
<div class="worker-modal-content">
|
|
1166
|
-
<span class="worker-modal-close">×</span>
|
|
1299
|
+
<span class="worker-modal-close" onclick="window.${modalJsNamespace}.close?.()">×</span>
|
|
1167
1300
|
<h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: #fff"></h3>
|
|
1168
1301
|
<div id="worker-modal-body-${chartId}"></div>
|
|
1169
1302
|
</div>
|
|
@@ -1194,6 +1327,7 @@ function generateWorkerDistributionChart(results) {
|
|
|
1194
1327
|
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1195
1328
|
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1196
1329
|
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1330
|
+
else if (test.status === 'flaky') color = '#00ccd3';
|
|
1197
1331
|
|
|
1198
1332
|
const escapedName = test.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1199
1333
|
testListHtml += \`<li style="color: \${color};"><span style="color: \${color}">[\${test.status.toUpperCase()}]</span> \${escapedName}</li>\`;
|
|
@@ -1210,10 +1344,14 @@ function generateWorkerDistributionChart(results) {
|
|
|
1210
1344
|
const closeModal = function() {
|
|
1211
1345
|
modal.style.display = 'none';
|
|
1212
1346
|
};
|
|
1347
|
+
|
|
1348
|
+
window.${modalJsNamespace}.close = closeModal;
|
|
1213
1349
|
|
|
1214
|
-
closeModalBtn
|
|
1350
|
+
if (closeModalBtn) {
|
|
1351
|
+
closeModalBtn.onclick = closeModal;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1215
1354
|
modal.onclick = function(event) {
|
|
1216
|
-
// Close if clicked on the dark overlay background
|
|
1217
1355
|
if (event.target == modal) {
|
|
1218
1356
|
closeModal();
|
|
1219
1357
|
}
|
|
@@ -1329,7 +1467,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1329
1467
|
? `test run ${overallRun.runId}`
|
|
1330
1468
|
: `test run ${index + 1}`;
|
|
1331
1469
|
const testRunForThisOverallRun = trendData.testRuns[runKey]?.find(
|
|
1332
|
-
(t) => t && t.testName === fullTestName
|
|
1470
|
+
(t) => t && t.testName === fullTestName,
|
|
1333
1471
|
);
|
|
1334
1472
|
if (testRunForThisOverallRun) {
|
|
1335
1473
|
history.push({
|
|
@@ -1355,6 +1493,7 @@ function generateTestHistoryContent(trendData) {
|
|
|
1355
1493
|
<option value="">All Statuses</option>
|
|
1356
1494
|
<option value="passed">Passed</option>
|
|
1357
1495
|
<option value="failed">Failed</option>
|
|
1496
|
+
<option value="flaky">Flaky</option>
|
|
1358
1497
|
<option value="skipped">Skipped</option>
|
|
1359
1498
|
</select>
|
|
1360
1499
|
<button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
|
|
@@ -1369,12 +1508,12 @@ function generateTestHistoryContent(trendData) {
|
|
|
1369
1508
|
: { status: "unknown" };
|
|
1370
1509
|
return `
|
|
1371
1510
|
<div class="test-history-card" data-test-name="${sanitizeHTML(
|
|
1372
|
-
test.testTitle.toLowerCase()
|
|
1511
|
+
test.testTitle.toLowerCase(),
|
|
1373
1512
|
)}" data-latest-status="${latestRun.status}">
|
|
1374
1513
|
<div class="test-history-header">
|
|
1375
1514
|
<p title="${sanitizeHTML(test.testTitle)}">${capitalize(
|
|
1376
|
-
|
|
1377
|
-
|
|
1515
|
+
sanitizeHTML(test.testTitle),
|
|
1516
|
+
)}</p>
|
|
1378
1517
|
<span class="status-badge ${getStatusClass(latestRun.status)}">
|
|
1379
1518
|
${String(latestRun.status).toUpperCase()}
|
|
1380
1519
|
</span>
|
|
@@ -1396,11 +1535,11 @@ function generateTestHistoryContent(trendData) {
|
|
|
1396
1535
|
<tr>
|
|
1397
1536
|
<td>${run.runId}</td>
|
|
1398
1537
|
<td><span class="status-badge-small ${getStatusClass(
|
|
1399
|
-
run.status
|
|
1538
|
+
run.status,
|
|
1400
1539
|
)}">${String(run.status).toUpperCase()}</span></td>
|
|
1401
1540
|
<td>${formatDuration(run.duration)}</td>
|
|
1402
1541
|
<td>${formatDate(run.timestamp)}</td>
|
|
1403
|
-
</tr
|
|
1542
|
+
</tr>`,
|
|
1404
1543
|
)
|
|
1405
1544
|
.join("")}
|
|
1406
1545
|
</tbody>
|
|
@@ -1422,6 +1561,8 @@ function getStatusClass(status) {
|
|
|
1422
1561
|
return "status-failed";
|
|
1423
1562
|
case "skipped":
|
|
1424
1563
|
return "status-skipped";
|
|
1564
|
+
case "flaky":
|
|
1565
|
+
return "status-flaky";
|
|
1425
1566
|
default:
|
|
1426
1567
|
return "status-unknown";
|
|
1427
1568
|
}
|
|
@@ -1434,6 +1575,8 @@ function getStatusIcon(status) {
|
|
|
1434
1575
|
return "❌";
|
|
1435
1576
|
case "skipped":
|
|
1436
1577
|
return "⏭️";
|
|
1578
|
+
case "flaky":
|
|
1579
|
+
return "⚠️";
|
|
1437
1580
|
default:
|
|
1438
1581
|
return "❓";
|
|
1439
1582
|
}
|
|
@@ -1469,6 +1612,7 @@ function getSuitesData(results) {
|
|
|
1469
1612
|
browser: browser,
|
|
1470
1613
|
passed: 0,
|
|
1471
1614
|
failed: 0,
|
|
1615
|
+
flaky: 0,
|
|
1472
1616
|
skipped: 0,
|
|
1473
1617
|
count: 0,
|
|
1474
1618
|
statusOverall: "passed",
|
|
@@ -1476,12 +1620,15 @@ function getSuitesData(results) {
|
|
|
1476
1620
|
}
|
|
1477
1621
|
const suite = suitesMap.get(key);
|
|
1478
1622
|
suite.count++;
|
|
1479
|
-
|
|
1623
|
+
let currentStatus = String(test.status).toLowerCase();
|
|
1624
|
+
if (test.outcome === 'flaky') currentStatus = 'flaky';
|
|
1480
1625
|
if (currentStatus && suite[currentStatus] !== undefined) {
|
|
1481
1626
|
suite[currentStatus]++;
|
|
1482
1627
|
}
|
|
1483
1628
|
if (currentStatus === "failed") suite.statusOverall = "failed";
|
|
1484
|
-
else if (currentStatus === "
|
|
1629
|
+
else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
|
|
1630
|
+
suite.statusOverall = "flaky";
|
|
1631
|
+
else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
|
|
1485
1632
|
suite.statusOverall = "skipped";
|
|
1486
1633
|
});
|
|
1487
1634
|
return Array.from(suitesMap.values());
|
|
@@ -1492,58 +1639,56 @@ function generateSuitesWidget(suitesData) {
|
|
|
1492
1639
|
return `<div class="suites-widget" style="height: 450px;"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
|
|
1493
1640
|
}
|
|
1494
1641
|
|
|
1495
|
-
//
|
|
1642
|
+
// Uses CSS classes for responsiveness instead of inline styles
|
|
1496
1643
|
return `
|
|
1497
|
-
<div class="suites-widget
|
|
1498
|
-
<div class="suites-header"
|
|
1644
|
+
<div class="suites-widget fixed-height-widget">
|
|
1645
|
+
<div class="suites-header">
|
|
1499
1646
|
<h2>Test Suites</h2>
|
|
1500
1647
|
<span class="summary-badge">${
|
|
1501
1648
|
suitesData.length
|
|
1502
1649
|
} suites • ${suitesData.reduce(
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1650
|
+
(sum, suite) => sum + suite.count,
|
|
1651
|
+
0,
|
|
1652
|
+
)} tests</span>
|
|
1506
1653
|
</div>
|
|
1507
1654
|
|
|
1508
|
-
<div class="suites-grid-container"
|
|
1655
|
+
<div class="suites-grid-container">
|
|
1509
1656
|
<div class="suites-grid">
|
|
1510
1657
|
${suitesData
|
|
1511
1658
|
.map(
|
|
1512
1659
|
(suite) => `
|
|
1513
1660
|
<div class="suite-card status-${suite.statusOverall}">
|
|
1514
1661
|
<div class="suite-card-header">
|
|
1515
|
-
<h3 class="suite-name" title="${sanitizeHTML(
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1662
|
+
<h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
1663
|
+
<div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
|
|
1664
|
+
</div>
|
|
1665
|
+
|
|
1666
|
+
<div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
|
|
1667
|
+
<span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
|
|
1520
1668
|
</div>
|
|
1521
|
-
|
|
1522
|
-
suite.browser
|
|
1523
|
-
)}</span></div>
|
|
1669
|
+
|
|
1524
1670
|
<div class="suite-card-body">
|
|
1525
|
-
<span class="test-count">${suite.count}
|
|
1526
|
-
suite.count !== 1 ? "s" : ""
|
|
1527
|
-
}</span>
|
|
1671
|
+
<span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
|
|
1528
1672
|
<div class="suite-stats">
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1673
|
+
<span class="stat-pill passed" title="Passed">
|
|
1674
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>
|
|
1675
|
+
${suite.passed}
|
|
1676
|
+
</span>
|
|
1677
|
+
<span class="stat-pill failed" title="Failed">
|
|
1678
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>
|
|
1679
|
+
${suite.failed}
|
|
1680
|
+
</span>
|
|
1681
|
+
<span class="stat-pill flaky" title="Flaky">
|
|
1682
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1683
|
+
${suite.flaky || 0}
|
|
1684
|
+
</span>
|
|
1685
|
+
<span class="stat-pill skipped" title="Skipped">
|
|
1686
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
|
|
1687
|
+
${suite.skipped}
|
|
1688
|
+
</span>
|
|
1544
1689
|
</div>
|
|
1545
1690
|
</div>
|
|
1546
|
-
</div
|
|
1691
|
+
</div>`,
|
|
1547
1692
|
)
|
|
1548
1693
|
.join("")}
|
|
1549
1694
|
</div>
|
|
@@ -1565,12 +1710,11 @@ function getAttachmentIcon(contentType) {
|
|
|
1565
1710
|
}
|
|
1566
1711
|
function generateAIFailureAnalyzerTab(results) {
|
|
1567
1712
|
const failedTests = (results || []).filter(
|
|
1568
|
-
(test) => test.status === "failed"
|
|
1713
|
+
(test) => test.status === "failed",
|
|
1569
1714
|
);
|
|
1570
1715
|
|
|
1571
1716
|
if (failedTests.length === 0) {
|
|
1572
1717
|
return `
|
|
1573
|
-
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
1574
1718
|
<div class="no-data">Congratulations! No failed tests in this run.</div>
|
|
1575
1719
|
`;
|
|
1576
1720
|
}
|
|
@@ -1579,7 +1723,6 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1579
1723
|
const btoa = (str) => Buffer.from(str).toString("base64");
|
|
1580
1724
|
|
|
1581
1725
|
return `
|
|
1582
|
-
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
1583
1726
|
<div class="ai-analyzer-stats">
|
|
1584
1727
|
<div class="stat-item">
|
|
1585
1728
|
<span class="stat-number">${failedTests.length}</span>
|
|
@@ -1594,7 +1737,7 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1594
1737
|
<div class="stat-item">
|
|
1595
1738
|
<span class="stat-number">${Math.round(
|
|
1596
1739
|
failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) /
|
|
1597
|
-
1000
|
|
1740
|
+
1000,
|
|
1598
1741
|
)}s</span>
|
|
1599
1742
|
<span class="stat-label">Total Duration</span>
|
|
1600
1743
|
</div>
|
|
@@ -1617,14 +1760,14 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1617
1760
|
<div class="failure-header">
|
|
1618
1761
|
<div class="failure-main-info">
|
|
1619
1762
|
<h3 class="failure-title" title="${sanitizeHTML(
|
|
1620
|
-
test.name
|
|
1763
|
+
test.name,
|
|
1621
1764
|
)}">${sanitizeHTML(testTitle)}</h3>
|
|
1622
1765
|
<div class="failure-meta">
|
|
1623
1766
|
<span class="browser-indicator">${sanitizeHTML(
|
|
1624
|
-
test.browser || "unknown"
|
|
1767
|
+
test.browser || "unknown",
|
|
1625
1768
|
)}</span>
|
|
1626
1769
|
<span class="duration-indicator">${formatDuration(
|
|
1627
|
-
test.duration
|
|
1770
|
+
test.duration,
|
|
1628
1771
|
)}</span>
|
|
1629
1772
|
</div>
|
|
1630
1773
|
</div>
|
|
@@ -1639,7 +1782,7 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1639
1782
|
</div>
|
|
1640
1783
|
<div class="failure-error-preview">
|
|
1641
1784
|
<div class="error-snippet">${formatPlaywrightError(
|
|
1642
|
-
truncatedError
|
|
1785
|
+
truncatedError,
|
|
1643
1786
|
)}</div>
|
|
1644
1787
|
<button class="expand-error-btn" onclick="toggleErrorDetails(this)">
|
|
1645
1788
|
<span class="expand-text">Show Full Error</span>
|
|
@@ -1649,10 +1792,16 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1649
1792
|
<div class="full-error-details" style="display: none;">
|
|
1650
1793
|
<div class="full-error-content">
|
|
1651
1794
|
${formatPlaywrightError(
|
|
1652
|
-
test.errorMessage ||
|
|
1795
|
+
test.errorMessage ||
|
|
1796
|
+
"No detailed error message available",
|
|
1653
1797
|
)}
|
|
1654
1798
|
</div>
|
|
1655
1799
|
</div>
|
|
1800
|
+
<div class="ai-suggestion-container" style="display: none;">
|
|
1801
|
+
<div class="ai-suggestion-content">
|
|
1802
|
+
<!-- AI suggestion will be injected here -->
|
|
1803
|
+
</div>
|
|
1804
|
+
</div>
|
|
1656
1805
|
</div>
|
|
1657
1806
|
`;
|
|
1658
1807
|
})
|
|
@@ -1895,7 +2044,25 @@ function generateDescribeDurationChart(results) {
|
|
|
1895
2044
|
series: [{
|
|
1896
2045
|
name: 'Duration',
|
|
1897
2046
|
data: ${dataStr},
|
|
1898
|
-
|
|
2047
|
+
colorByPoint: true,
|
|
2048
|
+
colors: [
|
|
2049
|
+
'#9333ea',
|
|
2050
|
+
'#6366f1',
|
|
2051
|
+
'#0ea5e9',
|
|
2052
|
+
'#10b981',
|
|
2053
|
+
'#84cc16',
|
|
2054
|
+
'#eab308',
|
|
2055
|
+
'#f97316',
|
|
2056
|
+
'#ef4444',
|
|
2057
|
+
'#ec4899',
|
|
2058
|
+
'#8b5cf6',
|
|
2059
|
+
'#06b6d4',
|
|
2060
|
+
'#14b8a6',
|
|
2061
|
+
'#a3e635',
|
|
2062
|
+
'#fbbf24',
|
|
2063
|
+
'#fb923c',
|
|
2064
|
+
'#f87171'
|
|
2065
|
+
],
|
|
1899
2066
|
}],
|
|
1900
2067
|
credits: { enabled: false }
|
|
1901
2068
|
});
|
|
@@ -1919,6 +2086,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
1919
2086
|
const data = {
|
|
1920
2087
|
passed: [0, 0, 0, 0, 0],
|
|
1921
2088
|
failed: [0, 0, 0, 0, 0],
|
|
2089
|
+
flaky: [0, 0, 0, 0, 0],
|
|
1922
2090
|
skipped: [0, 0, 0, 0, 0],
|
|
1923
2091
|
};
|
|
1924
2092
|
|
|
@@ -1937,6 +2105,8 @@ function generateSeverityDistributionChart(results) {
|
|
|
1937
2105
|
status === "interrupted"
|
|
1938
2106
|
) {
|
|
1939
2107
|
data.failed[index]++;
|
|
2108
|
+
} else if (status === "flaky") {
|
|
2109
|
+
data.flaky[index]++;
|
|
1940
2110
|
} else {
|
|
1941
2111
|
data.skipped[index]++;
|
|
1942
2112
|
}
|
|
@@ -1950,6 +2120,7 @@ function generateSeverityDistributionChart(results) {
|
|
|
1950
2120
|
const seriesData = [
|
|
1951
2121
|
{ name: "Passed", data: data.passed, color: "var(--success-color)" },
|
|
1952
2122
|
{ name: "Failed", data: data.failed, color: "var(--danger-color)" },
|
|
2123
|
+
{ name: "Flaky", data: data.flaky, color: "#00ccd3" },
|
|
1953
2124
|
{ name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
|
|
1954
2125
|
];
|
|
1955
2126
|
|
|
@@ -2063,16 +2234,98 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2063
2234
|
return p.replace(new RegExp(`^${DEFAULT_OUTPUT_DIR}[\\\\/]`), "");
|
|
2064
2235
|
};
|
|
2065
2236
|
|
|
2237
|
+
|
|
2238
|
+
const avgTestDuration =
|
|
2239
|
+
runSummary.totalTests > 0
|
|
2240
|
+
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
2241
|
+
: "0.0s";
|
|
2242
|
+
|
|
2243
|
+
const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
|
|
2244
|
+
|
|
2245
|
+
// Calculate retry statistics
|
|
2246
|
+
let retriedTestsCount = 0;
|
|
2247
|
+
const totalRetried = (results || []).reduce((acc, test) => {
|
|
2248
|
+
if (test.retryHistory && test.retryHistory.length > 0) {
|
|
2249
|
+
// Filter out any "passed" or "skipped" entries in the history
|
|
2250
|
+
// We only count attempts that actually failed or timed out, triggering a retry.
|
|
2251
|
+
const unsuccessfulRetries = test.retryHistory.filter(attempt =>
|
|
2252
|
+
attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
|
|
2253
|
+
);
|
|
2254
|
+
if (unsuccessfulRetries.length > 0) {
|
|
2255
|
+
retriedTestsCount++;
|
|
2256
|
+
}
|
|
2257
|
+
return acc + unsuccessfulRetries.length;
|
|
2258
|
+
}
|
|
2259
|
+
return acc;
|
|
2260
|
+
}, 0);
|
|
2261
|
+
|
|
2262
|
+
// --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
|
|
2263
|
+
let calculatedPassed = 0;
|
|
2264
|
+
let calculatedFailed = 0;
|
|
2265
|
+
let calculatedSkipped = 0;
|
|
2266
|
+
let calculatedFlaky = 0;
|
|
2267
|
+
let calculatedTotal = 0;
|
|
2268
|
+
|
|
2269
|
+
(results || []).forEach(test => {
|
|
2270
|
+
calculatedTotal++;
|
|
2271
|
+
// New Logic: If outcome is 'flaky', it overrides everything.
|
|
2272
|
+
let statusToUse = test.status;
|
|
2273
|
+
if (test.outcome === 'flaky') {
|
|
2274
|
+
statusToUse = 'flaky';
|
|
2275
|
+
} else if (test.status === 'flaky') {
|
|
2276
|
+
// Just in case outcome wasn't set but status was (unlikely with new reporter)
|
|
2277
|
+
statusToUse = 'flaky';
|
|
2278
|
+
} else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
|
|
2279
|
+
statusToUse = test.final_status;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// Update test status in place for charts
|
|
2283
|
+
test.status = statusToUse;
|
|
2284
|
+
|
|
2285
|
+
const s = String(statusToUse).toLowerCase();
|
|
2286
|
+
if (s === 'passed') calculatedPassed++;
|
|
2287
|
+
else if (s === 'skipped') calculatedSkipped++;
|
|
2288
|
+
else if (s === 'flaky') calculatedFlaky++;
|
|
2289
|
+
else calculatedFailed++; // failed, timedout, interrupted
|
|
2290
|
+
});
|
|
2291
|
+
|
|
2292
|
+
// Override runSummary counts with our calculated ones if results exist
|
|
2293
|
+
if (results && results.length > 0) {
|
|
2294
|
+
runSummary.passed = calculatedPassed;
|
|
2295
|
+
runSummary.failed = calculatedFailed;
|
|
2296
|
+
runSummary.skipped = calculatedSkipped;
|
|
2297
|
+
runSummary.flaky = calculatedFlaky;
|
|
2298
|
+
runSummary.totalTests = calculatedTotal;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2066
2301
|
const totalTestsOr1 = runSummary.totalTests || 1;
|
|
2067
2302
|
const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
|
|
2068
2303
|
const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
|
|
2069
2304
|
const skipPercentage = Math.round(
|
|
2070
|
-
((runSummary.skipped || 0) / totalTestsOr1) * 100
|
|
2305
|
+
((runSummary.skipped || 0) / totalTestsOr1) * 100,
|
|
2071
2306
|
);
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2307
|
+
const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
|
|
2308
|
+
|
|
2309
|
+
|
|
2310
|
+
// Calculate browser distribution
|
|
2311
|
+
const browserStats = (results || []).reduce((acc, test) => {
|
|
2312
|
+
let browserName = "unknown";
|
|
2313
|
+
if (test.browser) {
|
|
2314
|
+
// Use full browser name
|
|
2315
|
+
browserName = test.browser;
|
|
2316
|
+
}
|
|
2317
|
+
acc[browserName] = (acc[browserName] || 0) + 1;
|
|
2318
|
+
return acc;
|
|
2319
|
+
}, {});
|
|
2320
|
+
|
|
2321
|
+
const totalTests = runSummary.totalTests || 1;
|
|
2322
|
+
const browserBreakdown = Object.entries(browserStats)
|
|
2323
|
+
.map(([browser, count]) => ({
|
|
2324
|
+
browser,
|
|
2325
|
+
count,
|
|
2326
|
+
percentage: Math.round((count / totalTests) * 100),
|
|
2327
|
+
}))
|
|
2328
|
+
.sort((a, b) => b.count - a.count);
|
|
2076
2329
|
function generateTestCasesHTML() {
|
|
2077
2330
|
if (!results || results.length === 0)
|
|
2078
2331
|
return '<div class="no-tests">No test results found in this run.</div>';
|
|
@@ -2082,28 +2335,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2082
2335
|
const testFileParts = test.name.split(" > ");
|
|
2083
2336
|
const testTitle =
|
|
2084
2337
|
testFileParts[testFileParts.length - 1] || "Unnamed Test";
|
|
2085
|
-
// ---
|
|
2338
|
+
// --- Simplified Severity Badge ---
|
|
2086
2339
|
const severity = test.severity || "Medium";
|
|
2087
|
-
const
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
return "#FFA07A";
|
|
2093
|
-
case "Medium":
|
|
2094
|
-
return "#577A11";
|
|
2095
|
-
case "High":
|
|
2096
|
-
return "#B71C1C";
|
|
2097
|
-
case "Critical":
|
|
2098
|
-
return "#64158A";
|
|
2099
|
-
default:
|
|
2100
|
-
return "#577A11";
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2103
|
-
const severityColor = getSeverityColor(severity);
|
|
2104
|
-
// We reuse 'status-badge' class for size/font consistency, but override background color
|
|
2105
|
-
const severityBadge = `<span class="status-badge" style="background-color: ${severityColor}; margin-right: 8px;">${severity}</span>`;
|
|
2106
|
-
// ---------------------------
|
|
2340
|
+
const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
|
|
2341
|
+
|
|
2342
|
+
// --- Retry Count Badge (only show if retries occurred) ---
|
|
2343
|
+
const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
|
|
2344
|
+
const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
|
|
2107
2345
|
const generateStepsHTML = (steps, depth = 0) => {
|
|
2108
2346
|
if (!steps || steps.length === 0)
|
|
2109
2347
|
return "<div class='no-steps'>No steps recorded for this test.</div>";
|
|
@@ -2111,36 +2349,46 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2111
2349
|
.map((step) => {
|
|
2112
2350
|
const hasNestedSteps = step.steps && step.steps.length > 0;
|
|
2113
2351
|
const isHook = step.hookType;
|
|
2352
|
+
const isFailedStep = step.isFailedStep === true;
|
|
2114
2353
|
const stepClass = isHook
|
|
2115
2354
|
? `step-hook step-hook-${step.hookType}`
|
|
2116
2355
|
: "";
|
|
2356
|
+
const failedStepClass = isFailedStep ? " failed-step-highlight" : "";
|
|
2117
2357
|
const hookIndicator = isHook ? ` (${step.hookType} hook)` : "";
|
|
2358
|
+
const failedStepIndicator = isFailedStep ? ` <span class="failed-step-marker">⚠️ Failed at this step</span>` : "";
|
|
2118
2359
|
return `
|
|
2119
|
-
<div class="step-item" style="--depth: ${depth};">
|
|
2360
|
+
<div class="step-item${failedStepClass}" style="--depth: ${depth};">
|
|
2120
2361
|
<div class="step-header ${stepClass}" role="button" aria-expanded="false">
|
|
2121
2362
|
<span class="step-icon">${getStatusIcon(step.status)}</span>
|
|
2122
2363
|
<span class="step-title">${sanitizeHTML(
|
|
2123
|
-
step.title
|
|
2124
|
-
)}${hookIndicator}</span>
|
|
2364
|
+
step.title,
|
|
2365
|
+
)}${hookIndicator}${failedStepIndicator}</span>
|
|
2125
2366
|
<span class="step-duration">${formatDuration(
|
|
2126
|
-
step.duration
|
|
2367
|
+
step.duration,
|
|
2127
2368
|
)}</span>
|
|
2128
2369
|
</div>
|
|
2129
2370
|
<div class="step-details" style="display: none;">
|
|
2130
2371
|
${
|
|
2131
2372
|
step.codeLocation
|
|
2132
2373
|
? `<div class="step-info code-section"><strong>Location:</strong> ${sanitizeHTML(
|
|
2133
|
-
step.codeLocation
|
|
2374
|
+
step.codeLocation,
|
|
2134
2375
|
)}</div>`
|
|
2135
2376
|
: ""
|
|
2136
2377
|
}
|
|
2378
|
+
${
|
|
2379
|
+
step.codeSnippet
|
|
2380
|
+
? `<div class="code-snippet-section"><pre class="code-snippet">${sanitizeHTML(
|
|
2381
|
+
step.codeSnippet,
|
|
2382
|
+
)}</pre></div>`
|
|
2383
|
+
: ""
|
|
2384
|
+
}
|
|
2137
2385
|
${
|
|
2138
2386
|
step.errorMessage
|
|
2139
2387
|
? `<div class="test-error-summary">
|
|
2140
2388
|
${
|
|
2141
2389
|
step.stackTrace
|
|
2142
2390
|
? `<div class="stack-trace">${formatPlaywrightError(
|
|
2143
|
-
step.stackTrace
|
|
2391
|
+
step.stackTrace,
|
|
2144
2392
|
)}</div>`
|
|
2145
2393
|
: ""
|
|
2146
2394
|
}
|
|
@@ -2149,7 +2397,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2149
2397
|
onclick="copyErrorToClipboard(this)"
|
|
2150
2398
|
style="
|
|
2151
2399
|
margin-top: 8px;
|
|
2152
|
-
padding:
|
|
2400
|
+
padding: 6px 12px;
|
|
2153
2401
|
background: #f0f0f0;
|
|
2154
2402
|
border: 2px solid #ccc;
|
|
2155
2403
|
border-radius: 4px;
|
|
@@ -2157,6 +2405,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2157
2405
|
font-size: 12px;
|
|
2158
2406
|
border-color: #8B0000;
|
|
2159
2407
|
color: #8B0000;
|
|
2408
|
+
align-self: flex-end;
|
|
2409
|
+
width: auto;
|
|
2160
2410
|
"
|
|
2161
2411
|
onmouseover="this.style.background='#e0e0e0'"
|
|
2162
2412
|
onmouseout="this.style.background='#f0f0f0'"
|
|
@@ -2170,7 +2420,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2170
2420
|
hasNestedSteps
|
|
2171
2421
|
? `<div class="nested-steps">${generateStepsHTML(
|
|
2172
2422
|
step.steps,
|
|
2173
|
-
depth + 1
|
|
2423
|
+
depth + 1,
|
|
2174
2424
|
)}</div>`
|
|
2175
2425
|
: ""
|
|
2176
2426
|
}
|
|
@@ -2180,41 +2430,36 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2180
2430
|
.join("");
|
|
2181
2431
|
};
|
|
2182
2432
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
${
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2209
|
-
</div>
|
|
2210
|
-
</div>
|
|
2211
|
-
<div class="test-case-content" style="display: none;">
|
|
2212
|
-
<p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
|
|
2433
|
+
// Helper for Tab Badges
|
|
2434
|
+
const getSmallStatusBadge = (status) => {
|
|
2435
|
+
const s = String(status).toLowerCase();
|
|
2436
|
+
let colorVar = 'var(--text-tertiary)';
|
|
2437
|
+
if(s === 'passed') colorVar = 'var(--success-color)';
|
|
2438
|
+
else if(s === 'failed') colorVar = 'var(--danger-color)';
|
|
2439
|
+
else if(s === 'skipped') colorVar = 'var(--warning-color)';
|
|
2440
|
+
else if(s === 'flaky') colorVar = '#00ccd3';
|
|
2441
|
+
|
|
2442
|
+
return `<span style="
|
|
2443
|
+
display: inline-block;
|
|
2444
|
+
width: 8px;
|
|
2445
|
+
height: 8px;
|
|
2446
|
+
border-radius: 50%;
|
|
2447
|
+
background-color: ${colorVar};
|
|
2448
|
+
margin-left: 6px;
|
|
2449
|
+
vertical-align: middle;
|
|
2450
|
+
" title="${s}"></span>`;
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2453
|
+
// Function to generate test content HTML (used for base run and retry tabs)
|
|
2454
|
+
const getTestContentHTML = (testData, runSuffix) => {
|
|
2455
|
+
const logId = `stdout-log-${test.id || index}-${runSuffix}`;
|
|
2456
|
+
return `
|
|
2457
|
+
<p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
|
|
2213
2458
|
${
|
|
2214
|
-
|
|
2459
|
+
testData.annotations && testData.annotations.length > 0
|
|
2215
2460
|
? `<div class="annotations-section" style="margin: 12px 0; padding: 12px; background-color: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); border-left: 4px solid #8b5cf6; border-radius: 4px;">
|
|
2216
2461
|
<h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
|
|
2217
|
-
${
|
|
2462
|
+
${testData.annotations
|
|
2218
2463
|
.map((annotation, idx) => {
|
|
2219
2464
|
const isIssueOrBug =
|
|
2220
2465
|
annotation.type === "issue" ||
|
|
@@ -2222,22 +2467,22 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2222
2467
|
const descriptionText = annotation.description || "";
|
|
2223
2468
|
const typeLabel = sanitizeHTML(annotation.type);
|
|
2224
2469
|
const descriptionHtml =
|
|
2225
|
-
isIssueOrBug && descriptionText.match(/^[A-Z]
|
|
2470
|
+
isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
|
|
2226
2471
|
? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
|
|
2227
|
-
descriptionText
|
|
2472
|
+
descriptionText,
|
|
2228
2473
|
)}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
|
|
2229
|
-
descriptionText
|
|
2474
|
+
descriptionText,
|
|
2230
2475
|
)}</a>`
|
|
2231
2476
|
: sanitizeHTML(descriptionText);
|
|
2232
2477
|
const locationText = annotation.location
|
|
2233
2478
|
? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
|
|
2234
|
-
annotation.location.file
|
|
2479
|
+
annotation.location.file,
|
|
2235
2480
|
)}:${annotation.location.line}:${
|
|
2236
2481
|
annotation.location.column
|
|
2237
2482
|
}</div>`
|
|
2238
2483
|
: "";
|
|
2239
2484
|
return `<div style="margin-bottom: ${
|
|
2240
|
-
idx <
|
|
2485
|
+
idx < testData.annotations.length - 1 ? "10px" : "0"
|
|
2241
2486
|
};">
|
|
2242
2487
|
<strong style="color: #8b5cf6;">Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>
|
|
2243
2488
|
${
|
|
@@ -2253,21 +2498,21 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2253
2498
|
: ""
|
|
2254
2499
|
}
|
|
2255
2500
|
<p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
|
|
2256
|
-
|
|
2501
|
+
testData.workerId,
|
|
2257
2502
|
)} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
|
|
2258
|
-
|
|
2259
|
-
|
|
2503
|
+
testData.totalWorkers,
|
|
2504
|
+
)}]</p>
|
|
2260
2505
|
${
|
|
2261
|
-
|
|
2262
|
-
? `<div class="test-error-summary">${formatPlaywrightError(
|
|
2263
|
-
|
|
2264
|
-
)}
|
|
2506
|
+
testData.errorMessage
|
|
2507
|
+
? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
|
|
2508
|
+
testData.errorMessage,
|
|
2509
|
+
)}</div>
|
|
2265
2510
|
<button
|
|
2266
2511
|
class="copy-error-btn"
|
|
2267
2512
|
onclick="copyErrorToClipboard(this)"
|
|
2268
2513
|
style="
|
|
2269
2514
|
margin-top: 8px;
|
|
2270
|
-
padding:
|
|
2515
|
+
padding: 6px 12px;
|
|
2271
2516
|
background: #f0f0f0;
|
|
2272
2517
|
border: 2px solid #ccc;
|
|
2273
2518
|
border-radius: 4px;
|
|
@@ -2275,6 +2520,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2275
2520
|
font-size: 12px;
|
|
2276
2521
|
border-color: #8B0000;
|
|
2277
2522
|
color: #8B0000;
|
|
2523
|
+
align-self: flex-end;
|
|
2524
|
+
width: auto;
|
|
2278
2525
|
"
|
|
2279
2526
|
onmouseover="this.style.background='#e0e0e0'"
|
|
2280
2527
|
onmouseout="this.style.background='#f0f0f0'"
|
|
@@ -2285,61 +2532,61 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2285
2532
|
: ""
|
|
2286
2533
|
}
|
|
2287
2534
|
${
|
|
2288
|
-
|
|
2535
|
+
testData.snippet
|
|
2289
2536
|
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
2290
|
-
|
|
2537
|
+
testData.snippet,
|
|
2291
2538
|
)}</code></pre></div>`
|
|
2292
2539
|
: ""
|
|
2293
2540
|
}
|
|
2294
2541
|
<h4>Steps</h4>
|
|
2295
|
-
<div class="steps-list">${generateStepsHTML(
|
|
2542
|
+
<div class="steps-list">${generateStepsHTML(testData.steps)}</div>
|
|
2296
2543
|
${(() => {
|
|
2297
|
-
if (!
|
|
2298
|
-
// Create a unique ID for the <pre> element to target it for copying
|
|
2299
|
-
const logId = `stdout-log-${test.id || index}`;
|
|
2544
|
+
if (!testData.stdout || testData.stdout.length === 0) return "";
|
|
2300
2545
|
return `<div class="console-output-section">
|
|
2301
2546
|
<h4>Console Output (stdout)
|
|
2302
|
-
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy
|
|
2547
|
+
<button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
|
|
2303
2548
|
</h4>
|
|
2304
2549
|
<div class="log-wrapper">
|
|
2305
2550
|
<pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2306
|
-
|
|
2307
|
-
|
|
2551
|
+
testData.stdout
|
|
2552
|
+
.map((line) => sanitizeHTML(line))
|
|
2553
|
+
.join("\\n"),
|
|
2554
|
+
)}</pre>
|
|
2308
2555
|
</div>
|
|
2309
2556
|
</div>`;
|
|
2310
2557
|
})()}
|
|
2311
2558
|
${
|
|
2312
|
-
|
|
2559
|
+
testData.stderr && testData.stderr.length > 0
|
|
2313
2560
|
? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2314
|
-
|
|
2561
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\\n"),
|
|
2315
2562
|
)}</pre></div>`
|
|
2316
2563
|
: ""
|
|
2317
2564
|
}
|
|
2318
2565
|
${
|
|
2319
|
-
|
|
2566
|
+
testData.screenshots && testData.screenshots.length > 0
|
|
2320
2567
|
? `
|
|
2321
2568
|
<div class="attachments-section">
|
|
2322
2569
|
<h4>Screenshots</h4>
|
|
2323
2570
|
<div class="attachments-grid">
|
|
2324
|
-
${
|
|
2571
|
+
${testData.screenshots
|
|
2325
2572
|
.map(
|
|
2326
|
-
(screenshot,
|
|
2573
|
+
(screenshot, screenshotIndex) => `
|
|
2327
2574
|
<div class="attachment-item">
|
|
2328
2575
|
<img src="${fixPath(screenshot)}" alt="Screenshot ${
|
|
2329
|
-
|
|
2576
|
+
screenshotIndex + 1
|
|
2330
2577
|
}">
|
|
2331
2578
|
<div class="attachment-info">
|
|
2332
2579
|
<div class="trace-actions">
|
|
2333
2580
|
<a href="${fixPath(
|
|
2334
|
-
screenshot
|
|
2581
|
+
screenshot,
|
|
2335
2582
|
)}" target="_blank" class="view-full">View Full Image</a>
|
|
2336
2583
|
<a href="${fixPath(
|
|
2337
|
-
screenshot
|
|
2338
|
-
)}" target="_blank" download="screenshot-${Date.now()}-${
|
|
2584
|
+
screenshot,
|
|
2585
|
+
)}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
|
|
2339
2586
|
</div>
|
|
2340
2587
|
</div>
|
|
2341
2588
|
</div>
|
|
2342
|
-
|
|
2589
|
+
`,
|
|
2343
2590
|
)
|
|
2344
2591
|
.join("")}
|
|
2345
2592
|
</div>
|
|
@@ -2348,9 +2595,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2348
2595
|
: ""
|
|
2349
2596
|
}
|
|
2350
2597
|
${
|
|
2351
|
-
|
|
2352
|
-
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${
|
|
2353
|
-
.map((videoUrl,
|
|
2598
|
+
testData.videoPath && testData.videoPath.length > 0
|
|
2599
|
+
? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
|
|
2600
|
+
.map((videoUrl, videoIndex) => {
|
|
2354
2601
|
const fixedVideoUrl = fixPath(videoUrl);
|
|
2355
2602
|
const fileExtension = String(fixedVideoUrl)
|
|
2356
2603
|
.split(".")
|
|
@@ -2366,18 +2613,18 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2366
2613
|
}[fileExtension] || "video/mp4";
|
|
2367
2614
|
return `<div class="attachment-item video-item">
|
|
2368
2615
|
<video controls width="100%" height="auto" title="Video ${
|
|
2369
|
-
|
|
2616
|
+
videoIndex + 1
|
|
2370
2617
|
}">
|
|
2371
2618
|
<source src="${sanitizeHTML(
|
|
2372
|
-
fixedVideoUrl
|
|
2619
|
+
fixedVideoUrl,
|
|
2373
2620
|
)}" type="${mimeType}">
|
|
2374
2621
|
Your browser does not support the video tag.
|
|
2375
2622
|
</video>
|
|
2376
2623
|
<div class="attachment-info">
|
|
2377
2624
|
<div class="trace-actions">
|
|
2378
2625
|
<a href="${sanitizeHTML(
|
|
2379
|
-
fixedVideoUrl
|
|
2380
|
-
)}" target="_blank" download="video-${Date.now()}-${
|
|
2626
|
+
fixedVideoUrl,
|
|
2627
|
+
)}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
|
|
2381
2628
|
</div>
|
|
2382
2629
|
</div>
|
|
2383
2630
|
</div>`;
|
|
@@ -2386,7 +2633,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2386
2633
|
: ""
|
|
2387
2634
|
}
|
|
2388
2635
|
${
|
|
2389
|
-
|
|
2636
|
+
testData.tracePath
|
|
2390
2637
|
? `
|
|
2391
2638
|
<div class="attachments-section">
|
|
2392
2639
|
<h4>Trace Files</h4>
|
|
@@ -2395,16 +2642,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2395
2642
|
<div class="trace-preview">
|
|
2396
2643
|
<span class="trace-icon">📄</span>
|
|
2397
2644
|
<span class="trace-name">${sanitizeHTML(
|
|
2398
|
-
path.basename(
|
|
2645
|
+
path.basename(testData.tracePath),
|
|
2399
2646
|
)}</span>
|
|
2400
2647
|
</div>
|
|
2401
2648
|
<div class="attachment-info">
|
|
2402
2649
|
<div class="trace-actions">
|
|
2403
2650
|
<a href="${sanitizeHTML(
|
|
2404
|
-
fixPath(
|
|
2651
|
+
fixPath(testData.tracePath),
|
|
2405
2652
|
)}" target="_blank" download="${sanitizeHTML(
|
|
2406
|
-
|
|
2407
|
-
|
|
2653
|
+
path.basename(testData.tracePath),
|
|
2654
|
+
)}" class="download-trace">Download Trace</a>
|
|
2408
2655
|
</div>
|
|
2409
2656
|
</div>
|
|
2410
2657
|
</div>
|
|
@@ -2414,40 +2661,40 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2414
2661
|
: ""
|
|
2415
2662
|
}
|
|
2416
2663
|
${
|
|
2417
|
-
|
|
2664
|
+
testData.attachments && testData.attachments.length > 0
|
|
2418
2665
|
? `
|
|
2419
2666
|
<div class="attachments-section">
|
|
2420
2667
|
<h4>Other Attachments</h4>
|
|
2421
2668
|
<div class="attachments-grid">
|
|
2422
|
-
${
|
|
2669
|
+
${testData.attachments
|
|
2423
2670
|
.map(
|
|
2424
2671
|
(attachment) => `
|
|
2425
2672
|
<div class="attachment-item generic-attachment">
|
|
2426
2673
|
<div class="attachment-icon">${getAttachmentIcon(
|
|
2427
|
-
attachment.contentType
|
|
2674
|
+
attachment.contentType,
|
|
2428
2675
|
)}</div>
|
|
2429
2676
|
<div class="attachment-caption">
|
|
2430
2677
|
<span class="attachment-name" title="${sanitizeHTML(
|
|
2431
|
-
attachment.name
|
|
2678
|
+
attachment.name,
|
|
2432
2679
|
)}">${sanitizeHTML(attachment.name)}</span>
|
|
2433
2680
|
<span class="attachment-type">${sanitizeHTML(
|
|
2434
|
-
attachment.contentType
|
|
2681
|
+
attachment.contentType,
|
|
2435
2682
|
)}</span>
|
|
2436
2683
|
</div>
|
|
2437
2684
|
<div class="attachment-info">
|
|
2438
2685
|
<div class="trace-actions">
|
|
2439
2686
|
<a href="${sanitizeHTML(
|
|
2440
|
-
fixPath(attachment.path)
|
|
2687
|
+
fixPath(attachment.path),
|
|
2441
2688
|
)}" target="_blank" class="view-full">View</a>
|
|
2442
2689
|
<a href="${sanitizeHTML(
|
|
2443
|
-
fixPath(attachment.path)
|
|
2690
|
+
fixPath(attachment.path),
|
|
2444
2691
|
)}" target="_blank" download="${sanitizeHTML(
|
|
2445
|
-
|
|
2446
|
-
|
|
2692
|
+
attachment.name,
|
|
2693
|
+
)}" class="download-trace">Download</a>
|
|
2447
2694
|
</div>
|
|
2448
2695
|
</div>
|
|
2449
2696
|
</div>
|
|
2450
|
-
|
|
2697
|
+
`,
|
|
2451
2698
|
)
|
|
2452
2699
|
.join("")}
|
|
2453
2700
|
</div>
|
|
@@ -2455,13 +2702,76 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2455
2702
|
`
|
|
2456
2703
|
: ""
|
|
2457
2704
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2705
|
+
|
|
2706
|
+
`;
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
// Determine header status: use final_status if retried, else normal status
|
|
2710
|
+
const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
|
|
2711
|
+
? test.final_status
|
|
2712
|
+
: test.status;
|
|
2713
|
+
|
|
2714
|
+
const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
|
|
2715
|
+
? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
|
|
2716
|
+
: '';
|
|
2717
|
+
|
|
2718
|
+
return `
|
|
2719
|
+
<div class="test-case" data-status="${
|
|
2720
|
+
headerStatus
|
|
2721
|
+
}" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
|
|
2722
|
+
.join(",")
|
|
2723
|
+
.toLowerCase()}">
|
|
2724
|
+
<div class="test-case-header" role="button" aria-expanded="false">
|
|
2725
|
+
<div class="test-case-summary">
|
|
2726
|
+
<span class="test-case-title" title="${sanitizeHTML(
|
|
2727
|
+
test.name,
|
|
2728
|
+
)}">${sanitizeHTML(testTitle)}</span>
|
|
2729
|
+
<span class="test-case-browser">(${sanitizeHTML(browser)})</span>
|
|
2730
|
+
</div>
|
|
2731
|
+
<div class="test-case-meta">
|
|
2732
|
+
${severityBadge}
|
|
2733
|
+
${retryBadge}
|
|
2734
|
+
${outcomeBadge}
|
|
2735
|
+
${
|
|
2736
|
+
test.tags && test.tags.length > 0
|
|
2737
|
+
? test.tags
|
|
2738
|
+
.map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
|
|
2739
|
+
.join(" ")
|
|
2740
|
+
: ""
|
|
2741
|
+
}
|
|
2742
|
+
</div>
|
|
2743
|
+
<div class="test-case-status-duration">
|
|
2744
|
+
<span class="status-badge ${getStatusClass(headerStatus)}">${String(
|
|
2745
|
+
headerStatus,
|
|
2746
|
+
).toUpperCase()}</span>
|
|
2747
|
+
<span class="test-duration">${formatDuration(test.duration)}</span>
|
|
2748
|
+
</div>
|
|
2749
|
+
</div>
|
|
2750
|
+
<div class="test-case-content" style="display: none;">
|
|
2751
|
+
${test.retryHistory && test.retryHistory.length > 0 ? `
|
|
2752
|
+
<div class="retry-tabs-container">
|
|
2753
|
+
<div class="retry-tabs-header">
|
|
2754
|
+
<button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
|
|
2755
|
+
Base Run ${getSmallStatusBadge(test.final_status || test.status)}
|
|
2756
|
+
</button>
|
|
2757
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2758
|
+
<button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
|
|
2759
|
+
Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
|
|
2760
|
+
</button>
|
|
2761
|
+
`).join('')}
|
|
2762
|
+
</div>
|
|
2763
|
+
|
|
2764
|
+
<div id="base-run-${test.id}" class="retry-tab-content active">
|
|
2765
|
+
${getTestContentHTML(test, 'base')}
|
|
2766
|
+
</div>
|
|
2767
|
+
|
|
2768
|
+
${test.retryHistory.map((retry, idx) => `
|
|
2769
|
+
<div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
|
|
2770
|
+
${getTestContentHTML(retry, `retry-${idx + 1}`)}
|
|
2771
|
+
</div>
|
|
2772
|
+
`).join('')}
|
|
2773
|
+
</div>
|
|
2774
|
+
` : getTestContentHTML(test, 'single')}
|
|
2465
2775
|
</div>
|
|
2466
2776
|
</div>`;
|
|
2467
2777
|
})
|
|
@@ -2475,17 +2785,54 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2475
2785
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2476
2786
|
<link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
|
|
2477
2787
|
<link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
|
|
2788
|
+
<!-- Preconnect to external domains -->
|
|
2789
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
|
|
2790
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2791
|
+
<link rel="preconnect" href="https://code.highcharts.com">
|
|
2792
|
+
|
|
2793
|
+
<!-- Preload critical font -->
|
|
2794
|
+
<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'">
|
|
2795
|
+
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
|
|
2796
|
+
|
|
2478
2797
|
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
2479
2798
|
<title>Pulse Report</title>
|
|
2480
2799
|
<style>
|
|
2481
2800
|
:root {
|
|
2482
|
-
--primary-color: #
|
|
2483
|
-
--
|
|
2484
|
-
--
|
|
2485
|
-
--
|
|
2486
|
-
--
|
|
2487
|
-
--
|
|
2488
|
-
|
|
2801
|
+
--primary-color: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
|
|
2802
|
+
--secondary-color: #8b5cf6; --secondary-dark: #7c3aed; --secondary-light: #a78bfa;
|
|
2803
|
+
--accent-color: #ec4899; --accent-alt: #06b6d4;
|
|
2804
|
+
--success-color: #10b981; --success-dark: #059669; --success-light: #34d399;
|
|
2805
|
+
--danger-color: #ef4444; --danger-dark: #dc2626; --danger-light: #f87171;
|
|
2806
|
+
--warning-color: #f59e0b; --warning-dark: #d97706; --warning-light: #fbbf24;
|
|
2807
|
+
--info-color: #3b82f6;
|
|
2808
|
+
--flaky-color: #00ccd3;
|
|
2809
|
+
--neutral-50: #fafafa; --neutral-100: #f5f5f5; --neutral-200: #e5e5e5; --neutral-300: #d4d4d4;
|
|
2810
|
+
--neutral-400: #a3a3a3; --neutral-500: #737373; --neutral-600: #525252; --neutral-700: #404040;
|
|
2811
|
+
--neutral-800: #262626; --neutral-900: #171717;
|
|
2812
|
+
--text-primary: #0f172a; --text-secondary: #475569; --text-tertiary: #94a3b8;
|
|
2813
|
+
--bg-primary: #ffffff; --bg-secondary: #f8fafc; --bg-tertiary: #f1f5f9;
|
|
2814
|
+
--border-light: #e2e8f0; --border-medium: #cbd5e1; --border-dark: #94a3b8;
|
|
2815
|
+
--font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
2816
|
+
--radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-2xl: 24px;
|
|
2817
|
+
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
2818
|
+
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
|
|
2819
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
|
2820
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
|
2821
|
+
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
2822
|
+
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
2823
|
+
--glow-primary: 0 0 20px rgba(99, 102, 241, 0.4), 0 0 40px rgba(99, 102, 241, 0.2);
|
|
2824
|
+
--glow-success: 0 0 20px rgba(16, 185, 129, 0.4), 0 0 40px rgba(16, 185, 129, 0.2);
|
|
2825
|
+
--glow-danger: 0 0 20px rgba(239, 68, 68, 0.4), 0 0 40px rgba(239, 68, 68, 0.2);
|
|
2826
|
+
--bg-card: #ffffff; --bg-card-hover: #f8fafc;
|
|
2827
|
+
--gradient-card: linear-gradient(145deg, #ffffff 0%, #f9fafb 100%);
|
|
2828
|
+
--border-medium: #cbd5e1;
|
|
2829
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2830
|
+
::selection { background: var(--primary-color); color: white; }
|
|
2831
|
+
::-webkit-scrollbar { width: 0; height: 0; display: none; }
|
|
2832
|
+
::-webkit-scrollbar-track { display: none; }
|
|
2833
|
+
::-webkit-scrollbar-thumb { display: none; }
|
|
2834
|
+
::-webkit-scrollbar-thumb:hover { display: none; }
|
|
2835
|
+
* { scrollbar-width: none; -ms-overflow-style: none; }
|
|
2489
2836
|
.trend-chart-container, .test-history-trend div[id^="testHistoryChart-"] { min-height: 100px; }
|
|
2490
2837
|
.lazy-load-chart .no-data, .lazy-load-chart .no-data-chart { display: flex; align-items: center; justify-content: center; height: 100%; font-style: italic; color: var(--dark-gray-color); }
|
|
2491
2838
|
.highcharts-background { fill: transparent; }
|
|
@@ -2493,79 +2840,1187 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2493
2840
|
.highcharts-axis-labels text, .highcharts-legend-item text { fill: var(--text-color-secondary) !important; font-size: 12px !important; }
|
|
2494
2841
|
.highcharts-axis-title { fill: var(--text-color) !important; }
|
|
2495
2842
|
.highcharts-tooltip > span { background-color: rgba(10,10,10,0.92) !important; border-color: rgba(10,10,10,0.92) !important; color: #f5f5f5 !important; padding: 10px !important; border-radius: 6px !important; }
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2843
|
+
html {
|
|
2844
|
+
overflow-x: hidden;
|
|
2845
|
+
}
|
|
2846
|
+
body {
|
|
2847
|
+
font-family: var(--font-family);
|
|
2848
|
+
margin: 0;
|
|
2849
|
+
background: #fafbfc;
|
|
2850
|
+
color: var(--text-primary);
|
|
2851
|
+
line-height: 1.6;
|
|
2852
|
+
font-size: 15px;
|
|
2853
|
+
min-height: 100vh;
|
|
2854
|
+
overflow-x: hidden;
|
|
2855
|
+
-webkit-font-smoothing: antialiased;
|
|
2856
|
+
-moz-osx-font-smoothing: grayscale;
|
|
2857
|
+
text-rendering: optimizeLegibility;
|
|
2858
|
+
}
|
|
2859
|
+
* {
|
|
2860
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
2861
|
+
will-change: transform, opacity;
|
|
2862
|
+
}
|
|
2863
|
+
*:not(input):not(select):not(textarea):not(button) {
|
|
2864
|
+
transition-duration: 0.15s;
|
|
2865
|
+
}
|
|
2866
|
+
.container {
|
|
2867
|
+
padding: 0;
|
|
2868
|
+
margin: 0;
|
|
2869
|
+
max-width: 100%;
|
|
2870
|
+
overflow-x: hidden;
|
|
2871
|
+
}
|
|
2872
|
+
.header {
|
|
2873
|
+
display: flex;
|
|
2874
|
+
justify-content: space-between;
|
|
2875
|
+
align-items: center;
|
|
2876
|
+
padding: 32px 40px 28px 40px;
|
|
2877
|
+
border-bottom: 1px solid #e2e8f0;
|
|
2878
|
+
background: rgba(255, 255, 255, 0.95);
|
|
2879
|
+
}
|
|
2880
|
+
.header-title {
|
|
2881
|
+
display: flex;
|
|
2882
|
+
align-items: center;
|
|
2883
|
+
gap: 20px;
|
|
2884
|
+
}
|
|
2885
|
+
.header h1 {
|
|
2886
|
+
margin: 0;
|
|
2887
|
+
font-size: 2.5em;
|
|
2888
|
+
font-weight: 900;
|
|
2889
|
+
color: #0f172a;
|
|
2890
|
+
line-height: 1;
|
|
2891
|
+
letter-spacing: -0.03em;
|
|
2892
|
+
}
|
|
2893
|
+
#report-logo {
|
|
2894
|
+
height: 60px;
|
|
2895
|
+
}
|
|
2896
|
+
.run-info {
|
|
2897
|
+
display: flex;
|
|
2898
|
+
gap: 16px;
|
|
2899
|
+
align-items: stretch;
|
|
2900
|
+
background: transparent;
|
|
2901
|
+
border-radius: 12px;
|
|
2902
|
+
padding: 0;
|
|
2903
|
+
box-shadow: var(--shadow-md); /* Inherited from base static style */
|
|
2904
|
+
overflow: hidden; /* Inherited */
|
|
2905
|
+
}
|
|
2906
|
+
.run-info-item {
|
|
2907
|
+
display: flex;
|
|
2908
|
+
flex-direction: column;
|
|
2909
|
+
gap: 8px;
|
|
2910
|
+
padding: 16px 28px;
|
|
2911
|
+
position: relative;
|
|
2912
|
+
flex: 1;
|
|
2913
|
+
min-width: fit-content;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
.run-info-item:first-child {
|
|
2917
|
+
background: linear-gradient(135deg, rgba(251, 191, 36, 0.2) 0%, rgba(245, 158, 11, 0.15) 50%, rgba(217, 119, 6, 0.1) 100%);
|
|
2918
|
+
border: 1px solid rgba(251, 191, 36, 0.3);
|
|
2919
|
+
border-radius: var(--radius-md);
|
|
2920
|
+
box-shadow: 0 4px 16px rgba(251, 191, 36, 0.2), inset 0 1px 0 rgba(251, 191, 36, 0.25), 0 0 40px rgba(251, 191, 36, 0.08);
|
|
2921
|
+
}
|
|
2922
|
+
.run-info-item:first-child:hover {
|
|
2923
|
+
background: linear-gradient(135deg, rgba(251, 191, 36, 0.28) 0%, rgba(245, 158, 11, 0.22) 50%, rgba(217, 119, 6, 0.15) 100%);
|
|
2924
|
+
border-color: rgba(251, 191, 36, 0.4);
|
|
2925
|
+
box-shadow: 0 8px 24px rgba(251, 191, 36, 0.3), inset 0 1px 0 rgba(251, 191, 36, 0.35), 0 0 50px rgba(251, 191, 36, 0.15);
|
|
2926
|
+
}
|
|
2927
|
+
.run-info-item:last-child {
|
|
2928
|
+
background: linear-gradient(135deg, rgba(139, 92, 246, 0.18) 0%, rgba(124, 58, 237, 0.12) 50%, rgba(109, 40, 217, 0.08) 100%);
|
|
2929
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
2930
|
+
border-radius: var(--radius-md);
|
|
2931
|
+
box-shadow: 0 4px 16px rgba(139, 92, 246, 0.2), inset 0 1px 0 rgba(139, 92, 246, 0.25), 0 0 40px rgba(139, 92, 246, 0.08);
|
|
2932
|
+
}
|
|
2933
|
+
.run-info-item:last-child:hover {
|
|
2934
|
+
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25) 0%, rgba(124, 58, 237, 0.18) 50%, rgba(109, 40, 217, 0.12) 100%);
|
|
2935
|
+
border-color: rgba(139, 92, 246, 0.4);
|
|
2936
|
+
box-shadow: 0 8px 24px rgba(139, 92, 246, 0.3), inset 0 1px 0 rgba(139, 92, 246, 0.35), 0 0 50px rgba(139, 92, 246, 0.15);
|
|
2937
|
+
}
|
|
2938
|
+
.run-info strong {
|
|
2939
|
+
display: flex;
|
|
2940
|
+
align-items: center;
|
|
2941
|
+
gap: 8px;
|
|
2942
|
+
font-size: 0.7em;
|
|
2943
|
+
text-transform: uppercase;
|
|
2944
|
+
letter-spacing: 1.2px;
|
|
2945
|
+
color: #9ca3af;
|
|
2946
|
+
margin: 0;
|
|
2947
|
+
font-weight: 700;
|
|
2948
|
+
}
|
|
2949
|
+
.run-info strong::before {
|
|
2950
|
+
content: '';
|
|
2951
|
+
width: 10px;
|
|
2952
|
+
height: 10px;
|
|
2953
|
+
border-radius: 50%;
|
|
2954
|
+
background: currentColor;
|
|
2955
|
+
opacity: 0.7;
|
|
2956
|
+
box-shadow: 0 0 8px currentColor;
|
|
2957
|
+
}
|
|
2958
|
+
.run-info-item:first-child strong {
|
|
2959
|
+
color: var(--warning-light);
|
|
2960
|
+
}
|
|
2961
|
+
.run-info-item:last-child strong {
|
|
2962
|
+
color: var(--secondary-light);
|
|
2963
|
+
}
|
|
2964
|
+
.run-info span {
|
|
2965
|
+
font-size: 1.5em;
|
|
2966
|
+
font-weight: 800;
|
|
2967
|
+
color: #0f172a; /* Adjusted for light theme consistency, static uses #f9fafb */
|
|
2968
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
2969
|
+
letter-spacing: -0.02em;
|
|
2970
|
+
line-height: 1.2;
|
|
2971
|
+
white-space: nowrap;
|
|
2972
|
+
}
|
|
2973
|
+
.tabs {
|
|
2974
|
+
display: flex;
|
|
2975
|
+
background: #0f172a;
|
|
2976
|
+
padding: 0;
|
|
2977
|
+
margin: 0;
|
|
2978
|
+
position: sticky;
|
|
2979
|
+
top: 0;
|
|
2980
|
+
z-index: 100;
|
|
2981
|
+
overflow-x: auto;
|
|
2982
|
+
-webkit-overflow-scrolling: touch;
|
|
2983
|
+
max-width: 100vw;
|
|
2984
|
+
width: 100%;
|
|
2985
|
+
}
|
|
2986
|
+
.tab-button {
|
|
2987
|
+
flex: 1 1 auto;
|
|
2988
|
+
padding: 24px 20px;
|
|
2989
|
+
background: transparent;
|
|
2990
|
+
border: none;
|
|
2991
|
+
cursor: pointer;
|
|
2992
|
+
font-size: 0.85em;
|
|
2993
|
+
font-weight: 700;
|
|
2994
|
+
color: #64748b;
|
|
2995
|
+
transition: all 0.2s ease;
|
|
2996
|
+
white-space: nowrap;
|
|
2997
|
+
text-transform: uppercase;
|
|
2998
|
+
letter-spacing: 1.2px;
|
|
2999
|
+
border-right: 1px solid #1e293b;
|
|
3000
|
+
min-width: 0;
|
|
3001
|
+
}
|
|
3002
|
+
.tab-button:last-child { border-right: none; }
|
|
3003
|
+
.tab-button:hover {
|
|
3004
|
+
background: #1e293b;
|
|
3005
|
+
color: #ffffff;
|
|
3006
|
+
}
|
|
3007
|
+
.tab-button.active {
|
|
3008
|
+
background: #6366f1;
|
|
3009
|
+
color: #ffffff;
|
|
3010
|
+
}
|
|
3011
|
+
.tab-content {
|
|
3012
|
+
display: none;
|
|
3013
|
+
animation: fadeIn 0.4s ease-out;
|
|
3014
|
+
overflow-x: hidden;
|
|
3015
|
+
max-width: 100%;
|
|
3016
|
+
}
|
|
3017
|
+
.tab-content.active {
|
|
3018
|
+
display: block;
|
|
3019
|
+
}
|
|
2510
3020
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
3021
|
+
|
|
3022
|
+
@media (max-width: 1200px) {
|
|
3023
|
+
.trend-charts-row {
|
|
3024
|
+
grid-template-columns: 1fr;
|
|
3025
|
+
}
|
|
3026
|
+
.dashboard-bottom-row {
|
|
3027
|
+
grid-template-columns: 1fr;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
|
|
3032
|
+
.stat-pill.flaky { color: #4b5563; }
|
|
3033
|
+
|
|
3034
|
+
.dashboard-grid {
|
|
3035
|
+
display: grid;
|
|
3036
|
+
grid-template-columns: repeat(4, 1fr);
|
|
3037
|
+
gap: 0;
|
|
3038
|
+
margin: 0 0 40px 0;
|
|
3039
|
+
}
|
|
3040
|
+
.stats-pill.failed { color: var(--danger-dark); }
|
|
3041
|
+
.stats-pill.flaky { color: #4b5563; }
|
|
3042
|
+
.browser-breakdown {
|
|
3043
|
+
display: flex;
|
|
3044
|
+
flex-direction: column;
|
|
3045
|
+
gap: 4px;
|
|
3046
|
+
margin-top: 6px;
|
|
3047
|
+
max-height: 150px;
|
|
3048
|
+
overflow-y: auto;
|
|
3049
|
+
padding-right: 4px;
|
|
3050
|
+
scrollbar-width: thin;
|
|
3051
|
+
scrollbar-color: #cbd5e1 #f1f5f9;
|
|
3052
|
+
}
|
|
3053
|
+
.browser-breakdown::-webkit-scrollbar {
|
|
3054
|
+
width: 6px;
|
|
3055
|
+
display: block;
|
|
3056
|
+
}
|
|
3057
|
+
.browser-breakdown::-webkit-scrollbar-track {
|
|
3058
|
+
background: #f1f5f9;
|
|
3059
|
+
border-radius: 3px;
|
|
3060
|
+
display: block;
|
|
3061
|
+
}
|
|
3062
|
+
.browser-breakdown::-webkit-scrollbar-thumb {
|
|
3063
|
+
background: #cbd5e1;
|
|
3064
|
+
border-radius: 3px;
|
|
3065
|
+
display: block;
|
|
3066
|
+
}
|
|
3067
|
+
.browser-breakdown::-webkit-scrollbar-thumb:hover {
|
|
3068
|
+
background: #94a3b8;
|
|
3069
|
+
display: block;
|
|
3070
|
+
}
|
|
3071
|
+
.browser-item {
|
|
3072
|
+
display: flex;
|
|
3073
|
+
justify-content: space-between;
|
|
3074
|
+
align-items: center;
|
|
3075
|
+
font-size: 0.95em;
|
|
3076
|
+
}
|
|
3077
|
+
.browser-name {
|
|
3078
|
+
font-weight: 700;
|
|
3079
|
+
color: #0f172a;
|
|
3080
|
+
text-transform: capitalize;
|
|
3081
|
+
font-size: 1.05em;
|
|
3082
|
+
white-space: nowrap;
|
|
3083
|
+
overflow: hidden;
|
|
3084
|
+
text-overflow: ellipsis;
|
|
3085
|
+
flex: 1;
|
|
3086
|
+
min-width: 0;
|
|
3087
|
+
margin-right: 8px;
|
|
3088
|
+
}
|
|
3089
|
+
.browser-stats {
|
|
3090
|
+
color: #64748b;
|
|
3091
|
+
white-space: nowrap;
|
|
3092
|
+
flex-shrink: 0;
|
|
3093
|
+
font-weight: 700;
|
|
3094
|
+
font-size: 0.95em;
|
|
3095
|
+
}
|
|
3096
|
+
.summary-card {
|
|
3097
|
+
padding: 36px 32px;
|
|
3098
|
+
text-align: left;
|
|
3099
|
+
background: rgba(255, 255, 255, 0.95);
|
|
3100
|
+
border: 1px solid #e2e8f0;
|
|
3101
|
+
transition: background 0.2s ease;
|
|
3102
|
+
border-right: 1px solid #e2e8f0;
|
|
3103
|
+
border-bottom: 1px solid #e2e8f0;
|
|
3104
|
+
}
|
|
3105
|
+
.summary-card:nth-child(4n) { border-right: none; }
|
|
3106
|
+
.summary-card h3 {
|
|
3107
|
+
margin: 0 0 12px;
|
|
3108
|
+
font-size: 0.7em;
|
|
3109
|
+
font-weight: 700;
|
|
3110
|
+
color: #94a3b8;
|
|
3111
|
+
text-transform: uppercase;
|
|
3112
|
+
letter-spacing: 1.2px;
|
|
3113
|
+
}
|
|
3114
|
+
.summary-card .value {
|
|
3115
|
+
font-size: 2.8em;
|
|
3116
|
+
font-weight: 900;
|
|
3117
|
+
margin: 0;
|
|
3118
|
+
line-height: 1;
|
|
3119
|
+
letter-spacing: -0.03em;
|
|
3120
|
+
}
|
|
3121
|
+
.summary-card .trend-percentage {
|
|
3122
|
+
font-size: 0.9em;
|
|
3123
|
+
color: #64748b;
|
|
3124
|
+
margin-top: 8px;
|
|
3125
|
+
font-weight: 600;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
@media (max-width: 1024px) {
|
|
3129
|
+
.header {
|
|
3130
|
+
padding: 32px 24px;
|
|
3131
|
+
flex-direction: column;
|
|
3132
|
+
gap: 24px;
|
|
3133
|
+
align-items: flex-start;
|
|
3134
|
+
}
|
|
3135
|
+
.run-info {
|
|
3136
|
+
flex-direction: column;
|
|
3137
|
+
gap: 0;
|
|
3138
|
+
width: 100%;
|
|
3139
|
+
border-radius: 14px;
|
|
3140
|
+
overflow: hidden;
|
|
3141
|
+
}
|
|
3142
|
+
.dashboard-grid {
|
|
3143
|
+
grid-template-columns: repeat(2, 1fr);
|
|
3144
|
+
}
|
|
3145
|
+
.summary-card:nth-child(2n) { border-right: none; }
|
|
3146
|
+
.summary-card:nth-child(n+7) { border-bottom: none; }
|
|
3147
|
+
.filters {
|
|
3148
|
+
padding: 24px;
|
|
3149
|
+
flex-wrap: wrap;
|
|
3150
|
+
gap: 12px;
|
|
3151
|
+
}
|
|
3152
|
+
.filters input {
|
|
3153
|
+
flex: 1 1 auto;
|
|
3154
|
+
min-width: 0;
|
|
3155
|
+
width: auto;
|
|
3156
|
+
}
|
|
3157
|
+
.filters select {
|
|
3158
|
+
flex: 0 0 auto;
|
|
3159
|
+
min-width: 0;
|
|
3160
|
+
width: auto;
|
|
3161
|
+
}
|
|
3162
|
+
.filters button {
|
|
3163
|
+
width: auto;
|
|
3164
|
+
flex: 0 0 auto;
|
|
3165
|
+
}
|
|
3166
|
+
.copy-btn {
|
|
3167
|
+
font-size: 0.75em;
|
|
3168
|
+
padding: 8px 16px;
|
|
3169
|
+
margin-left: 0;
|
|
3170
|
+
}
|
|
3171
|
+
.console-output-section h4 {
|
|
3172
|
+
flex-direction: column;
|
|
3173
|
+
align-items: flex-start;
|
|
3174
|
+
gap: 8px;
|
|
3175
|
+
}
|
|
3176
|
+
.log-wrapper {
|
|
3177
|
+
max-height: 300px;
|
|
3178
|
+
}
|
|
3179
|
+
.tabs {
|
|
3180
|
+
overflow-x: auto;
|
|
3181
|
+
}
|
|
3182
|
+
.tab-button {
|
|
3183
|
+
padding: 20px 24px;
|
|
3184
|
+
font-size: 0.75em;
|
|
3185
|
+
white-space: nowrap;
|
|
3186
|
+
}
|
|
3187
|
+
.tag {
|
|
3188
|
+
font-size: 0.65em;
|
|
3189
|
+
padding: 4px 10px;
|
|
3190
|
+
margin-right: 4px;
|
|
3191
|
+
margin-bottom: 4px;
|
|
3192
|
+
letter-spacing: 0.3px;
|
|
3193
|
+
}
|
|
3194
|
+
.test-case-header {
|
|
3195
|
+
grid-template-columns: 1fr;
|
|
3196
|
+
grid-template-rows: auto auto auto;
|
|
3197
|
+
gap: 12px;
|
|
3198
|
+
padding: 16px 20px;
|
|
3199
|
+
}
|
|
3200
|
+
.test-case-summary {
|
|
3201
|
+
grid-column: 1;
|
|
3202
|
+
grid-row: 1;
|
|
3203
|
+
flex-direction: column;
|
|
3204
|
+
align-items: flex-start;
|
|
3205
|
+
gap: 8px;
|
|
3206
|
+
width: 100%;
|
|
3207
|
+
max-width: 100%;
|
|
3208
|
+
overflow: hidden;
|
|
3209
|
+
}
|
|
3210
|
+
.test-case-title {
|
|
3211
|
+
width: 100%;
|
|
3212
|
+
max-width: 100%;
|
|
3213
|
+
}
|
|
3214
|
+
.test-case-browser {
|
|
3215
|
+
width: 100%;
|
|
3216
|
+
max-width: 100%;
|
|
3217
|
+
white-space: normal;
|
|
3218
|
+
}
|
|
3219
|
+
.test-case-meta {
|
|
3220
|
+
grid-column: 1;
|
|
3221
|
+
grid-row: 2;
|
|
3222
|
+
width: 100%;
|
|
3223
|
+
gap: 6px;
|
|
3224
|
+
}
|
|
3225
|
+
.test-case-status-duration {
|
|
3226
|
+
grid-column: 1;
|
|
3227
|
+
grid-row: 3;
|
|
3228
|
+
align-items: flex-start;
|
|
3229
|
+
}
|
|
3230
|
+
.test-case {
|
|
3231
|
+
margin: 0 0 12px 0;
|
|
3232
|
+
border-radius: 8px;
|
|
3233
|
+
}
|
|
3234
|
+
.test-case-content {
|
|
3235
|
+
padding: 20px;
|
|
3236
|
+
}
|
|
3237
|
+
.pie-chart-wrapper, .suites-widget, .trend-chart {
|
|
3238
|
+
padding: 32px 24px;
|
|
3239
|
+
}
|
|
3240
|
+
.test-history-grid {
|
|
3241
|
+
grid-template-columns: 1fr;
|
|
3242
|
+
}
|
|
3243
|
+
.ai-failure-cards-grid {
|
|
3244
|
+
grid-template-columns: 1fr;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
@media (max-width: 768px) {
|
|
3249
|
+
.header h1 { font-size: 1.8em; }
|
|
3250
|
+
#report-logo { height: 48px; }
|
|
3251
|
+
.tabs {
|
|
3252
|
+
flex-wrap: nowrap;
|
|
3253
|
+
gap: 0;
|
|
3254
|
+
overflow-x: auto;
|
|
3255
|
+
}
|
|
3256
|
+
.tab-button {
|
|
3257
|
+
padding: 16px 20px;
|
|
3258
|
+
font-size: 0.7em;
|
|
3259
|
+
flex: 1 1 auto;
|
|
3260
|
+
min-width: 100px;
|
|
3261
|
+
}
|
|
3262
|
+
.dashboard-grid {
|
|
3263
|
+
grid-template-columns: 1fr;
|
|
3264
|
+
}
|
|
3265
|
+
.summary-card {
|
|
3266
|
+
padding: 32px 24px !important;
|
|
3267
|
+
border-right: none !important;
|
|
3268
|
+
}
|
|
3269
|
+
.summary-card .value { font-size: 2.5em !important; }
|
|
3270
|
+
.dashboard-bottom-row {
|
|
3271
|
+
grid-template-columns: 1fr;
|
|
3272
|
+
gap: 0;
|
|
3273
|
+
}
|
|
3274
|
+
.dashboard-column {
|
|
3275
|
+
gap: 0;
|
|
3276
|
+
}
|
|
3277
|
+
.pie-chart-wrapper, .suites-widget, .trend-chart {
|
|
3278
|
+
padding: 28px 20px;
|
|
3279
|
+
}
|
|
3280
|
+
.pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
|
|
3281
|
+
font-size: 1.2em;
|
|
3282
|
+
margin-bottom: 20px;
|
|
3283
|
+
}
|
|
3284
|
+
.pie-chart-wrapper div[id^="pieChart-"] {
|
|
3285
|
+
width: 100% !important;
|
|
3286
|
+
max-width: 100% !important;
|
|
3287
|
+
min-height: 280px;
|
|
3288
|
+
overflow: visible !important;
|
|
3289
|
+
}
|
|
3290
|
+
.pie-chart-wrapper {
|
|
3291
|
+
overflow: visible !important;
|
|
3292
|
+
}
|
|
3293
|
+
.trend-chart-container {
|
|
3294
|
+
min-height: 280px;
|
|
3295
|
+
}
|
|
3296
|
+
.suites-grid {
|
|
3297
|
+
grid-template-columns: 1fr;
|
|
3298
|
+
}
|
|
3299
|
+
.test-case-summary {
|
|
3300
|
+
flex-direction: column;
|
|
3301
|
+
align-items: flex-start;
|
|
3302
|
+
gap: 8px;
|
|
3303
|
+
}
|
|
3304
|
+
.test-case-title {
|
|
3305
|
+
width: 100%;
|
|
3306
|
+
}
|
|
3307
|
+
.test-case-browser {
|
|
3308
|
+
width: 100%;
|
|
3309
|
+
}
|
|
3310
|
+
.test-case-meta {
|
|
3311
|
+
flex-wrap: wrap;
|
|
3312
|
+
gap: 6px;
|
|
3313
|
+
width: 100%;
|
|
3314
|
+
}
|
|
3315
|
+
.test-history-trend-section {
|
|
3316
|
+
padding: 0px 20px !important;
|
|
3317
|
+
}
|
|
3318
|
+
.ai-failure-cards-grid {
|
|
3319
|
+
grid-template-columns: 1fr;
|
|
3320
|
+
}
|
|
3321
|
+
.ai-analyzer-stats {
|
|
3322
|
+
flex-direction: column;
|
|
3323
|
+
gap: 15px;
|
|
3324
|
+
text-align: center;
|
|
3325
|
+
}
|
|
3326
|
+
.failure-header {
|
|
3327
|
+
flex-direction: column;
|
|
3328
|
+
align-items: stretch;
|
|
3329
|
+
gap: 15px;
|
|
3330
|
+
}
|
|
3331
|
+
.failure-main-info {
|
|
3332
|
+
text-align: center;
|
|
3333
|
+
}
|
|
3334
|
+
.failure-meta {
|
|
3335
|
+
justify-content: center;
|
|
3336
|
+
}
|
|
3337
|
+
.ai-buttons-group {
|
|
3338
|
+
flex-direction: column;
|
|
3339
|
+
width: 100%;
|
|
3340
|
+
}
|
|
3341
|
+
.compact-ai-btn, .copy-prompt-btn {
|
|
3342
|
+
justify-content: center;
|
|
3343
|
+
padding: 12px 20px;
|
|
3344
|
+
width: 100%;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
@media (max-width: 480px) {
|
|
3349
|
+
.header { padding: 24px 16px; }
|
|
3350
|
+
.header h1 { font-size: 1.5em; }
|
|
3351
|
+
#report-logo { height: 42px; }
|
|
3352
|
+
.run-info {
|
|
3353
|
+
flex-direction: column;
|
|
3354
|
+
gap: 12px;
|
|
3355
|
+
width: 100%;
|
|
3356
|
+
}
|
|
3357
|
+
.run-info-item {
|
|
3358
|
+
padding: 14px 20px;
|
|
3359
|
+
}
|
|
3360
|
+
.run-info-item:not(:last-child)::after {
|
|
3361
|
+
display: none;
|
|
3362
|
+
}
|
|
3363
|
+
.run-info-item:not(:last-child) {
|
|
3364
|
+
border-bottom: 1px solid var(--border-medium);
|
|
3365
|
+
}
|
|
3366
|
+
.run-info strong {
|
|
3367
|
+
font-size: 0.65em;
|
|
3368
|
+
}
|
|
3369
|
+
.run-info span {
|
|
3370
|
+
font-size: 1.1em;
|
|
3371
|
+
}
|
|
3372
|
+
.tabs {
|
|
3373
|
+
flex-wrap: wrap;
|
|
3374
|
+
gap: 4px;
|
|
3375
|
+
padding: 8px;
|
|
3376
|
+
}
|
|
3377
|
+
.tab-button {
|
|
3378
|
+
padding: 14px 10px;
|
|
3379
|
+
font-size: 0.6em;
|
|
3380
|
+
letter-spacing: 0.3px;
|
|
3381
|
+
flex: 1 1 calc(50% - 4px);
|
|
3382
|
+
min-width: 0;
|
|
3383
|
+
text-align: center;
|
|
3384
|
+
}
|
|
3385
|
+
.dashboard-grid { gap: 0; }
|
|
3386
|
+
.summary-card { padding: 28px 16px !important; }
|
|
3387
|
+
.summary-card h3 { font-size: 0.65em; }
|
|
3388
|
+
.summary-card .value { font-size: 2em !important; }
|
|
3389
|
+
.dashboard-bottom-row { gap: 0; }
|
|
3390
|
+
.dashboard-column {
|
|
3391
|
+
gap: 0;
|
|
3392
|
+
}
|
|
3393
|
+
.pie-chart-wrapper, .suites-widget, .trend-chart {
|
|
3394
|
+
padding: 20px 16px;
|
|
3395
|
+
}
|
|
3396
|
+
.pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
|
|
3397
|
+
font-size: 1em;
|
|
3398
|
+
margin-bottom: 16px;
|
|
3399
|
+
font-weight: 800;
|
|
3400
|
+
}
|
|
3401
|
+
.env-dashboard-title {
|
|
3402
|
+
font-size: 1em;
|
|
3403
|
+
margin-bottom: 6px;
|
|
3404
|
+
}
|
|
3405
|
+
.env-dashboard-subtitle {
|
|
3406
|
+
font-size: 0.85em;
|
|
3407
|
+
}
|
|
3408
|
+
.env-card-header {
|
|
3409
|
+
font-size: 0.85em;
|
|
3410
|
+
}
|
|
3411
|
+
.pie-chart-wrapper div[id^="pieChart-"] {
|
|
3412
|
+
width: 100% !important;
|
|
3413
|
+
max-width: 100% !important;
|
|
3414
|
+
min-height: 250px;
|
|
3415
|
+
overflow: visible !important;
|
|
3416
|
+
}
|
|
3417
|
+
.pie-chart-wrapper {
|
|
3418
|
+
overflow: visible !important;
|
|
3419
|
+
padding: 20px 12px;
|
|
3420
|
+
}
|
|
3421
|
+
.trend-chart-container {
|
|
3422
|
+
min-height: 250px;
|
|
3423
|
+
}
|
|
3424
|
+
.suites-grid {
|
|
3425
|
+
grid-template-columns: 1fr;
|
|
3426
|
+
gap: 16px;
|
|
3427
|
+
}
|
|
3428
|
+
.suite-card {
|
|
3429
|
+
padding: 16px;
|
|
3430
|
+
}
|
|
3431
|
+
.filters {
|
|
3432
|
+
padding: 16px;
|
|
3433
|
+
gap: 8px;
|
|
3434
|
+
}
|
|
3435
|
+
.test-history-trend-section {
|
|
3436
|
+
padding: 0px 16px !important;
|
|
3437
|
+
}
|
|
3438
|
+
.test-case {
|
|
3439
|
+
margin: 0 0 10px 0;
|
|
3440
|
+
border-radius: 6px;
|
|
3441
|
+
}
|
|
3442
|
+
.test-case-header {
|
|
3443
|
+
padding: 14px 16px;
|
|
3444
|
+
}
|
|
3445
|
+
.test-case-content {
|
|
3446
|
+
padding: 16px;
|
|
3447
|
+
}
|
|
3448
|
+
.stat-item .stat-number {
|
|
3449
|
+
font-size: 1.5em;
|
|
3450
|
+
}
|
|
3451
|
+
.failure-header {
|
|
3452
|
+
padding: 15px;
|
|
3453
|
+
}
|
|
3454
|
+
.failure-error-preview, .full-error-details {
|
|
3455
|
+
padding-left: 15px;
|
|
3456
|
+
padding-right: 15px;
|
|
3457
|
+
}
|
|
3458
|
+
.header h1 {
|
|
3459
|
+
word-break: break-word;
|
|
3460
|
+
overflow-wrap: break-word;
|
|
3461
|
+
}
|
|
3462
|
+
h2, h3, h4 {
|
|
3463
|
+
word-break: break-word;
|
|
3464
|
+
overflow-wrap: break-word;
|
|
3465
|
+
}
|
|
3466
|
+
.environment-dashboard-wrapper {
|
|
3467
|
+
padding: 24px 16px;
|
|
3468
|
+
gap: 24px;
|
|
3469
|
+
}
|
|
3470
|
+
.env-card {
|
|
3471
|
+
padding: 20px;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
.summary-card.status-passed { background: rgba(16, 185, 129, 0.02); }
|
|
3475
|
+
.summary-card.status-passed:hover {
|
|
3476
|
+
background: rgba(16, 185, 129, 0.15);
|
|
3477
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
3478
|
+
}
|
|
3479
|
+
.summary-card.status-passed .value { color: #10b981; }
|
|
3480
|
+
.summary-card.status-failed { background: rgba(239, 68, 68, 0.02); }
|
|
3481
|
+
.summary-card.status-failed:hover {
|
|
3482
|
+
background: rgba(239, 68, 68, 0.15);
|
|
3483
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
3484
|
+
}
|
|
3485
|
+
.summary-card.status-failed .value { color: #ef4444; }
|
|
3486
|
+
.summary-card.status-flaky::before { background: #00ccd3; }
|
|
3487
|
+
.summary-card.status-skipped { background: rgba(245, 158, 11, 0.02); }
|
|
3488
|
+
.summary-card.status-skipped:hover {
|
|
3489
|
+
background: rgba(245, 158, 11, 0.15);
|
|
3490
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
|
|
3491
|
+
}
|
|
3492
|
+
.summary-card.status-skipped .value { color: #f59e0b; }
|
|
3493
|
+
.summary-card.flaky-status { background: rgba(0, 204, 211, 0.05); }
|
|
3494
|
+
.summary-card.flaky-status:hover {
|
|
3495
|
+
background: rgba(0, 204, 211, 0.15);
|
|
3496
|
+
box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
|
|
3497
|
+
}
|
|
3498
|
+
.summary-card.flaky-status .value { color: #00ccd3; }
|
|
3499
|
+
.summary-card:not([class*='status-']) .value { color: #0f172a; }
|
|
2520
3500
|
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: start; }
|
|
2521
|
-
.
|
|
2522
|
-
|
|
2523
|
-
|
|
3501
|
+
.dashboard-column {
|
|
3502
|
+
display: flex;
|
|
3503
|
+
flex-direction: column;
|
|
3504
|
+
gap: 28px;
|
|
3505
|
+
}
|
|
3506
|
+
.pie-chart-wrapper, .suites-widget, .trend-chart {
|
|
3507
|
+
background: rgba(255, 255, 255, 0.95);
|
|
3508
|
+
padding: 48px;
|
|
3509
|
+
border: 1px solid #e2e8f0;
|
|
3510
|
+
border-radius: 16px;
|
|
3511
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
3512
|
+
display: flex;
|
|
3513
|
+
flex-direction: column;
|
|
3514
|
+
overflow: visible;
|
|
3515
|
+
margin-bottom: 24px;
|
|
3516
|
+
}
|
|
3517
|
+
.pie-chart-wrapper {
|
|
3518
|
+
position: relative;
|
|
3519
|
+
}
|
|
3520
|
+
.pie-chart-wrapper h3, .suites-header h2, .trend-chart h3, .chart-title-header {
|
|
3521
|
+
text-align: left;
|
|
3522
|
+
margin: 0 0 40px 0;
|
|
3523
|
+
font-size: 1.8em;
|
|
3524
|
+
font-weight: 900;
|
|
3525
|
+
color: #0f172a;
|
|
3526
|
+
letter-spacing: -0.02em;
|
|
3527
|
+
}
|
|
3528
|
+
.trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] {
|
|
3529
|
+
flex-grow: 1;
|
|
3530
|
+
min-height: 250px;
|
|
3531
|
+
width: 100%;
|
|
3532
|
+
overflow: visible;
|
|
3533
|
+
}
|
|
2524
3534
|
.status-badge-small-tooltip { padding: 2px 5px; border-radius: 3px; font-size: 0.9em; font-weight: 600; color: white; text-transform: uppercase; }
|
|
2525
3535
|
.status-badge-small-tooltip.status-passed { background-color: var(--success-color); }
|
|
2526
3536
|
.status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
|
|
2527
3537
|
.status-badge-small-tooltip.status-skipped { background-color: var(--warning-color); }
|
|
2528
3538
|
.status-badge-small-tooltip.status-unknown { background-color: var(--dark-gray-color); }
|
|
2529
|
-
.suites-header {
|
|
3539
|
+
.suites-header {
|
|
3540
|
+
flex-shrink: 0;
|
|
3541
|
+
display: flex;
|
|
3542
|
+
justify-content: space-between;
|
|
3543
|
+
align-items: center;
|
|
3544
|
+
margin-bottom: 20px;
|
|
3545
|
+
}
|
|
2530
3546
|
.summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
2531
3547
|
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
2532
|
-
.
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
.
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
.
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
.
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
.
|
|
2565
|
-
|
|
3548
|
+
.suites-widget {
|
|
3549
|
+
display: flex;
|
|
3550
|
+
flex-direction: column;
|
|
3551
|
+
}
|
|
3552
|
+
.fixed-height-widget {
|
|
3553
|
+
height: 450px;
|
|
3554
|
+
}
|
|
3555
|
+
.suites-grid-container {
|
|
3556
|
+
flex-grow: 1;
|
|
3557
|
+
overflow-y: auto;
|
|
3558
|
+
padding-right: 5px;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
@media (max-width: 768px) {
|
|
3562
|
+
.fixed-height-widget {
|
|
3563
|
+
height: auto;
|
|
3564
|
+
max-height: 600px;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
.suite-card {
|
|
3568
|
+
background: #ffffff;
|
|
3569
|
+
border: 1px solid var(--border-light);
|
|
3570
|
+
border-radius: 16px;
|
|
3571
|
+
padding: 24px;
|
|
3572
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
|
3573
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3574
|
+
display: flex;
|
|
3575
|
+
flex-direction: column;
|
|
3576
|
+
height: 100%;
|
|
3577
|
+
position: relative;
|
|
3578
|
+
overflow: hidden;
|
|
3579
|
+
}
|
|
3580
|
+
.suite-card:hover {
|
|
3581
|
+
transform: translateY(-4px);
|
|
3582
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
3583
|
+
border-color: var(--primary-light);
|
|
3584
|
+
}
|
|
3585
|
+
.suite-card::before {
|
|
3586
|
+
content: '';
|
|
3587
|
+
position: absolute;
|
|
3588
|
+
top: 0;
|
|
3589
|
+
left: 0;
|
|
3590
|
+
width: 100%;
|
|
3591
|
+
height: 4px;
|
|
3592
|
+
background: var(--neutral-200);
|
|
3593
|
+
opacity: 0.8;
|
|
3594
|
+
transition: background 0.3s ease;
|
|
3595
|
+
}
|
|
3596
|
+
.suite-card.status-passed::before { background: var(--success-color); }
|
|
3597
|
+
.suite-card.status-failed::before { background: var(--danger-color); }
|
|
3598
|
+
.suite-card.status-flaky::before { background: #00ccd3; }
|
|
3599
|
+
.suite-card.status-skipped::before { background: var(--warning-color); }
|
|
3600
|
+
|
|
3601
|
+
/* Outcome Badge */
|
|
3602
|
+
.outcome-badge {
|
|
3603
|
+
background-color: var(--secondary-color);
|
|
3604
|
+
color: #fff;
|
|
3605
|
+
padding: 2px 8px;
|
|
3606
|
+
border-radius: 4px;
|
|
3607
|
+
font-size: 0.75em;
|
|
3608
|
+
font-weight: 700;
|
|
3609
|
+
text-transform: uppercase;
|
|
3610
|
+
margin-right: 8px;
|
|
3611
|
+
letter-spacing: 0.5px;
|
|
3612
|
+
}
|
|
3613
|
+
.outcome-badge.flaky {
|
|
3614
|
+
background-color: #00ccd3;
|
|
3615
|
+
color: #fff;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
.suite-card-header {
|
|
3619
|
+
display: flex;
|
|
3620
|
+
justify-content: space-between;
|
|
3621
|
+
align-items: flex-start;
|
|
3622
|
+
margin-bottom: 16px;
|
|
3623
|
+
}
|
|
3624
|
+
.suite-name {
|
|
3625
|
+
font-size: 1.15em;
|
|
3626
|
+
font-weight: 700;
|
|
3627
|
+
color: var(--text-primary);
|
|
3628
|
+
line-height: 1.4;
|
|
3629
|
+
display: -webkit-box;
|
|
3630
|
+
-webkit-line-clamp: 2;
|
|
3631
|
+
-webkit-box-orient: vertical;
|
|
3632
|
+
overflow: hidden;
|
|
3633
|
+
margin-right: 12px;
|
|
3634
|
+
}
|
|
3635
|
+
.status-indicator-dot {
|
|
3636
|
+
width: 10px;
|
|
3637
|
+
height: 10px;
|
|
3638
|
+
border-radius: 50%;
|
|
3639
|
+
flex-shrink: 0;
|
|
3640
|
+
margin-top: 6px;
|
|
3641
|
+
}
|
|
3642
|
+
.status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
|
|
3643
|
+
.status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
|
|
3644
|
+
.status-indicator-dot.status-flaky { background-color: #00ccd3; box-shadow: 0 0 0 4px rgba(0, 204, 211, 0.15); }
|
|
3645
|
+
.status-indicator-dot.status-skipped { background-color: rgba(245, 158, 11, 0.1); color: var(--warning-dark); border: 1px solid rgba(245, 158, 11, 0.2); }
|
|
3646
|
+
.status-flaky { background-color: rgba(0, 204, 211, 0.1); color: #00ccd3; border: 1px solid #00ccd3; }
|
|
3647
|
+
|
|
3648
|
+
.browser-tag {
|
|
3649
|
+
font-size: 0.8em;
|
|
3650
|
+
font-weight: 600;
|
|
3651
|
+
background: var(--bg-secondary);
|
|
3652
|
+
color: var(--text-secondary);
|
|
3653
|
+
padding: 4px 10px;
|
|
3654
|
+
border-radius: 20px;
|
|
3655
|
+
border: 1px solid var(--border-light);
|
|
3656
|
+
display: inline-flex;
|
|
3657
|
+
align-items: center;
|
|
3658
|
+
gap: 6px;
|
|
3659
|
+
margin-bottom: 20px;
|
|
3660
|
+
align-self: flex-start;
|
|
3661
|
+
box-shadow: none;
|
|
3662
|
+
text-shadow: none;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
.suite-card-body {
|
|
3666
|
+
margin-top: auto;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
.test-count-label {
|
|
3670
|
+
font-size: 0.85em;
|
|
3671
|
+
font-weight: 600;
|
|
3672
|
+
color: var(--text-tertiary);
|
|
3673
|
+
text-transform: uppercase;
|
|
3674
|
+
letter-spacing: 0.05em;
|
|
3675
|
+
margin-bottom: 8px;
|
|
3676
|
+
display: block;
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
.suite-stats {
|
|
3680
|
+
display: flex;
|
|
3681
|
+
gap: 8px;
|
|
3682
|
+
background: var(--bg-secondary);
|
|
3683
|
+
padding: 10px 14px;
|
|
3684
|
+
border-radius: 10px;
|
|
3685
|
+
justify-content: space-between;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
.stat-pill {
|
|
3689
|
+
display: flex;
|
|
3690
|
+
align-items: center;
|
|
3691
|
+
gap: 6px;
|
|
3692
|
+
font-size: 0.9em;
|
|
3693
|
+
font-weight: 600;
|
|
3694
|
+
}
|
|
3695
|
+
.stat-pill svg { width: 14px; height: 14px; }
|
|
3696
|
+
.stat-pill.passed { color: var(--success-dark); }
|
|
3697
|
+
.stat-pill.failed { color: var(--danger-dark); }
|
|
3698
|
+
.stat-pill.flaky { color: #00ccd3; }
|
|
3699
|
+
.stat-pill.skipped { color: var(--warning-dark); }
|
|
3700
|
+
.filters {
|
|
3701
|
+
display: flex;
|
|
3702
|
+
flex-wrap: wrap;
|
|
3703
|
+
gap: 12px;
|
|
3704
|
+
margin: 0;
|
|
3705
|
+
padding: 24px 32px;
|
|
3706
|
+
background: #f8fafc;
|
|
3707
|
+
border-bottom: 1px solid #e2e8f0;
|
|
3708
|
+
}
|
|
3709
|
+
.filters input, .filters select, .filters button {
|
|
3710
|
+
padding: 14px 18px;
|
|
3711
|
+
border: 2px solid #e2e8f0;
|
|
3712
|
+
font-size: 0.9em;
|
|
3713
|
+
font-family: var(--font-family);
|
|
3714
|
+
font-weight: 600;
|
|
3715
|
+
transition: all 0.15s ease;
|
|
3716
|
+
}
|
|
3717
|
+
.filters input {
|
|
3718
|
+
flex: 1 1 300px;
|
|
3719
|
+
min-width: 0;
|
|
3720
|
+
background: white;
|
|
3721
|
+
}
|
|
3722
|
+
.filters input:focus {
|
|
3723
|
+
outline: none;
|
|
3724
|
+
border-color: #6366f1;
|
|
3725
|
+
}
|
|
3726
|
+
.filters select {
|
|
3727
|
+
flex: 0 0 auto;
|
|
3728
|
+
min-width: 180px;
|
|
3729
|
+
background: white;
|
|
3730
|
+
cursor: pointer;
|
|
3731
|
+
width: 100%;
|
|
3732
|
+
}
|
|
3733
|
+
.filters select:focus {
|
|
3734
|
+
outline: none;
|
|
3735
|
+
border-color: #6366f1;
|
|
3736
|
+
}
|
|
3737
|
+
.filters button {
|
|
3738
|
+
background: #0f172a;
|
|
3739
|
+
color: white;
|
|
3740
|
+
cursor: pointer;
|
|
3741
|
+
border: none;
|
|
3742
|
+
font-weight: 700;
|
|
3743
|
+
padding: 14px 32px;
|
|
3744
|
+
text-transform: uppercase;
|
|
3745
|
+
letter-spacing: 0.5px;
|
|
3746
|
+
font-size: 0.8em;
|
|
3747
|
+
flex: 0 0 auto;
|
|
3748
|
+
}
|
|
3749
|
+
.filters button:hover {
|
|
3750
|
+
background: #1e293b;
|
|
3751
|
+
color: white;
|
|
3752
|
+
}
|
|
3753
|
+
.test-case {
|
|
3754
|
+
margin: 0 0 16px 0;
|
|
3755
|
+
padding: 0;
|
|
3756
|
+
border: 1px solid #e2e8f0;
|
|
3757
|
+
border-radius: 12px;
|
|
3758
|
+
background: rgba(255, 255, 255, 0.95);
|
|
3759
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
3760
|
+
transition: transform 0.2s ease;
|
|
3761
|
+
overflow: hidden;
|
|
3762
|
+
}
|
|
3763
|
+
.test-case:hover {
|
|
3764
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
3765
|
+
transform: translateY(-2px);
|
|
3766
|
+
border-color: #cbd5e1;
|
|
3767
|
+
}
|
|
3768
|
+
.test-case:last-child {
|
|
3769
|
+
margin-bottom: 0;
|
|
3770
|
+
}
|
|
3771
|
+
.test-case-header {
|
|
3772
|
+
padding: 20px 24px;
|
|
3773
|
+
background: linear-gradient(to right, #ffffff 0%, #f8fafc 100%);
|
|
3774
|
+
cursor: pointer;
|
|
3775
|
+
display: grid;
|
|
3776
|
+
grid-template-columns: 1fr auto;
|
|
3777
|
+
grid-template-rows: auto auto;
|
|
3778
|
+
gap: 12px 20px;
|
|
3779
|
+
transition: all 0.2s ease;
|
|
3780
|
+
border-bottom: 2px solid #f1f5f9;
|
|
3781
|
+
position: relative;
|
|
3782
|
+
}
|
|
3783
|
+
.test-case-header::before {
|
|
3784
|
+
content: '';
|
|
3785
|
+
position: absolute;
|
|
3786
|
+
left: 0;
|
|
3787
|
+
top: 0;
|
|
3788
|
+
bottom: 0;
|
|
3789
|
+
width: 4px;
|
|
3790
|
+
background: transparent;
|
|
3791
|
+
transition: background 0.2s ease;
|
|
3792
|
+
}
|
|
3793
|
+
.test-case-header:hover::before {
|
|
3794
|
+
background: linear-gradient(to bottom, #6366f1 0%, #8b5cf6 100%);
|
|
3795
|
+
}
|
|
3796
|
+
.test-case-header[aria-expanded="true"] {
|
|
3797
|
+
background: linear-gradient(to right, #f8fafc 0%, #f1f5f9 100%);
|
|
3798
|
+
border-bottom-color: #e2e8f0;
|
|
3799
|
+
}
|
|
3800
|
+
.test-case-header[aria-expanded="true"]::before {
|
|
3801
|
+
background: linear-gradient(to bottom, #6366f1 0%, #8b5cf6 100%);
|
|
3802
|
+
}
|
|
3803
|
+
.test-case-summary {
|
|
3804
|
+
display: flex;
|
|
3805
|
+
align-items: center;
|
|
3806
|
+
gap: 14px;
|
|
3807
|
+
flex-wrap: wrap;
|
|
3808
|
+
min-width: 0;
|
|
3809
|
+
grid-column: 1;
|
|
3810
|
+
grid-row: 1;
|
|
3811
|
+
}
|
|
3812
|
+
.test-case-title {
|
|
3813
|
+
font-weight: 600;
|
|
3814
|
+
color: var(--text-color);
|
|
3815
|
+
font-size: 1em;
|
|
3816
|
+
word-break: break-word;
|
|
3817
|
+
overflow-wrap: break-word;
|
|
3818
|
+
flex: 1 1 auto;
|
|
3819
|
+
min-width: 0;
|
|
3820
|
+
}
|
|
3821
|
+
.test-case-browser {
|
|
3822
|
+
font-size: 0.9em;
|
|
3823
|
+
color: var(--text-color-secondary);
|
|
3824
|
+
word-break: break-word;
|
|
3825
|
+
overflow-wrap: break-word;
|
|
3826
|
+
max-width: 100%;
|
|
3827
|
+
}
|
|
3828
|
+
.test-case-meta {
|
|
3829
|
+
display: flex;
|
|
3830
|
+
align-items: center;
|
|
3831
|
+
gap: 8px;
|
|
3832
|
+
font-size: 0.9em;
|
|
3833
|
+
color: var(--text-color-secondary);
|
|
3834
|
+
flex-wrap: wrap;
|
|
3835
|
+
min-width: 0;
|
|
3836
|
+
grid-column: 1;
|
|
3837
|
+
grid-row: 2;
|
|
3838
|
+
}
|
|
3839
|
+
.test-case-status-duration {
|
|
3840
|
+
display: flex;
|
|
3841
|
+
flex-direction: column;
|
|
3842
|
+
align-items: flex-end;
|
|
3843
|
+
gap: 8px;
|
|
3844
|
+
grid-column: 2;
|
|
3845
|
+
grid-row: 1 / 3;
|
|
3846
|
+
align-self: center;
|
|
3847
|
+
}
|
|
3848
|
+
.test-duration {
|
|
3849
|
+
background-color: var(--light-gray-color);
|
|
3850
|
+
padding: 6px 12px;
|
|
3851
|
+
border-radius: 8px;
|
|
3852
|
+
font-size: 0.9em;
|
|
3853
|
+
white-space: nowrap;
|
|
3854
|
+
flex-shrink: 0;
|
|
3855
|
+
font-weight: 700;
|
|
3856
|
+
color: #0f172a;
|
|
3857
|
+
}
|
|
3858
|
+
.status-badge {
|
|
3859
|
+
padding: 8px 20px;
|
|
3860
|
+
border-radius: 0;
|
|
3861
|
+
font-size: 0.7em;
|
|
3862
|
+
font-weight: 800;
|
|
3863
|
+
color: black;
|
|
3864
|
+
text-transform: uppercase;
|
|
3865
|
+
min-width: 100px;
|
|
3866
|
+
text-align: center;
|
|
3867
|
+
letter-spacing: 1px;
|
|
3868
|
+
}
|
|
3869
|
+
.status-badge.status-passed { background: #10b981; }
|
|
3870
|
+
.status-badge.status-failed { background: #ef4444; }
|
|
3871
|
+
.status-badge.status-skipped { background: #f59e0b; }
|
|
3872
|
+
.status-badge.status-unknown { background: #64748b; }
|
|
3873
|
+
|
|
3874
|
+
/* --- NEON GLASS SEVERITY BADGES --- */
|
|
3875
|
+
.severity-badge {
|
|
3876
|
+
display: inline-flex;
|
|
3877
|
+
align-items: center;
|
|
3878
|
+
gap: 6px;
|
|
3879
|
+
padding: 5px 12px;
|
|
3880
|
+
border-radius: 99px;
|
|
3881
|
+
font-size: 0.75em;
|
|
3882
|
+
font-weight: 700;
|
|
3883
|
+
text-transform: uppercase;
|
|
3884
|
+
letter-spacing: 0.05em;
|
|
3885
|
+
border: 1px solid;
|
|
3886
|
+
backdrop-filter: blur(4px);
|
|
3887
|
+
-webkit-backdrop-filter: blur(4px);
|
|
3888
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
3889
|
+
}
|
|
3890
|
+
.severity-badge::before {
|
|
3891
|
+
content: '';
|
|
3892
|
+
width: 6px;
|
|
3893
|
+
height: 6px;
|
|
3894
|
+
border-radius: 50%;
|
|
3895
|
+
background-color: currentColor;
|
|
3896
|
+
box-shadow: 0 0 6px currentColor;
|
|
3897
|
+
}
|
|
3898
|
+
/* Auto-map colors based on data-severity attribute */
|
|
3899
|
+
.severity-badge[data-severity="critical"] {
|
|
3900
|
+
color: #ff4d4d;
|
|
3901
|
+
background-color: rgba(255, 77, 77, 0.1);
|
|
3902
|
+
border-color: rgba(255, 77, 77, 0.25);
|
|
3903
|
+
}
|
|
3904
|
+
.severity-badge[data-severity="high"] {
|
|
3905
|
+
color: #fb923c;
|
|
3906
|
+
background-color: rgba(251, 146, 60, 0.1);
|
|
3907
|
+
border-color: rgba(251, 146, 60, 0.25);
|
|
3908
|
+
}
|
|
3909
|
+
.severity-badge[data-severity="medium"] {
|
|
3910
|
+
color: #facc15;
|
|
3911
|
+
background-color: rgba(250, 204, 21, 0.1);
|
|
3912
|
+
border-color: rgba(250, 204, 21, 0.25);
|
|
3913
|
+
}
|
|
3914
|
+
.severity-badge[data-severity="low"] {
|
|
3915
|
+
color: #4ade80;
|
|
3916
|
+
background-color: rgba(74, 222, 128, 0.1);
|
|
3917
|
+
border-color: rgba(74, 222, 128, 0.25);
|
|
3918
|
+
}
|
|
3919
|
+
.severity-badge[data-severity="minor"] {
|
|
3920
|
+
color: #94a3b8;
|
|
3921
|
+
background-color: rgba(148, 163, 184, 0.1);
|
|
3922
|
+
border-color: rgba(148, 163, 184, 0.25);
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
/* --- RETRY COUNT BADGE --- */
|
|
3926
|
+
.retry-badge {
|
|
3927
|
+
display: inline-flex;
|
|
3928
|
+
align-items: center;
|
|
3929
|
+
padding: 5px 12px;
|
|
3930
|
+
border-radius: 12px;
|
|
3931
|
+
font-size: 0.75rem;
|
|
3932
|
+
font-weight: 600;
|
|
3933
|
+
background: rgba(147, 51, 234, 0.15);
|
|
3934
|
+
color: #a855f7;
|
|
3935
|
+
border: 1px solid rgba(147, 51, 234, 0.3);
|
|
3936
|
+
margin-left: 8px;
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
/* --- RETRY TABS --- */
|
|
3940
|
+
.retry-tabs-container {
|
|
3941
|
+
margin-top: 16px;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
.retry-tabs-header {
|
|
3945
|
+
display: flex;
|
|
3946
|
+
gap: 8px;
|
|
3947
|
+
border-bottom: 2px solid var(--border-medium);
|
|
3948
|
+
margin-bottom: 20px;
|
|
3949
|
+
flex-wrap: wrap;
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
.retry-tab {
|
|
3953
|
+
padding: 10px 20px;
|
|
3954
|
+
background: transparent;
|
|
3955
|
+
border: none;
|
|
3956
|
+
border-bottom: 3px solid transparent;
|
|
3957
|
+
cursor: pointer;
|
|
3958
|
+
font-size: 0.95rem;
|
|
3959
|
+
font-weight: 600;
|
|
3960
|
+
color: var(--text-color-secondary);
|
|
3961
|
+
transition: all 0.2s ease;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.retry-tab:hover {
|
|
3965
|
+
color: var(--primary-color);
|
|
3966
|
+
background: rgba(147, 51, 234, 0.05);
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
.retry-tab.active {
|
|
3970
|
+
color: #a855f7;
|
|
3971
|
+
border-bottom-color: #a855f7;
|
|
3972
|
+
background: rgba(147, 51, 234, 0.1);
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
.retry-tab-content {
|
|
3976
|
+
animation: fadeIn 0.3s ease-in;
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
@keyframes fadeIn {
|
|
3980
|
+
from { opacity: 0; }
|
|
3981
|
+
to { opacity: 1; }
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
.tag {
|
|
3985
|
+
display: inline-flex;
|
|
3986
|
+
align-items: center;
|
|
3987
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
3988
|
+
color: #ffffff;
|
|
3989
|
+
padding: 6px 14px;
|
|
3990
|
+
border-radius: 6px;
|
|
3991
|
+
font-size: 0.8em;
|
|
3992
|
+
margin-right: 8px;
|
|
3993
|
+
margin-bottom: 4px;
|
|
3994
|
+
font-weight: 600;
|
|
3995
|
+
text-transform: uppercase;
|
|
3996
|
+
letter-spacing: 0.5px;
|
|
3997
|
+
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.25);
|
|
3998
|
+
transition: all 0.2s ease;
|
|
3999
|
+
flex-shrink: 0;
|
|
4000
|
+
white-space: nowrap;
|
|
4001
|
+
}
|
|
4002
|
+
.tag:hover {
|
|
4003
|
+
box-shadow: 0 4px 10px rgba(99, 102, 241, 0.35);
|
|
4004
|
+
transform: translateY(-1px);
|
|
4005
|
+
}
|
|
4006
|
+
.test-case-content {
|
|
4007
|
+
display: none;
|
|
4008
|
+
padding: 24px;
|
|
4009
|
+
background: linear-gradient(to bottom, #ffffff 0%, #f9fafb 100%);
|
|
4010
|
+
border-top: 1px solid #e2e8f0;
|
|
4011
|
+
}
|
|
2566
4012
|
.test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
|
|
2567
4013
|
.test-case-content p { margin-bottom: 10px; font-size: 1em; }
|
|
2568
|
-
.test-error-summary {
|
|
4014
|
+
.test-error-summary {
|
|
4015
|
+
margin-bottom: 20px;
|
|
4016
|
+
padding: 14px;
|
|
4017
|
+
background-color: rgba(244,67,54,0.05);
|
|
4018
|
+
border: 1px solid rgba(244,67,54,0.2);
|
|
4019
|
+
border-left: 4px solid var(--danger-color);
|
|
4020
|
+
border-radius: 4px;
|
|
4021
|
+
display: flex;
|
|
4022
|
+
flex-direction: column;
|
|
4023
|
+
}
|
|
2569
4024
|
.test-error-summary h4 { color: var(--danger-color); margin-top:0;}
|
|
2570
4025
|
.test-error-summary pre { white-space: pre-wrap; word-break: break-all; color: var(--danger-color); font-size: 0.95em;}
|
|
2571
4026
|
.steps-list { margin: 18px 0; }
|
|
@@ -2577,10 +4032,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2577
4032
|
.step-duration { color: var(--dark-gray-color); font-size: 0.9em; }
|
|
2578
4033
|
.step-details { display: none; padding: 14px; margin-top: 8px; background: #fdfdfd; border-radius: 6px; font-size: 0.95em; border: 1px solid var(--light-gray-color); }
|
|
2579
4034
|
.step-info { margin-bottom: 8px; }
|
|
4035
|
+
.code-snippet-section { margin: 12px 0; }
|
|
4036
|
+
.code-snippet { background-color: #f8f9fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 12px; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 0.9em; line-height: 1.5; overflow-x: auto; color: #24292e; margin: 0; white-space: pre; }
|
|
2580
4037
|
.test-error-summary { color: var(--danger-color); margin-top: 12px; padding: 14px; background: rgba(244,67,54,0.05); border-radius: 4px; font-size: 0.95em; border-left: 3px solid var(--danger-color); }
|
|
2581
4038
|
.test-error-summary pre.stack-trace { margin-top: 10px; padding: 12px; background-color: rgba(0,0,0,0.03); border-radius: 4px; font-size:0.9em; max-height: 280px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
|
|
2582
4039
|
.step-hook { background-color: rgba(33,150,243,0.04); border-left: 3px solid var(--info-color) !important; }
|
|
2583
4040
|
.step-hook .step-title { font-style: italic; color: var(--info-color)}
|
|
4041
|
+
.failed-step-highlight { border-left: 4px solid var(--danger-color) !important; background-color: rgba(244,67,54,0.03); }
|
|
4042
|
+
.failed-step-highlight .step-header { background-color: rgba(244,67,54,0.05); border-color: rgba(244,67,54,0.3); }
|
|
4043
|
+
.failed-step-marker { display: inline-block; margin-left: 10px; padding: 2px 8px; background-color: var(--danger-color); color: white; border-radius: 4px; font-size: 0.85em; font-weight: 600; }
|
|
2584
4044
|
.nested-steps { margin-top: 12px; }
|
|
2585
4045
|
.attachments-section { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--light-gray-color); }
|
|
2586
4046
|
.attachments-section h4 { margin-top: 0; margin-bottom: 20px; font-size: 1.1em; color: var(--text-color); }
|
|
@@ -2609,10 +4069,24 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2609
4069
|
.test-history-header h3 { margin: 0; font-size: 1.15em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* This was h3, changed to p for consistency with user file */
|
|
2610
4070
|
.test-history-header p { font-weight: 500 } /* Added this */
|
|
2611
4071
|
.test-history-trend { margin-bottom: 20px; min-height: 110px; }
|
|
4072
|
+
.test-history-trend-section {
|
|
4073
|
+
padding: 0px 48px !important;
|
|
4074
|
+
}
|
|
4075
|
+
.test-history-trend-section .chart-title-header {
|
|
4076
|
+
margin: 0 0 20px 0 !important;
|
|
4077
|
+
}
|
|
2612
4078
|
.test-history-trend div[id^="testHistoryChart-"] { display: block; margin: 0 auto; max-width:100%; height: 100px; width: 320px; }
|
|
2613
4079
|
.test-history-details-collapsible summary { cursor: pointer; font-size: 1em; color: var(--primary-color); margin-bottom: 10px; font-weight:500; }
|
|
2614
4080
|
.test-history-details-collapsible summary:hover {text-decoration: underline;}
|
|
2615
|
-
.test-history-details
|
|
4081
|
+
.test-history-details {
|
|
4082
|
+
overflow-x: auto;
|
|
4083
|
+
max-width: 100%;
|
|
4084
|
+
}
|
|
4085
|
+
.test-history-details table {
|
|
4086
|
+
width: 100%;
|
|
4087
|
+
border-collapse: collapse;
|
|
4088
|
+
font-size: 0.95em;
|
|
4089
|
+
}
|
|
2616
4090
|
.test-history-details th, .test-history-details td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--light-gray-color); }
|
|
2617
4091
|
.test-history-details th { background-color: var(--light-gray-color); font-weight: 600; }
|
|
2618
4092
|
.status-badge-small { padding: 3px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 600; color: white; text-transform: uppercase; display: inline-block; }
|
|
@@ -2630,13 +4104,57 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2630
4104
|
.ai-failure-card-body { padding: 20px; }
|
|
2631
4105
|
.ai-fix-btn { background-color: var(--primary-color); color: white; border: none; padding: 10px 18px; font-size: 1em; font-weight: 600; border-radius: 6px; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; display: inline-flex; align-items: center; gap: 8px; }
|
|
2632
4106
|
.ai-fix-btn:hover { background-color: var(--accent-color); transform: translateY(-2px); }
|
|
2633
|
-
.ai-modal-overlay {
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
4107
|
+
.ai-modal-overlay {
|
|
4108
|
+
position: fixed;
|
|
4109
|
+
top: 0;
|
|
4110
|
+
left: 0;
|
|
4111
|
+
width: 100%;
|
|
4112
|
+
height: 100%;
|
|
4113
|
+
background-color: rgba(0,0,0,0.8);
|
|
4114
|
+
display: none;
|
|
4115
|
+
align-items: center;
|
|
4116
|
+
justify-content: center;
|
|
4117
|
+
z-index: 1050;
|
|
4118
|
+
animation: fadeIn 0.3s;
|
|
4119
|
+
}
|
|
4120
|
+
.ai-modal-content {
|
|
4121
|
+
background-color: var(--card-background-color);
|
|
4122
|
+
color: var(--text-color);
|
|
4123
|
+
border-radius: var(--border-radius);
|
|
4124
|
+
width: 90%;
|
|
4125
|
+
max-width: 800px;
|
|
4126
|
+
max-height: 90vh;
|
|
4127
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
4128
|
+
display: flex;
|
|
4129
|
+
flex-direction: column;
|
|
4130
|
+
overflow: hidden;
|
|
4131
|
+
}
|
|
4132
|
+
.ai-modal-header {
|
|
4133
|
+
padding: 18px 25px;
|
|
4134
|
+
border-bottom: 1px solid var(--border-color);
|
|
4135
|
+
display: flex;
|
|
4136
|
+
justify-content: space-between;
|
|
4137
|
+
align-items: center;
|
|
4138
|
+
}
|
|
4139
|
+
.ai-modal-header h3 {
|
|
4140
|
+
margin: 0;
|
|
4141
|
+
font-size: 1.25em;
|
|
4142
|
+
}
|
|
4143
|
+
.ai-modal-close {
|
|
4144
|
+
font-size: 2rem;
|
|
4145
|
+
font-weight: 300;
|
|
4146
|
+
cursor: pointer;
|
|
4147
|
+
color: var(--dark-gray-color);
|
|
4148
|
+
line-height: 1;
|
|
4149
|
+
transition: color 0.2s;
|
|
4150
|
+
}
|
|
4151
|
+
.ai-modal-close:hover {
|
|
4152
|
+
color: var(--danger-color);
|
|
4153
|
+
}
|
|
4154
|
+
.ai-modal-body {
|
|
4155
|
+
padding: 25px;
|
|
4156
|
+
overflow-y: auto;
|
|
4157
|
+
}
|
|
2640
4158
|
.ai-modal-body h4 { margin-top: 18px; margin-bottom: 10px; font-size: 1.1em; color: var(--primary-color); }
|
|
2641
4159
|
.ai-modal-body p { margin-bottom: 15px; }
|
|
2642
4160
|
.ai-loader { margin: 40px auto; border: 5px solid #f3f3f3; border-top: 5px solid var(--primary-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; }
|
|
@@ -2650,9 +4168,69 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2650
4168
|
.view-trace:hover { background: #2c5282; }
|
|
2651
4169
|
.download-trace { background: #e2e8f0; color: #2d3748; }
|
|
2652
4170
|
.download-trace:hover { background: #cbd5e0; }
|
|
2653
|
-
.filters button.clear-filters-btn {
|
|
2654
|
-
|
|
2655
|
-
|
|
4171
|
+
.filters button.clear-filters-btn {
|
|
4172
|
+
background-color: var(--medium-gray-color);
|
|
4173
|
+
color: var(--text-color);
|
|
4174
|
+
pointer-events: auto;
|
|
4175
|
+
cursor: pointer;
|
|
4176
|
+
width: 100%;
|
|
4177
|
+
}
|
|
4178
|
+
.filters button.clear-filters-btn:active,
|
|
4179
|
+
.filters button.clear-filters-btn:focus {
|
|
4180
|
+
background-color: var(--medium-gray-color);
|
|
4181
|
+
color: var(--text-color);
|
|
4182
|
+
transform: none;
|
|
4183
|
+
box-shadow: none;
|
|
4184
|
+
outline: none;
|
|
4185
|
+
}
|
|
4186
|
+
.copy-btn {
|
|
4187
|
+
color: #ffffff;
|
|
4188
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
4189
|
+
border: none;
|
|
4190
|
+
border-radius: 8px;
|
|
4191
|
+
cursor: pointer;
|
|
4192
|
+
font-size: 0.85em;
|
|
4193
|
+
font-weight: 600;
|
|
4194
|
+
padding: 10px 20px;
|
|
4195
|
+
transition: all 0.2s ease;
|
|
4196
|
+
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);
|
|
4197
|
+
text-transform: uppercase;
|
|
4198
|
+
letter-spacing: 0.5px;
|
|
4199
|
+
margin-left: auto;
|
|
4200
|
+
display: inline-flex;
|
|
4201
|
+
align-items: center;
|
|
4202
|
+
gap: 6px;
|
|
4203
|
+
}
|
|
4204
|
+
.copy-btn:hover {
|
|
4205
|
+
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
|
4206
|
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
|
4207
|
+
transform: translateY(-1px);
|
|
4208
|
+
}
|
|
4209
|
+
.copy-btn:active {
|
|
4210
|
+
transform: translateY(0);
|
|
4211
|
+
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.2);
|
|
4212
|
+
}
|
|
4213
|
+
.log-wrapper {
|
|
4214
|
+
max-width: 100%;
|
|
4215
|
+
overflow-x: auto;
|
|
4216
|
+
overflow-y: auto;
|
|
4217
|
+
max-height: 400px;
|
|
4218
|
+
border-radius: 8px;
|
|
4219
|
+
background: #2d2d2d;
|
|
4220
|
+
}
|
|
4221
|
+
.log-wrapper pre {
|
|
4222
|
+
margin: 0;
|
|
4223
|
+
white-space: pre;
|
|
4224
|
+
word-wrap: normal;
|
|
4225
|
+
overflow-wrap: normal;
|
|
4226
|
+
}
|
|
4227
|
+
.console-output-section h4 {
|
|
4228
|
+
display: flex;
|
|
4229
|
+
align-items: center;
|
|
4230
|
+
justify-content: space-between;
|
|
4231
|
+
gap: 16px;
|
|
4232
|
+
margin-bottom: 12px;
|
|
4233
|
+
}
|
|
2656
4234
|
/* Compact AI Failure Analyzer Styles */
|
|
2657
4235
|
.ai-analyzer-stats {
|
|
2658
4236
|
display: flex;
|
|
@@ -2852,6 +4430,62 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2852
4430
|
max-height: 300px;
|
|
2853
4431
|
overflow-y: auto;
|
|
2854
4432
|
}
|
|
4433
|
+
.ai-suggestion-container {
|
|
4434
|
+
margin-top: 15px;
|
|
4435
|
+
border-top: 2px solid #e2e8f0;
|
|
4436
|
+
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
|
4437
|
+
animation: slideDown 0.3s ease-out;
|
|
4438
|
+
}
|
|
4439
|
+
@keyframes slideDown {
|
|
4440
|
+
from {
|
|
4441
|
+
opacity: 0;
|
|
4442
|
+
max-height: 0;
|
|
4443
|
+
transform: translateY(-10px);
|
|
4444
|
+
}
|
|
4445
|
+
to {
|
|
4446
|
+
opacity: 1;
|
|
4447
|
+
max-height: 1000px;
|
|
4448
|
+
transform: translateY(0);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
.ai-suggestion-content {
|
|
4452
|
+
padding: 20px;
|
|
4453
|
+
}
|
|
4454
|
+
.ai-suggestion-header {
|
|
4455
|
+
display: flex;
|
|
4456
|
+
align-items: center;
|
|
4457
|
+
gap: 10px;
|
|
4458
|
+
margin-bottom: 15px;
|
|
4459
|
+
padding-bottom: 10px;
|
|
4460
|
+
border-bottom: 2px solid #6366f1;
|
|
4461
|
+
}
|
|
4462
|
+
.ai-suggestion-header h4 {
|
|
4463
|
+
margin: 0;
|
|
4464
|
+
color: #6366f1;
|
|
4465
|
+
font-size: 1.1em;
|
|
4466
|
+
font-weight: 700;
|
|
4467
|
+
}
|
|
4468
|
+
.ai-suggestion-body {
|
|
4469
|
+
color: #0f172a;
|
|
4470
|
+
line-height: 1.6;
|
|
4471
|
+
}
|
|
4472
|
+
.ai-suggestion-body h4 {
|
|
4473
|
+
color: #6366f1;
|
|
4474
|
+
margin-top: 15px;
|
|
4475
|
+
margin-bottom: 8px;
|
|
4476
|
+
font-size: 1em;
|
|
4477
|
+
}
|
|
4478
|
+
.ai-suggestion-body p {
|
|
4479
|
+
margin-bottom: 10px;
|
|
4480
|
+
}
|
|
4481
|
+
.ai-suggestion-body pre {
|
|
4482
|
+
background: #1e293b;
|
|
4483
|
+
color: #e2e8f0;
|
|
4484
|
+
padding: 12px;
|
|
4485
|
+
border-radius: 6px;
|
|
4486
|
+
overflow-x: auto;
|
|
4487
|
+
font-size: 0.9em;
|
|
4488
|
+
}
|
|
2855
4489
|
|
|
2856
4490
|
/* Responsive adjustments for compact design */
|
|
2857
4491
|
@media (max-width: 768px) {
|
|
@@ -2894,10 +4528,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2894
4528
|
}
|
|
2895
4529
|
}
|
|
2896
4530
|
|
|
2897
|
-
|
|
2898
|
-
@media (max-width: 992px) { .dashboard-bottom-row { grid-template-columns: 1fr; } .pie-chart-wrapper div[id^="pieChart-"] { max-width: 350px; margin: 0 auto; } .filters input { min-width: 180px; } .filters select { min-width: 150px; } }
|
|
2899
|
-
@media (max-width: 768px) { body { font-size: 15px; } .container { margin: 10px; padding: 20px; } .header { flex-direction: column; align-items: flex-start; gap: 15px; } .header h1 { font-size: 1.6em; } .run-info { text-align: left; font-size:0.9em; } .tabs { margin-bottom: 25px;} .tab-button { padding: 12px 20px; font-size: 1.05em;} .dashboard-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 18px;} .summary-card .value {font-size: 2em;} .summary-card h3 {font-size: 0.95em;} .filters { flex-direction: column; padding: 18px; gap: 12px;} .filters input, .filters select, .filters button {width: 100%; box-sizing: border-box;} .test-case-header { flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px; } .test-case-summary {gap: 10px;} .test-case-title {font-size: 1.05em;} .test-case-meta { flex-direction: row; flex-wrap: wrap; gap: 8px; margin-top: 8px;} .attachments-grid {grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px;} .test-history-grid {grid-template-columns: 1fr;} .pie-chart-wrapper {min-height: auto;} .ai-failure-cards-grid { grid-template-columns: 1fr; } }
|
|
2900
|
-
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 50px; } .tab-button {padding: 10px 15px; font-size: 1em;} .summary-card .value {font-size: 1.8em;} .attachments-grid {grid-template-columns: 1fr;} .step-item {padding-left: calc(var(--depth, 0) * 18px);} .test-case-content, .step-details {padding: 15px;} .trend-charts-row {gap: 20px;} .trend-chart {padding: 20px;} }
|
|
4531
|
+
|
|
2901
4532
|
</style>
|
|
2902
4533
|
</head>
|
|
2903
4534
|
<body>
|
|
@@ -2907,11 +4538,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2907
4538
|
<img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
|
|
2908
4539
|
<h1>Pulse Report</h1>
|
|
2909
4540
|
</div>
|
|
2910
|
-
<div class="run-info"
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
4541
|
+
<div class="run-info">
|
|
4542
|
+
<div class="run-info-item">
|
|
4543
|
+
<strong>Run Date</strong>
|
|
4544
|
+
<span>${formatDate(runSummary.timestamp)}</span>
|
|
4545
|
+
</div>
|
|
4546
|
+
<div class="run-info-item">
|
|
4547
|
+
<strong>Total Duration</strong>
|
|
4548
|
+
<span>${formatDuration(runSummary.duration)}</span>
|
|
4549
|
+
</div>
|
|
4550
|
+
</div>
|
|
2915
4551
|
</header>
|
|
2916
4552
|
<div class="tabs">
|
|
2917
4553
|
<button class="tab-button active" data-tab="dashboard">Dashboard</button>
|
|
@@ -2933,31 +4569,55 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2933
4569
|
<div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
|
|
2934
4570
|
runSummary.skipped || 0
|
|
2935
4571
|
}</div><div class="trend-percentage">${skipPercentage}%</div></div>
|
|
2936
|
-
<div class="summary-card"><h3>
|
|
2937
|
-
<div class="
|
|
2938
|
-
|
|
2939
|
-
|
|
4572
|
+
<div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
|
|
4573
|
+
<div class="trend-percentage">${flakyPercentage}%</div></div>
|
|
4574
|
+
<div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
|
|
4575
|
+
runSummary.duration,
|
|
4576
|
+
)}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
|
|
4577
|
+
<div class="summary-card">
|
|
4578
|
+
<h3>Total Retry Count</h3>
|
|
4579
|
+
<div class="value">${totalRetried}</div>
|
|
4580
|
+
<div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
|
|
4581
|
+
</div>
|
|
4582
|
+
<div class="summary-card">
|
|
4583
|
+
<h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
|
|
4584
|
+
<div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
|
|
4585
|
+
${browserBreakdown
|
|
4586
|
+
.slice(0, 3)
|
|
4587
|
+
.map(
|
|
4588
|
+
(b) =>
|
|
4589
|
+
`<div class="browser-item">
|
|
4590
|
+
<span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
|
|
4591
|
+
<span class="browser-stats">${b.percentage}% (${b.count})</span>
|
|
4592
|
+
</div>`,
|
|
4593
|
+
)
|
|
4594
|
+
.join("")}
|
|
4595
|
+
${
|
|
4596
|
+
browserBreakdown.length > 3
|
|
4597
|
+
? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid #e2e8f0; margin-top: 8px; padding-top: 8px;">
|
|
4598
|
+
<span>+${browserBreakdown.length - 3} more browsers</span>
|
|
4599
|
+
</div>`
|
|
4600
|
+
: ""
|
|
4601
|
+
}
|
|
4602
|
+
</div>
|
|
4603
|
+
</div>
|
|
2940
4604
|
</div>
|
|
2941
4605
|
<div class="dashboard-bottom-row">
|
|
2942
|
-
<div
|
|
4606
|
+
<div class="dashboard-column">
|
|
2943
4607
|
${generatePieChart(
|
|
2944
4608
|
[
|
|
2945
4609
|
{ label: "Passed", value: runSummary.passed },
|
|
2946
4610
|
{ label: "Failed", value: runSummary.failed },
|
|
4611
|
+
{ label: "Flaky", value: runSummary.flaky || 0 },
|
|
2947
4612
|
{ label: "Skipped", value: runSummary.skipped || 0 },
|
|
2948
4613
|
],
|
|
2949
4614
|
400,
|
|
2950
|
-
390
|
|
4615
|
+
390,
|
|
2951
4616
|
)}
|
|
2952
|
-
${
|
|
2953
|
-
runSummary.environment &&
|
|
2954
|
-
Object.keys(runSummary.environment).length > 0
|
|
2955
|
-
? generateEnvironmentDashboard(runSummary.environment)
|
|
2956
|
-
: '<div class="no-data">Environment data not available.</div>'
|
|
2957
|
-
}
|
|
4617
|
+
${generateEnvironmentSection(runSummary.environment)}
|
|
2958
4618
|
</div>
|
|
2959
4619
|
|
|
2960
|
-
<div
|
|
4620
|
+
<div class="dashboard-column">
|
|
2961
4621
|
${generateSuitesWidget(suitesData)}
|
|
2962
4622
|
${generateSeverityDistributionChart(results)}
|
|
2963
4623
|
</div>
|
|
@@ -2966,25 +4626,24 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2966
4626
|
<div id="test-runs" class="tab-content">
|
|
2967
4627
|
<div class="filters">
|
|
2968
4628
|
<input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
|
|
2969
|
-
<select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="skipped">Skipped</option></select>
|
|
4629
|
+
<select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="flaky">Flaky</option><option value="skipped">Skipped</option></select>
|
|
2970
4630
|
<select id="filter-browser"><option value="">All Browsers</option>${Array.from(
|
|
2971
4631
|
new Set(
|
|
2972
|
-
(results || []).map((test) => test.browser || "unknown")
|
|
2973
|
-
)
|
|
4632
|
+
(results || []).map((test) => test.browser || "unknown"),
|
|
4633
|
+
),
|
|
2974
4634
|
)
|
|
2975
4635
|
.map(
|
|
2976
4636
|
(browser) =>
|
|
2977
4637
|
`<option value="${sanitizeHTML(browser)}">${sanitizeHTML(
|
|
2978
|
-
browser
|
|
2979
|
-
)}</option
|
|
4638
|
+
browser,
|
|
4639
|
+
)}</option>`,
|
|
2980
4640
|
)
|
|
2981
4641
|
.join("")}</select>
|
|
2982
|
-
<button id="
|
|
4642
|
+
<button id="clear-run-summary-filters" class="clear-filters-btn">Clear Filters</button>
|
|
2983
4643
|
</div>
|
|
2984
4644
|
<div class="test-cases-list">${generateTestCasesHTML()}</div>
|
|
2985
4645
|
</div>
|
|
2986
4646
|
<div id="test-history" class="tab-content">
|
|
2987
|
-
<h2 class="tab-main-title">Execution Trends</h2>
|
|
2988
4647
|
<div class="trend-charts-row">
|
|
2989
4648
|
<div class="trend-chart"><h3 class="chart-title-header">Test Volume & Outcome Trends</h3>
|
|
2990
4649
|
${
|
|
@@ -3011,13 +4670,15 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3011
4670
|
${generateDescribeDurationChart(results)}
|
|
3012
4671
|
</div>
|
|
3013
4672
|
</div>
|
|
3014
|
-
<h2 class="tab-main-title">Test Distribution by Worker ${infoTooltip}</h2>
|
|
3015
4673
|
<div class="trend-charts-row">
|
|
3016
4674
|
<div class="trend-chart">
|
|
4675
|
+
<h3 class="chart-title-header">Test Distribution by Worker ${infoTooltip}</h3>
|
|
3017
4676
|
${generateWorkerDistributionChart(results)}
|
|
3018
4677
|
</div>
|
|
3019
4678
|
</div>
|
|
3020
|
-
<
|
|
4679
|
+
<div class="trend-chart test-history-trend-section" style="border-bottom: none;">
|
|
4680
|
+
<h3 class="chart-title-header">Individual Test History</h3>
|
|
4681
|
+
</div>
|
|
3021
4682
|
${
|
|
3022
4683
|
trendData &&
|
|
3023
4684
|
trendData.testRuns &&
|
|
@@ -3032,7 +4693,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3032
4693
|
<footer style="padding: 0.5rem; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); text-align: center; font-family: 'Segoe UI', system-ui, sans-serif;">
|
|
3033
4694
|
<div style="display: inline-flex; align-items: center; gap: 0.5rem; color: #333; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
|
|
3034
4695
|
<span>Created by</span>
|
|
3035
|
-
<a href="https://
|
|
4696
|
+
<a href="https://www.npmjs.com/package/@arghajit/playwright-pulse-report" target="_blank" rel="noopener noreferrer" style="color: #7737BF; font-weight: 700; font-style: italic; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.color='#BF5C37'" onmouseout="this.style.color='#7737BF'">Pulse Report</a>
|
|
3036
4697
|
</div>
|
|
3037
4698
|
<div style="margin-top: 0.5rem; font-size: 0.75rem; color: #666;">Crafted with precision</div>
|
|
3038
4699
|
</footer>
|
|
@@ -3051,25 +4712,62 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3051
4712
|
console.error('Could not find log element with ID:', elementId);
|
|
3052
4713
|
return;
|
|
3053
4714
|
}
|
|
4715
|
+
const originalText = button.textContent;
|
|
3054
4716
|
navigator.clipboard.writeText(logElement.innerText).then(() => {
|
|
3055
4717
|
button.textContent = 'Copied!';
|
|
3056
|
-
setTimeout(() => { button.textContent =
|
|
4718
|
+
setTimeout(() => { button.textContent = originalText; }, 2000);
|
|
3057
4719
|
}).catch(err => {
|
|
3058
4720
|
console.error('Failed to copy log content:', err);
|
|
3059
4721
|
button.textContent = 'Failed';
|
|
3060
|
-
setTimeout(() => { button.textContent =
|
|
4722
|
+
setTimeout(() => { button.textContent = originalText; }, 2000);
|
|
3061
4723
|
});
|
|
3062
4724
|
}
|
|
3063
4725
|
|
|
4726
|
+
// --- Retry Tab Switching Function ---
|
|
4727
|
+
function switchRetryTab(event, tabId) {
|
|
4728
|
+
const tabButton = event.currentTarget;
|
|
4729
|
+
const tabsContainer = tabButton.closest('.retry-tabs-container');
|
|
4730
|
+
|
|
4731
|
+
// Hide all tab contents in this container
|
|
4732
|
+
const allTabContents = tabsContainer.querySelectorAll('.retry-tab-content');
|
|
4733
|
+
allTabContents.forEach(content => {
|
|
4734
|
+
content.style.display = 'none';
|
|
4735
|
+
content.classList.remove('active');
|
|
4736
|
+
});
|
|
4737
|
+
|
|
4738
|
+
// Remove active class from all tabs
|
|
4739
|
+
const allTabs = tabsContainer.querySelectorAll('.retry-tab');
|
|
4740
|
+
allTabs.forEach(tab => tab.classList.remove('active'));
|
|
4741
|
+
|
|
4742
|
+
// Show selected tab content
|
|
4743
|
+
const selectedContent = document.getElementById(tabId);
|
|
4744
|
+
if (selectedContent) {
|
|
4745
|
+
selectedContent.style.display = 'block';
|
|
4746
|
+
selectedContent.classList.add('active');
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
// Add active class to clicked tab
|
|
4750
|
+
tabButton.classList.add('active');
|
|
4751
|
+
}
|
|
4752
|
+
|
|
3064
4753
|
// --- AI Failure Analyzer Functions ---
|
|
3065
4754
|
function getAIFix(button) {
|
|
3066
|
-
const
|
|
3067
|
-
const
|
|
3068
|
-
const
|
|
4755
|
+
const failureItem = button.closest('.compact-failure-item');
|
|
4756
|
+
const aiContainer = failureItem.querySelector('.ai-suggestion-container');
|
|
4757
|
+
const aiContent = failureItem.querySelector('.ai-suggestion-content');
|
|
4758
|
+
|
|
4759
|
+
// Toggle if already visible
|
|
4760
|
+
if (aiContainer.style.display === 'block') {
|
|
4761
|
+
aiContainer.style.display = 'none';
|
|
4762
|
+
button.querySelector('.ai-text').textContent = 'AI Fix';
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
3069
4765
|
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
4766
|
+
// Show loading state
|
|
4767
|
+
aiContainer.style.display = 'block';
|
|
4768
|
+
aiContent.innerHTML = '<div class="ai-loader" style="margin: 40px auto;"></div>';
|
|
4769
|
+
button.querySelector('.ai-text').textContent = 'Loading...';
|
|
4770
|
+
button.disabled = true;
|
|
3073
4771
|
|
|
3074
4772
|
try {
|
|
3075
4773
|
const testJson = button.dataset.testJson;
|
|
@@ -3087,7 +4785,6 @@ function getAIFix(button) {
|
|
|
3087
4785
|
const codeSnippet = test.snippet || '';
|
|
3088
4786
|
|
|
3089
4787
|
const shortTestName = testName.split(' > ').pop();
|
|
3090
|
-
modalTitle.textContent = \`Analysis for: \${shortTestName}\`;
|
|
3091
4788
|
|
|
3092
4789
|
const apiUrl = 'https://ai-test-analyser.netlify.app/api/analyze';
|
|
3093
4790
|
fetch(apiUrl, {
|
|
@@ -3151,18 +4848,31 @@ function getAIFix(button) {
|
|
|
3151
4848
|
suggestionsHtml += \`<div class="code-section"><pre><code>No suggestion provided.</code></pre></div>\`;
|
|
3152
4849
|
}
|
|
3153
4850
|
|
|
3154
|
-
// Combine both parts and
|
|
3155
|
-
|
|
4851
|
+
// Combine both parts and display inline
|
|
4852
|
+
button.querySelector('.ai-text').textContent = 'Hide AI Fix';
|
|
4853
|
+
button.disabled = false;
|
|
4854
|
+
aiContent.innerHTML = \`
|
|
4855
|
+
<div class="ai-suggestion-header">
|
|
4856
|
+
<h4>🤖 AI Analysis Result</h4>
|
|
4857
|
+
</div>
|
|
4858
|
+
<div class="ai-suggestion-body">
|
|
4859
|
+
\${analysisHtml}
|
|
4860
|
+
\${suggestionsHtml}
|
|
4861
|
+
</div>
|
|
4862
|
+
\`;
|
|
3156
4863
|
})
|
|
3157
4864
|
.catch(err => {
|
|
3158
4865
|
console.error('AI Fix Error:', err);
|
|
3159
|
-
|
|
4866
|
+
button.disabled = false;
|
|
4867
|
+
button.querySelector('.ai-text').textContent = 'AI Fix';
|
|
4868
|
+
aiContent.innerHTML = \`<div class="test-error-summary"><strong>Error:</strong> Failed to get AI analysis. Please check the console for details. <br><br> \${err.message}</div>\`;
|
|
3160
4869
|
});
|
|
3161
4870
|
|
|
3162
4871
|
} catch (e) {
|
|
3163
4872
|
console.error('Error processing test data for AI Fix:', e);
|
|
3164
|
-
|
|
3165
|
-
|
|
4873
|
+
button.disabled = false;
|
|
4874
|
+
button.querySelector('.ai-text').textContent = 'AI Fix';
|
|
4875
|
+
aiContent.innerHTML = \`<div class="test-error-summary">Could not process test data. Is it formatted correctly?</div>\`;
|
|
3166
4876
|
}
|
|
3167
4877
|
}
|
|
3168
4878
|
|
|
@@ -3245,6 +4955,7 @@ Code Snippet:
|
|
|
3245
4955
|
function closeAiModal() {
|
|
3246
4956
|
const modal = document.getElementById('ai-fix-modal');
|
|
3247
4957
|
if(modal) modal.style.display = 'none';
|
|
4958
|
+
document.body.style.setProperty('overflow', '', 'important');
|
|
3248
4959
|
}
|
|
3249
4960
|
|
|
3250
4961
|
function toggleErrorDetails(button) {
|
|
@@ -3352,16 +5063,7 @@ Code Snippet:
|
|
|
3352
5063
|
document.querySelectorAll('#test-runs .step-header').forEach(header => {
|
|
3353
5064
|
header.addEventListener('click', () => toggleElementDetails(header, '.step-details'));
|
|
3354
5065
|
});
|
|
3355
|
-
|
|
3356
|
-
const collapseAllBtn = document.getElementById('collapse-all-tests');
|
|
3357
|
-
function setAllTestRunDetailsVisibility(displayMode, ariaState) {
|
|
3358
|
-
document.querySelectorAll('#test-runs .test-case-content').forEach(el => el.style.display = displayMode);
|
|
3359
|
-
document.querySelectorAll('#test-runs .step-details').forEach(el => el.style.display = displayMode);
|
|
3360
|
-
document.querySelectorAll('#test-runs .test-case-header[aria-expanded]').forEach(el => el.setAttribute('aria-expanded', ariaState));
|
|
3361
|
-
document.querySelectorAll('#test-runs .step-header[aria-expanded]').forEach(el => el.setAttribute('aria-expanded', ariaState));
|
|
3362
|
-
}
|
|
3363
|
-
if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
|
|
3364
|
-
if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
|
|
5066
|
+
|
|
3365
5067
|
// --- Annotation Link Handler ---
|
|
3366
5068
|
document.querySelectorAll('a.annotation-link').forEach(link => {
|
|
3367
5069
|
link.addEventListener('click', (e) => {
|
|
@@ -3515,6 +5217,8 @@ async function runScript(scriptPath, args = []) {
|
|
|
3515
5217
|
});
|
|
3516
5218
|
}
|
|
3517
5219
|
async function main() {
|
|
5220
|
+
await animate();
|
|
5221
|
+
|
|
3518
5222
|
const __filename = fileURLToPath(import.meta.url);
|
|
3519
5223
|
const __dirname = path.dirname(__filename);
|
|
3520
5224
|
|
|
@@ -3530,7 +5234,7 @@ async function main() {
|
|
|
3530
5234
|
// Script to archive current run to JSON history (this is your modified "generate-trend.mjs")
|
|
3531
5235
|
const archiveRunScriptPath = path.resolve(
|
|
3532
5236
|
__dirname,
|
|
3533
|
-
"generate-trend.mjs" // Keeping the filename as per your request
|
|
5237
|
+
"generate-trend.mjs", // Keeping the filename as per your request
|
|
3534
5238
|
);
|
|
3535
5239
|
|
|
3536
5240
|
const outputDir = await getOutputDir(customOutputDir);
|
|
@@ -3547,7 +5251,7 @@ async function main() {
|
|
|
3547
5251
|
console.log(chalk.gray(` (from CLI argument)`));
|
|
3548
5252
|
} else {
|
|
3549
5253
|
console.log(
|
|
3550
|
-
chalk.gray(` (auto-detected from playwright.config or using default)`)
|
|
5254
|
+
chalk.gray(` (auto-detected from playwright.config or using default)`),
|
|
3551
5255
|
);
|
|
3552
5256
|
}
|
|
3553
5257
|
|
|
@@ -3556,14 +5260,14 @@ async function main() {
|
|
|
3556
5260
|
const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
|
|
3557
5261
|
await runScript(archiveRunScriptPath, archiveArgs);
|
|
3558
5262
|
console.log(
|
|
3559
|
-
chalk.green("Current run data archiving to history completed.")
|
|
5263
|
+
chalk.green("Current run data archiving to history completed."),
|
|
3560
5264
|
);
|
|
3561
5265
|
} catch (error) {
|
|
3562
5266
|
console.error(
|
|
3563
5267
|
chalk.red(
|
|
3564
|
-
"Failed to archive current run data. Report might use stale or incomplete historical trends."
|
|
5268
|
+
"Failed to archive current run data. Report might use stale or incomplete historical trends.",
|
|
3565
5269
|
),
|
|
3566
|
-
error
|
|
5270
|
+
error,
|
|
3567
5271
|
);
|
|
3568
5272
|
}
|
|
3569
5273
|
|
|
@@ -3578,22 +5282,22 @@ async function main() {
|
|
|
3578
5282
|
!currentRunReportData.results
|
|
3579
5283
|
) {
|
|
3580
5284
|
throw new Error(
|
|
3581
|
-
"Invalid report JSON structure. 'results' field is missing or invalid."
|
|
5285
|
+
"Invalid report JSON structure. 'results' field is missing or invalid.",
|
|
3582
5286
|
);
|
|
3583
5287
|
}
|
|
3584
5288
|
if (!Array.isArray(currentRunReportData.results)) {
|
|
3585
5289
|
currentRunReportData.results = [];
|
|
3586
5290
|
console.warn(
|
|
3587
5291
|
chalk.yellow(
|
|
3588
|
-
"Warning: 'results' field in current run JSON was not an array. Treated as empty."
|
|
3589
|
-
)
|
|
5292
|
+
"Warning: 'results' field in current run JSON was not an array. Treated as empty.",
|
|
5293
|
+
),
|
|
3590
5294
|
);
|
|
3591
5295
|
}
|
|
3592
5296
|
} catch (error) {
|
|
3593
5297
|
console.error(
|
|
3594
5298
|
chalk.red(
|
|
3595
|
-
`Critical Error: Could not read or parse main report JSON at ${reportJsonPath}: ${error.message}
|
|
3596
|
-
)
|
|
5299
|
+
`Critical Error: Could not read or parse main report JSON at ${reportJsonPath}: ${error.message}`,
|
|
5300
|
+
),
|
|
3597
5301
|
);
|
|
3598
5302
|
process.exit(1);
|
|
3599
5303
|
}
|
|
@@ -3606,7 +5310,8 @@ async function main() {
|
|
|
3606
5310
|
|
|
3607
5311
|
const jsonHistoryFiles = allHistoryFiles
|
|
3608
5312
|
.filter(
|
|
3609
|
-
(file) =>
|
|
5313
|
+
(file) =>
|
|
5314
|
+
file.startsWith(HISTORY_FILE_PREFIX) && file.endsWith(".json"),
|
|
3610
5315
|
)
|
|
3611
5316
|
.map((file) => {
|
|
3612
5317
|
const timestampPart = file
|
|
@@ -3623,7 +5328,7 @@ async function main() {
|
|
|
3623
5328
|
|
|
3624
5329
|
const filesToLoadForTrend = jsonHistoryFiles.slice(
|
|
3625
5330
|
0,
|
|
3626
|
-
MAX_HISTORY_FILES_TO_LOAD_FOR_REPORT
|
|
5331
|
+
MAX_HISTORY_FILES_TO_LOAD_FOR_REPORT,
|
|
3627
5332
|
);
|
|
3628
5333
|
|
|
3629
5334
|
for (const fileMeta of filesToLoadForTrend) {
|
|
@@ -3634,29 +5339,29 @@ async function main() {
|
|
|
3634
5339
|
} catch (fileReadError) {
|
|
3635
5340
|
console.warn(
|
|
3636
5341
|
chalk.yellow(
|
|
3637
|
-
`Could not read/parse history file ${fileMeta.name}: ${fileReadError.message}
|
|
3638
|
-
)
|
|
5342
|
+
`Could not read/parse history file ${fileMeta.name}: ${fileReadError.message}`,
|
|
5343
|
+
),
|
|
3639
5344
|
);
|
|
3640
5345
|
}
|
|
3641
5346
|
}
|
|
3642
5347
|
historicalRuns.reverse(); // Oldest first for charts
|
|
3643
5348
|
console.log(
|
|
3644
5349
|
chalk.green(
|
|
3645
|
-
`Loaded ${historicalRuns.length} historical run(s) for trend analysis
|
|
3646
|
-
)
|
|
5350
|
+
`Loaded ${historicalRuns.length} historical run(s) for trend analysis.`,
|
|
5351
|
+
),
|
|
3647
5352
|
);
|
|
3648
5353
|
} catch (error) {
|
|
3649
5354
|
if (error.code === "ENOENT") {
|
|
3650
5355
|
console.warn(
|
|
3651
5356
|
chalk.yellow(
|
|
3652
|
-
`History directory '${historyDir}' not found. No historical trends will be displayed
|
|
3653
|
-
)
|
|
5357
|
+
`History directory '${historyDir}' not found. No historical trends will be displayed.`,
|
|
5358
|
+
),
|
|
3654
5359
|
);
|
|
3655
5360
|
} else {
|
|
3656
5361
|
console.warn(
|
|
3657
5362
|
chalk.yellow(
|
|
3658
|
-
`Error loading historical data from '${historyDir}': ${error.message}
|
|
3659
|
-
)
|
|
5363
|
+
`Error loading historical data from '${historyDir}': ${error.message}`,
|
|
5364
|
+
),
|
|
3660
5365
|
);
|
|
3661
5366
|
}
|
|
3662
5367
|
}
|
|
@@ -3679,6 +5384,7 @@ async function main() {
|
|
|
3679
5384
|
passed: histRunReport.run.passed,
|
|
3680
5385
|
failed: histRunReport.run.failed,
|
|
3681
5386
|
skipped: histRunReport.run.skipped || 0,
|
|
5387
|
+
flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
|
|
3682
5388
|
});
|
|
3683
5389
|
|
|
3684
5390
|
if (histRunReport.results && Array.isArray(histRunReport.results)) {
|
|
@@ -3687,15 +5393,15 @@ async function main() {
|
|
|
3687
5393
|
(test) => ({
|
|
3688
5394
|
testName: test.name,
|
|
3689
5395
|
duration: test.duration,
|
|
3690
|
-
status: test.status,
|
|
5396
|
+
status: test.final_status || test.status,
|
|
3691
5397
|
timestamp: new Date(test.startTime),
|
|
3692
|
-
})
|
|
5398
|
+
}),
|
|
3693
5399
|
);
|
|
3694
5400
|
}
|
|
3695
5401
|
}
|
|
3696
5402
|
});
|
|
3697
5403
|
trendData.overall.sort(
|
|
3698
|
-
(a, b) => a.timestamp.getTime() - b.timestamp.getTime()
|
|
5404
|
+
(a, b) => a.timestamp.getTime() - b.timestamp.getTime(),
|
|
3699
5405
|
);
|
|
3700
5406
|
}
|
|
3701
5407
|
|
|
@@ -3705,8 +5411,8 @@ async function main() {
|
|
|
3705
5411
|
await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
|
|
3706
5412
|
console.log(
|
|
3707
5413
|
chalk.green.bold(
|
|
3708
|
-
|
|
3709
|
-
)
|
|
5414
|
+
`Pulse report generated successfully at: ${reportHtmlPath}`,
|
|
5415
|
+
),
|
|
3710
5416
|
);
|
|
3711
5417
|
console.log(chalk.gray(`(You can open this file in your browser)`));
|
|
3712
5418
|
} catch (error) {
|
|
@@ -3717,7 +5423,7 @@ async function main() {
|
|
|
3717
5423
|
}
|
|
3718
5424
|
main().catch((err) => {
|
|
3719
5425
|
console.error(
|
|
3720
|
-
chalk.red.bold(`Unhandled error during script execution: ${err.message}`)
|
|
5426
|
+
chalk.red.bold(`Unhandled error during script execution: ${err.message}`),
|
|
3721
5427
|
);
|
|
3722
5428
|
console.error(err.stack);
|
|
3723
5429
|
process.exit(1);
|