@arghajit/dummy 0.1.0-beta-28 → 0.1.0-beta-29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/scripts/generate-static-report.mjs +875 -513
|
@@ -6,7 +6,11 @@ import path from "path";
|
|
|
6
6
|
import { fork } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Dynamically imports the 'chalk' library for terminal string styling.
|
|
11
|
+
* This is necessary because chalk is an ESM-only module.
|
|
12
|
+
* If the import fails, a fallback object with plain console log functions is used.
|
|
13
|
+
*/
|
|
10
14
|
let chalk;
|
|
11
15
|
try {
|
|
12
16
|
chalk = (await import("chalk")).default;
|
|
@@ -22,12 +26,30 @@ try {
|
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @constant {string} DEFAULT_OUTPUT_DIR
|
|
31
|
+
* The default directory where the report will be generated.
|
|
32
|
+
*/
|
|
26
33
|
const DEFAULT_OUTPUT_DIR = "pulse-report";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @constant {string} DEFAULT_JSON_FILE
|
|
37
|
+
* The default name for the JSON file containing the test data.
|
|
38
|
+
*/
|
|
27
39
|
const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @constant {string} DEFAULT_HTML_FILE
|
|
43
|
+
* The default name for the generated HTML report file.
|
|
44
|
+
*/
|
|
28
45
|
const DEFAULT_HTML_FILE = "playwright-pulse-static-report.html";
|
|
29
46
|
|
|
30
47
|
// Helper functions
|
|
48
|
+
/**
|
|
49
|
+
* Converts a string with ANSI escape codes to an HTML string with inline styles.
|
|
50
|
+
* @param {string} text The text with ANSI codes.
|
|
51
|
+
* @returns {string} The converted HTML string.
|
|
52
|
+
*/
|
|
31
53
|
export function ansiToHtml(text) {
|
|
32
54
|
if (!text) {
|
|
33
55
|
return "";
|
|
@@ -141,6 +163,11 @@ export function ansiToHtml(text) {
|
|
|
141
163
|
}
|
|
142
164
|
return html;
|
|
143
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Sanitizes an HTML string by replacing special characters with their corresponding HTML entities.
|
|
168
|
+
* @param {string} str The HTML string to sanitize.
|
|
169
|
+
* @returns {string} The sanitized HTML string.
|
|
170
|
+
*/
|
|
144
171
|
function sanitizeHTML(str) {
|
|
145
172
|
if (str === null || str === undefined) return "";
|
|
146
173
|
return String(str).replace(
|
|
@@ -149,28 +176,52 @@ function sanitizeHTML(str) {
|
|
|
149
176
|
({ "&": "&", "<": "<", ">": ">", '"': '"', "'": "'" }[match] || match)
|
|
150
177
|
);
|
|
151
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Capitalizes the first letter of a string and converts the rest to lowercase.
|
|
181
|
+
* @param {string} str The string to capitalize.
|
|
182
|
+
* @returns {string} The capitalized string.
|
|
183
|
+
*/
|
|
152
184
|
function capitalize(str) {
|
|
153
185
|
if (!str) return "";
|
|
154
186
|
return str[0].toUpperCase() + str.slice(1).toLowerCase();
|
|
155
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Formats a Playwright error object or message into an HTML string.
|
|
190
|
+
* @param {Error|string} error The error object or message string.
|
|
191
|
+
* @returns {string} The formatted HTML error string.
|
|
192
|
+
*/
|
|
156
193
|
function formatPlaywrightError(error) {
|
|
157
194
|
const commandOutput = ansiToHtml(error || error.message);
|
|
158
195
|
return convertPlaywrightErrorToHTML(commandOutput);
|
|
159
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Converts a string containing Playwright-style error formatting to HTML.
|
|
199
|
+
* @param {string} str The error string.
|
|
200
|
+
* @returns {string} The HTML-formatted error string.
|
|
201
|
+
*/
|
|
160
202
|
function convertPlaywrightErrorToHTML(str) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
if (!str) return "";
|
|
204
|
+
return str
|
|
205
|
+
.replace(/^(\s+)/gm, (match) =>
|
|
206
|
+
match.replace(/ /g, " ").replace(/\t/g, " ")
|
|
207
|
+
)
|
|
208
|
+
.replace(/<red>/g, '<span style="color: red;">')
|
|
209
|
+
.replace(/<green>/g, '<span style="color: green;">')
|
|
210
|
+
.replace(/<dim>/g, '<span style="opacity: 0.6;">')
|
|
211
|
+
.replace(/<intensity>/g, '<span style="font-weight: bold;">')
|
|
212
|
+
.replace(/<\/color>/g, "</span>")
|
|
213
|
+
.replace(/<\/intensity>/g, "</span>")
|
|
214
|
+
.replace(/\n/g, "<br>");
|
|
173
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Formats a duration in milliseconds into a human-readable string (e.g., '1h 2m 3s', '4.5s').
|
|
218
|
+
* @param {number} ms The duration in milliseconds.
|
|
219
|
+
* @param {object} [options={}] Formatting options.
|
|
220
|
+
* @param {number} [options.precision=1] The number of decimal places for seconds.
|
|
221
|
+
* @param {string} [options.invalidInputReturn="N/A"] The string to return for invalid input.
|
|
222
|
+
* @param {string|null} [options.defaultForNullUndefinedNegative=null] The value for null, undefined, or negative inputs.
|
|
223
|
+
* @returns {string} The formatted duration string.
|
|
224
|
+
*/
|
|
174
225
|
function formatDuration(ms, options = {}) {
|
|
175
226
|
const {
|
|
176
227
|
precision = 1,
|
|
@@ -241,6 +292,12 @@ function formatDuration(ms, options = {}) {
|
|
|
241
292
|
return parts.join(" ");
|
|
242
293
|
}
|
|
243
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Generates HTML and JavaScript for a Highcharts line chart to display test result trends over multiple runs.
|
|
297
|
+
* @param {object} trendData The trend data.
|
|
298
|
+
* @param {Array<object>} trendData.overall An array of run objects with test statistics.
|
|
299
|
+
* @returns {string} The HTML string for the test trends chart.
|
|
300
|
+
*/
|
|
244
301
|
function generateTestTrendsChart(trendData) {
|
|
245
302
|
if (!trendData || !trendData.overall || trendData.overall.length === 0) {
|
|
246
303
|
return '<div class="no-data">No overall trend data available for test counts.</div>';
|
|
@@ -336,6 +393,12 @@ function generateTestTrendsChart(trendData) {
|
|
|
336
393
|
</script>
|
|
337
394
|
`;
|
|
338
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Generates HTML and JavaScript for a Highcharts area chart to display test duration trends.
|
|
398
|
+
* @param {object} trendData Data for duration trends.
|
|
399
|
+
* @param {Array<object>} trendData.overall Array of objects, each representing a test run with a duration.
|
|
400
|
+
* @returns {string} The HTML string for the duration trend chart.
|
|
401
|
+
*/
|
|
339
402
|
function generateDurationTrendChart(trendData) {
|
|
340
403
|
if (!trendData || !trendData.overall || trendData.overall.length === 0) {
|
|
341
404
|
return '<div class="no-data">No overall trend data available for durations.</div>';
|
|
@@ -420,6 +483,11 @@ function generateDurationTrendChart(trendData) {
|
|
|
420
483
|
</script>
|
|
421
484
|
`;
|
|
422
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* Formats a date string or Date object into a more readable format (e.g., "MM/DD/YY HH:MM").
|
|
488
|
+
* @param {string|Date} dateStrOrDate The date string or Date object to format.
|
|
489
|
+
* @returns {string} The formatted date string, or "N/A" for invalid dates.
|
|
490
|
+
*/
|
|
423
491
|
function formatDate(dateStrOrDate) {
|
|
424
492
|
if (!dateStrOrDate) return "N/A";
|
|
425
493
|
try {
|
|
@@ -438,6 +506,12 @@ function formatDate(dateStrOrDate) {
|
|
|
438
506
|
return "Invalid Date Format";
|
|
439
507
|
}
|
|
440
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Generates a small area chart showing the duration history of a single test across multiple runs.
|
|
511
|
+
* The status of each run is indicated by the color of the marker.
|
|
512
|
+
* @param {Array<object>} history An array of run objects, each with status and duration.
|
|
513
|
+
* @returns {string} The HTML string for the test history chart.
|
|
514
|
+
*/
|
|
441
515
|
function generateTestHistoryChart(history) {
|
|
442
516
|
if (!history || history.length === 0)
|
|
443
517
|
return '<div class="no-data-chart">No data for chart</div>';
|
|
@@ -548,6 +622,13 @@ function generateTestHistoryChart(history) {
|
|
|
548
622
|
</script>
|
|
549
623
|
`;
|
|
550
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Generates a Highcharts pie chart to visualize the distribution of test statuses.
|
|
627
|
+
* @param {Array<object>} data The data for the pie chart, with each object having a 'label' and 'value'.
|
|
628
|
+
* @param {number} [chartWidth=300] The width of the chart.
|
|
629
|
+
* @param {number} [chartHeight=300] The height of the chart.
|
|
630
|
+
* @returns {string} The HTML string for the pie chart.
|
|
631
|
+
*/
|
|
551
632
|
function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
552
633
|
const total = data.reduce((sum, d) => sum + d.value, 0);
|
|
553
634
|
if (total === 0) {
|
|
@@ -677,6 +758,12 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
|
|
|
677
758
|
</div>
|
|
678
759
|
`;
|
|
679
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Generates an HTML dashboard to display environment details.
|
|
763
|
+
* @param {object} environment The environment information.
|
|
764
|
+
* @param {number} [dashboardHeight=600] The height of the dashboard.
|
|
765
|
+
* @returns {string} The HTML string for the environment dashboard.
|
|
766
|
+
*/
|
|
680
767
|
function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
681
768
|
// Format memory for display
|
|
682
769
|
const formattedMemory = environment.memory.replace(/(\d+\.\d{2})GB/, "$1 GB");
|
|
@@ -692,176 +779,176 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
692
779
|
return `
|
|
693
780
|
<div class="environment-dashboard-wrapper" id="${dashboardId}">
|
|
694
781
|
<style>
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
782
|
+
.environment-dashboard-wrapper *,
|
|
783
|
+
.environment-dashboard-wrapper *::before,
|
|
784
|
+
.environment-dashboard-wrapper *::after {
|
|
785
|
+
box-sizing: border-box;
|
|
786
|
+
}
|
|
700
787
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
.env-dashboard-header {
|
|
736
|
-
grid-column: 1 / -1;
|
|
737
|
-
display: flex;
|
|
738
|
-
justify-content: space-between;
|
|
739
|
-
align-items: center;
|
|
740
|
-
border-bottom: 1px solid var(--border-color);
|
|
741
|
-
padding-bottom: 16px;
|
|
742
|
-
margin-bottom: 8px;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.env-dashboard-title {
|
|
746
|
-
font-size: 1.5rem;
|
|
747
|
-
font-weight: 600;
|
|
748
|
-
color: var(--text-color);
|
|
749
|
-
margin: 0;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
.env-dashboard-subtitle {
|
|
753
|
-
font-size: 0.875rem;
|
|
754
|
-
color: var(--text-color-secondary);
|
|
755
|
-
margin-top: 4px;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
.env-card {
|
|
759
|
-
background-color: var(--card-background-color);
|
|
760
|
-
border-radius: 8px;
|
|
761
|
-
padding: ${cardContentPadding}px;
|
|
762
|
-
box-shadow: 0 3px 6px var(--shadow-color);
|
|
763
|
-
height: ${cardHeight}px;
|
|
764
|
-
display: flex;
|
|
765
|
-
flex-direction: column;
|
|
766
|
-
overflow: hidden;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
.env-card-header {
|
|
770
|
-
font-weight: 600;
|
|
771
|
-
font-size: 1rem;
|
|
772
|
-
margin-bottom: 12px;
|
|
773
|
-
color: var(--text-color);
|
|
774
|
-
display: flex;
|
|
775
|
-
align-items: center;
|
|
776
|
-
padding-bottom: 8px;
|
|
777
|
-
border-bottom: 1px solid var(--border-light-color);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
.env-card-header svg {
|
|
781
|
-
margin-right: 10px;
|
|
782
|
-
width: 18px;
|
|
783
|
-
height: 18px;
|
|
784
|
-
fill: var(--icon-color);
|
|
785
|
-
}
|
|
788
|
+
.environment-dashboard-wrapper {
|
|
789
|
+
--primary-color: #4a9eff;
|
|
790
|
+
--primary-light-color: #1a2332;
|
|
791
|
+
--secondary-color: #9ca3af;
|
|
792
|
+
--success-color: #34d399;
|
|
793
|
+
--success-light-color: #1a2e23;
|
|
794
|
+
--warning-color: #fbbf24;
|
|
795
|
+
--warning-light-color: #2d2a1a;
|
|
796
|
+
--danger-color: #f87171;
|
|
797
|
+
|
|
798
|
+
--background-color: #1f2937;
|
|
799
|
+
--card-background-color: #374151;
|
|
800
|
+
--text-color: #f9fafb;
|
|
801
|
+
--text-color-secondary: #d1d5db;
|
|
802
|
+
--border-color: #4b5563;
|
|
803
|
+
--border-light-color: #374151;
|
|
804
|
+
--icon-color: #d1d5db;
|
|
805
|
+
--chip-background: #4b5563;
|
|
806
|
+
--chip-text: #f9fafb;
|
|
807
|
+
--shadow-color: rgba(0, 0, 0, 0.3);
|
|
808
|
+
|
|
809
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
810
|
+
background-color: var(--background-color);
|
|
811
|
+
border-radius: 12px;
|
|
812
|
+
box-shadow: 0 6px 12px var(--shadow-color);
|
|
813
|
+
padding: 24px;
|
|
814
|
+
color: var(--text-color);
|
|
815
|
+
display: grid;
|
|
816
|
+
grid-template-columns: 1fr 1fr;
|
|
817
|
+
grid-template-rows: auto 1fr;
|
|
818
|
+
gap: 20px;
|
|
819
|
+
font-size: 14px;
|
|
820
|
+
}
|
|
786
821
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
822
|
+
.env-dashboard-header {
|
|
823
|
+
grid-column: 1 / -1;
|
|
824
|
+
display: flex;
|
|
825
|
+
justify-content: space-between;
|
|
826
|
+
align-items: center;
|
|
827
|
+
border-bottom: 1px solid var(--border-color);
|
|
828
|
+
padding-bottom: 16px;
|
|
829
|
+
margin-bottom: 8px;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.env-dashboard-title {
|
|
833
|
+
font-size: 1.5rem;
|
|
834
|
+
font-weight: 600;
|
|
835
|
+
color: var(--text-color);
|
|
836
|
+
margin: 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.env-dashboard-subtitle {
|
|
840
|
+
font-size: 0.875rem;
|
|
841
|
+
color: var(--text-color-secondary);
|
|
842
|
+
margin-top: 4px;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.env-card {
|
|
846
|
+
background-color: var(--card-background-color);
|
|
847
|
+
border-radius: 8px;
|
|
848
|
+
padding: ${cardContentPadding}px;
|
|
849
|
+
box-shadow: 0 3px 6px var(--shadow-color);
|
|
850
|
+
height: ${cardHeight}px;
|
|
851
|
+
display: flex;
|
|
852
|
+
flex-direction: column;
|
|
853
|
+
overflow: hidden;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.env-card-header {
|
|
857
|
+
font-weight: 600;
|
|
858
|
+
font-size: 1rem;
|
|
859
|
+
margin-bottom: 12px;
|
|
860
|
+
color: var(--text-color);
|
|
861
|
+
display: flex;
|
|
862
|
+
align-items: center;
|
|
863
|
+
padding-bottom: 8px;
|
|
864
|
+
border-bottom: 1px solid var(--border-light-color);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.env-card-header svg {
|
|
868
|
+
margin-right: 10px;
|
|
869
|
+
width: 18px;
|
|
870
|
+
height: 18px;
|
|
871
|
+
fill: var(--icon-color);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.env-card-content {
|
|
875
|
+
flex-grow: 1;
|
|
876
|
+
overflow-y: auto;
|
|
877
|
+
padding-right: 5px;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.env-detail-row {
|
|
881
|
+
display: flex;
|
|
882
|
+
justify-content: space-between;
|
|
883
|
+
align-items: center;
|
|
884
|
+
padding: 10px 0;
|
|
885
|
+
border-bottom: 1px solid var(--border-light-color);
|
|
886
|
+
font-size: 0.875rem;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.env-detail-row:last-child {
|
|
890
|
+
border-bottom: none;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.env-detail-label {
|
|
894
|
+
color: var(--text-color-secondary);
|
|
895
|
+
font-weight: 500;
|
|
896
|
+
margin-right: 10px;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.env-detail-value {
|
|
900
|
+
color: var(--text-color);
|
|
901
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
902
|
+
text-align: right;
|
|
903
|
+
word-break: break-all;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.env-chip {
|
|
907
|
+
display: inline-block;
|
|
908
|
+
padding: 4px 10px;
|
|
909
|
+
border-radius: 16px;
|
|
910
|
+
font-size: 0.75rem;
|
|
911
|
+
font-weight: 500;
|
|
912
|
+
line-height: 1.2;
|
|
913
|
+
background-color: var(--chip-background);
|
|
914
|
+
color: var(--chip-text);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.env-chip-primary {
|
|
918
|
+
background-color: var(--primary-light-color);
|
|
919
|
+
color: var(--primary-color);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.env-chip-success {
|
|
923
|
+
background-color: var(--success-light-color);
|
|
924
|
+
color: var(--success-color);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.env-chip-warning {
|
|
928
|
+
background-color: var(--warning-light-color);
|
|
929
|
+
color: var(--warning-color);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.env-cpu-cores {
|
|
933
|
+
display: flex;
|
|
934
|
+
align-items: center;
|
|
935
|
+
gap: 6px;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.env-core-indicator {
|
|
939
|
+
width: 12px;
|
|
940
|
+
height: 12px;
|
|
941
|
+
border-radius: 50%;
|
|
942
|
+
background-color: var(--success-color);
|
|
943
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.env-core-indicator.inactive {
|
|
947
|
+
background-color: var(--border-light-color);
|
|
948
|
+
opacity: 0.7;
|
|
949
|
+
border-color: var(--border-color);
|
|
950
|
+
}
|
|
951
|
+
</style>
|
|
865
952
|
|
|
866
953
|
<div class="env-dashboard-header">
|
|
867
954
|
<div>
|
|
@@ -1006,6 +1093,11 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
|
|
|
1006
1093
|
</div>
|
|
1007
1094
|
`;
|
|
1008
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Generates a Highcharts bar chart to visualize the distribution of test results across different workers.
|
|
1098
|
+
* @param {Array<object>} results The test results data.
|
|
1099
|
+
* @returns {string} The HTML string for the worker distribution chart and its associated modal.
|
|
1100
|
+
*/
|
|
1009
1101
|
function generateWorkerDistributionChart(results) {
|
|
1010
1102
|
if (!results || results.length === 0) {
|
|
1011
1103
|
return '<div class="no-data">No test results data available to display worker distribution.</div>';
|
|
@@ -1082,45 +1174,46 @@ function generateWorkerDistributionChart(results) {
|
|
|
1082
1174
|
|
|
1083
1175
|
// The HTML now includes the chart container, the modal, and styles for the modal
|
|
1084
1176
|
return `
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1177
|
+
<style>
|
|
1178
|
+
.worker-modal-overlay {
|
|
1179
|
+
position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%;
|
|
1180
|
+
overflow: auto; background-color: rgba(0,0,0,0.8);
|
|
1181
|
+
display: none; align-items: center; justify-content: center;
|
|
1182
|
+
}
|
|
1183
|
+
.worker-modal-content {
|
|
1184
|
+
background-color: #1f2937;
|
|
1185
|
+
color: #f9fafb;
|
|
1186
|
+
margin: auto; padding: 20px; border: 1px solid #4b5563;
|
|
1187
|
+
width: 80%; max-width: 700px; border-radius: 8px;
|
|
1188
|
+
position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.7);
|
|
1189
|
+
}
|
|
1190
|
+
.worker-modal-close {
|
|
1191
|
+
position: absolute; top: 10px; right: 20px;
|
|
1192
|
+
font-size: 28px; font-weight: bold; cursor: pointer;
|
|
1193
|
+
line-height: 1; color: #d1d5db;
|
|
1194
|
+
}
|
|
1195
|
+
.worker-modal-close:hover, .worker-modal-close:focus {
|
|
1196
|
+
color: #f9fafb;
|
|
1197
|
+
}
|
|
1198
|
+
#worker-modal-body-${chartId} ul {
|
|
1199
|
+
list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
|
|
1200
|
+
}
|
|
1201
|
+
#worker-modal-body-${chartId} li {
|
|
1202
|
+
padding: 8px 5px; border-bottom: 1px solid #4b5563;
|
|
1203
|
+
font-size: 0.9em; color: #f9fafb;
|
|
1204
|
+
}
|
|
1205
|
+
#worker-modal-body-${chartId} li:last-child {
|
|
1206
|
+
border-bottom: none;
|
|
1207
|
+
}
|
|
1208
|
+
#worker-modal-body-${chartId} li > span {
|
|
1209
|
+
display: inline-block;
|
|
1210
|
+
width: 70px;
|
|
1211
|
+
font-weight: bold;
|
|
1212
|
+
text-align: right;
|
|
1213
|
+
margin-right: 10px;
|
|
1214
|
+
color: #d1d5db;
|
|
1215
|
+
}
|
|
1216
|
+
</style>
|
|
1124
1217
|
|
|
1125
1218
|
<div id="${chartId}" class="trend-chart-container lazy-load-chart" data-render-function-name="${renderFunctionName}" style="min-height: 350px;">
|
|
1126
1219
|
<div class="no-data">Loading Worker Distribution Chart...</div>
|
|
@@ -1147,42 +1240,63 @@ function generateWorkerDistributionChart(results) {
|
|
|
1147
1240
|
const modalTitle = document.getElementById('worker-modal-title-${chartId}');
|
|
1148
1241
|
const modalBody = document.getElementById('worker-modal-body-${chartId}');
|
|
1149
1242
|
const closeModalBtn = modal.querySelector('.worker-modal-close');
|
|
1243
|
+
if (modal && modal.parentElement !== document.body) {
|
|
1244
|
+
document.body.appendChild(modal);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Lightweight HTML escaper for client-side use
|
|
1248
|
+
function __escHtml(s){return String(s==null?'':s).replace(/[&<>\"]/g,function(ch){return ch==='&'?'&':ch==='<'?'<':ch==='>'?'>':'"';});}
|
|
1150
1249
|
|
|
1151
1250
|
window.${modalJsNamespace}.open = function(worker) {
|
|
1152
1251
|
if (!worker) return;
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
worker.tests.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1252
|
+
try {
|
|
1253
|
+
modalTitle.textContent = 'Test Details for ' + worker.name;
|
|
1254
|
+
|
|
1255
|
+
let testListHtml = '<ul>';
|
|
1256
|
+
if (worker.tests && worker.tests.length > 0) {
|
|
1257
|
+
worker.tests.forEach(test => {
|
|
1258
|
+
let color = 'inherit';
|
|
1259
|
+
if (test.status === 'passed') color = 'var(--success-color)';
|
|
1260
|
+
else if (test.status === 'failed') color = 'var(--danger-color)';
|
|
1261
|
+
else if (test.status === 'skipped') color = 'var(--warning-color)';
|
|
1262
|
+
|
|
1263
|
+
const safeName = __escHtml(test.name);
|
|
1264
|
+
testListHtml += '<li style="color: ' + color + ';"><span style="color: ' + color + '">[' + String(test.status).toUpperCase() + ']</span> ' + safeName + '</li>';
|
|
1265
|
+
});
|
|
1266
|
+
} else {
|
|
1267
|
+
testListHtml += '<li>No detailed test data available for this worker.</li>';
|
|
1268
|
+
}
|
|
1269
|
+
testListHtml += '</ul>';
|
|
1170
1270
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1271
|
+
modalBody.innerHTML = testListHtml;
|
|
1272
|
+
if (typeof openModal === 'function') openModal(); else modal.style.display = 'flex';
|
|
1273
|
+
} catch (err) {
|
|
1274
|
+
console.error('Failed to open worker modal:', err);
|
|
1275
|
+
}
|
|
1173
1276
|
};
|
|
1174
1277
|
|
|
1175
1278
|
const closeModal = function() {
|
|
1176
1279
|
modal.style.display = 'none';
|
|
1280
|
+
try { document.body.style.overflow = ''; } catch (_) {}
|
|
1177
1281
|
};
|
|
1178
1282
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1283
|
+
const openModal = function() {
|
|
1284
|
+
modal.style.display = 'flex';
|
|
1285
|
+
try { document.body.style.overflow = 'hidden'; } catch (_) {}
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
if (closeModalBtn) closeModalBtn.onclick = closeModal;
|
|
1289
|
+
modal.addEventListener('click', function(event) {
|
|
1290
|
+
if (event.target === modal) {
|
|
1183
1291
|
closeModal();
|
|
1184
1292
|
}
|
|
1185
|
-
};
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
document.addEventListener('keydown', function escHandler(e) {
|
|
1296
|
+
if (modal.style.display === 'flex' && (e.key === 'Escape' || e.key === 'Esc')) {
|
|
1297
|
+
closeModal();
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1186
1300
|
|
|
1187
1301
|
|
|
1188
1302
|
// --- Highcharts Setup ---
|
|
@@ -1242,6 +1356,10 @@ function generateWorkerDistributionChart(results) {
|
|
|
1242
1356
|
</script>
|
|
1243
1357
|
`;
|
|
1244
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* A tooltip providing information about why worker -1 is special in Playwright.
|
|
1361
|
+
* @type {string}
|
|
1362
|
+
*/
|
|
1245
1363
|
const infoTooltip = `
|
|
1246
1364
|
<span class="info-tooltip" style="display: inline-block; margin-left: 8px;">
|
|
1247
1365
|
<span class="info-icon"
|
|
@@ -1260,6 +1378,11 @@ const infoTooltip = `
|
|
|
1260
1378
|
}
|
|
1261
1379
|
</script>
|
|
1262
1380
|
`;
|
|
1381
|
+
/**
|
|
1382
|
+
* Generates the HTML content for the test history section.
|
|
1383
|
+
* @param {object} trendData - The historical trend data.
|
|
1384
|
+
* @returns {string} The HTML string for the test history content.
|
|
1385
|
+
*/
|
|
1263
1386
|
function generateTestHistoryContent(trendData) {
|
|
1264
1387
|
if (
|
|
1265
1388
|
!trendData ||
|
|
@@ -1379,6 +1502,11 @@ function generateTestHistoryContent(trendData) {
|
|
|
1379
1502
|
</div>
|
|
1380
1503
|
`;
|
|
1381
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Gets the CSS class for a given test status.
|
|
1507
|
+
* @param {string} status - The test status.
|
|
1508
|
+
* @returns {string} The CSS class for the status.
|
|
1509
|
+
*/
|
|
1382
1510
|
function getStatusClass(status) {
|
|
1383
1511
|
switch (String(status).toLowerCase()) {
|
|
1384
1512
|
case "passed":
|
|
@@ -1391,6 +1519,11 @@ function getStatusClass(status) {
|
|
|
1391
1519
|
return "status-unknown";
|
|
1392
1520
|
}
|
|
1393
1521
|
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Gets the icon for a given test status.
|
|
1524
|
+
* @param {string} status - The test status.
|
|
1525
|
+
* @returns {string} The icon for the status.
|
|
1526
|
+
*/
|
|
1394
1527
|
function getStatusIcon(status) {
|
|
1395
1528
|
switch (String(status).toLowerCase()) {
|
|
1396
1529
|
case "passed":
|
|
@@ -1403,6 +1536,11 @@ function getStatusIcon(status) {
|
|
|
1403
1536
|
return "❓";
|
|
1404
1537
|
}
|
|
1405
1538
|
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Processes test results to extract suite data.
|
|
1541
|
+
* @param {Array<object>} results - The test results.
|
|
1542
|
+
* @returns {Array<object>} An array of suite data objects.
|
|
1543
|
+
*/
|
|
1406
1544
|
function getSuitesData(results) {
|
|
1407
1545
|
const suitesMap = new Map();
|
|
1408
1546
|
if (!results || results.length === 0) return [];
|
|
@@ -1451,6 +1589,16 @@ function getSuitesData(results) {
|
|
|
1451
1589
|
});
|
|
1452
1590
|
return Array.from(suitesMap.values());
|
|
1453
1591
|
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Returns an icon for a given content type.
|
|
1594
|
+
* @param {string} contentType - The content type of the file.
|
|
1595
|
+
* @returns {string} The icon for the content type.
|
|
1596
|
+
*/
|
|
1597
|
+
/**
|
|
1598
|
+
* Returns an icon for a given content type.
|
|
1599
|
+
* @param {string} contentType - The content type of the file.
|
|
1600
|
+
* @returns {string} The icon for the content type.
|
|
1601
|
+
*/
|
|
1454
1602
|
function getAttachmentIcon(contentType) {
|
|
1455
1603
|
if (!contentType) return "📎"; // Handle undefined/null
|
|
1456
1604
|
|
|
@@ -1464,6 +1612,16 @@ function getAttachmentIcon(contentType) {
|
|
|
1464
1612
|
if (normalizedType.startsWith("text/")) return "📝";
|
|
1465
1613
|
return "📎";
|
|
1466
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Generates the HTML for the suites widget.
|
|
1617
|
+
* @param {Array} suitesData - The data for the suites.
|
|
1618
|
+
* @returns {string} The HTML for the suites widget.
|
|
1619
|
+
*/
|
|
1620
|
+
/**
|
|
1621
|
+
* Generates the HTML for the suites widget.
|
|
1622
|
+
* @param {Array} suitesData - The data for the suites.
|
|
1623
|
+
* @returns {string} The HTML for the suites widget.
|
|
1624
|
+
*/
|
|
1467
1625
|
function generateSuitesWidget(suitesData) {
|
|
1468
1626
|
if (!suitesData || suitesData.length === 0) {
|
|
1469
1627
|
return `<div class="suites-widget"><div class="suites-header"><h2>Test Suites</h2></div><div class="no-data">No suite data available.</div></div>`;
|
|
@@ -1520,8 +1678,15 @@ function generateSuitesWidget(suitesData) {
|
|
|
1520
1678
|
</div>
|
|
1521
1679
|
</div>`;
|
|
1522
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Generates the HTML for the AI failure analyzer tab.
|
|
1683
|
+
* @param {Array} results - The results of the test run.
|
|
1684
|
+
* @returns {string} The HTML for the AI failure analyzer tab.
|
|
1685
|
+
*/
|
|
1523
1686
|
function generateAIFailureAnalyzerTab(results) {
|
|
1524
|
-
const failedTests = (results || []).filter(
|
|
1687
|
+
const failedTests = (results || []).filter(
|
|
1688
|
+
(test) => test.status === "failed"
|
|
1689
|
+
);
|
|
1525
1690
|
|
|
1526
1691
|
if (failedTests.length === 0) {
|
|
1527
1692
|
return `
|
|
@@ -1531,7 +1696,7 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1531
1696
|
}
|
|
1532
1697
|
|
|
1533
1698
|
// btoa is not available in Node.js environment, so we define a simple polyfill for it.
|
|
1534
|
-
const btoa = (str) => Buffer.from(str).toString(
|
|
1699
|
+
const btoa = (str) => Buffer.from(str).toString("base64");
|
|
1535
1700
|
|
|
1536
1701
|
return `
|
|
1537
1702
|
<h2 class="tab-main-title">AI Failure Analysis</h2>
|
|
@@ -1541,11 +1706,16 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1541
1706
|
<span class="stat-label">Failed Tests</span>
|
|
1542
1707
|
</div>
|
|
1543
1708
|
<div class="stat-item">
|
|
1544
|
-
<span class="stat-number">${
|
|
1709
|
+
<span class="stat-number">${
|
|
1710
|
+
new Set(failedTests.map((t) => t.browser)).size
|
|
1711
|
+
}</span>
|
|
1545
1712
|
<span class="stat-label">Browsers</span>
|
|
1546
1713
|
</div>
|
|
1547
1714
|
<div class="stat-item">
|
|
1548
|
-
<span class="stat-number">${
|
|
1715
|
+
<span class="stat-number">${Math.round(
|
|
1716
|
+
failedTests.reduce((sum, test) => sum + (test.duration || 0), 0) /
|
|
1717
|
+
1000
|
|
1718
|
+
)}s</span>
|
|
1549
1719
|
<span class="stat-label">Total Duration</span>
|
|
1550
1720
|
</div>
|
|
1551
1721
|
</div>
|
|
@@ -1554,20 +1724,28 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1554
1724
|
</p>
|
|
1555
1725
|
|
|
1556
1726
|
<div class="compact-failure-list">
|
|
1557
|
-
${failedTests
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1727
|
+
${failedTests
|
|
1728
|
+
.map((test) => {
|
|
1729
|
+
const testTitle = test.name.split(" > ").pop() || "Unnamed Test";
|
|
1730
|
+
const testJson = btoa(JSON.stringify(test)); // Base64 encode the test object
|
|
1731
|
+
const truncatedError =
|
|
1732
|
+
(test.errorMessage || "No error message").slice(0, 150) +
|
|
1733
|
+
(test.errorMessage && test.errorMessage.length > 150 ? "..." : "");
|
|
1734
|
+
|
|
1735
|
+
return `
|
|
1564
1736
|
<div class="compact-failure-item">
|
|
1565
1737
|
<div class="failure-header">
|
|
1566
1738
|
<div class="failure-main-info">
|
|
1567
|
-
<h3 class="failure-title" title="${sanitizeHTML(
|
|
1739
|
+
<h3 class="failure-title" title="${sanitizeHTML(
|
|
1740
|
+
test.name
|
|
1741
|
+
)}">${sanitizeHTML(testTitle)}</h3>
|
|
1568
1742
|
<div class="failure-meta">
|
|
1569
|
-
<span class="browser-indicator">${sanitizeHTML(
|
|
1570
|
-
|
|
1743
|
+
<span class="browser-indicator">${sanitizeHTML(
|
|
1744
|
+
test.browser || "unknown"
|
|
1745
|
+
)}</span>
|
|
1746
|
+
<span class="duration-indicator">${formatDuration(
|
|
1747
|
+
test.duration
|
|
1748
|
+
)}</span>
|
|
1571
1749
|
</div>
|
|
1572
1750
|
</div>
|
|
1573
1751
|
<button class="compact-ai-btn" onclick="getAIFix(this)" data-test-json="${testJson}">
|
|
@@ -1575,7 +1753,9 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1575
1753
|
</button>
|
|
1576
1754
|
</div>
|
|
1577
1755
|
<div class="failure-error-preview">
|
|
1578
|
-
<div class="error-snippet">${formatPlaywrightError(
|
|
1756
|
+
<div class="error-snippet">${formatPlaywrightError(
|
|
1757
|
+
truncatedError
|
|
1758
|
+
)}</div>
|
|
1579
1759
|
<button class="expand-error-btn" onclick="toggleErrorDetails(this)">
|
|
1580
1760
|
<span class="expand-text">Show Full Error</span>
|
|
1581
1761
|
<span class="expand-icon">▼</span>
|
|
@@ -1583,12 +1763,15 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1583
1763
|
</div>
|
|
1584
1764
|
<div class="full-error-details" style="display: none;">
|
|
1585
1765
|
<div class="full-error-content">
|
|
1586
|
-
${formatPlaywrightError(
|
|
1766
|
+
${formatPlaywrightError(
|
|
1767
|
+
test.errorMessage || "No detailed error message available"
|
|
1768
|
+
)}
|
|
1587
1769
|
</div>
|
|
1588
1770
|
</div>
|
|
1589
1771
|
</div>
|
|
1590
|
-
|
|
1591
|
-
|
|
1772
|
+
`;
|
|
1773
|
+
})
|
|
1774
|
+
.join("")}
|
|
1592
1775
|
</div>
|
|
1593
1776
|
|
|
1594
1777
|
<!-- AI Fix Modal -->
|
|
@@ -1605,6 +1788,12 @@ function generateAIFailureAnalyzerTab(results) {
|
|
|
1605
1788
|
</div>
|
|
1606
1789
|
`;
|
|
1607
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Generates the HTML report.
|
|
1793
|
+
* @param {object} reportData - The data for the report.
|
|
1794
|
+
* @param {object} trendData - The data for the trend chart.
|
|
1795
|
+
* @returns {string} The HTML report.
|
|
1796
|
+
*/
|
|
1608
1797
|
function generateHTML(reportData, trendData = null) {
|
|
1609
1798
|
const { run, results } = reportData;
|
|
1610
1799
|
const suitesData = getSuitesData(reportData.results || []);
|
|
@@ -1627,11 +1816,16 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1627
1816
|
? formatDuration(runSummary.duration / runSummary.totalTests)
|
|
1628
1817
|
: "0.0s";
|
|
1629
1818
|
|
|
1630
|
-
|
|
1819
|
+
/**
|
|
1820
|
+
* Generates the HTML for the test cases.
|
|
1821
|
+
* @returns {string} The HTML for the test cases.
|
|
1822
|
+
*/
|
|
1823
|
+
function generateTestCasesHTML(subset = results, baseIndex = 0) {
|
|
1631
1824
|
if (!results || results.length === 0)
|
|
1632
1825
|
return '<div class="no-tests">No test results found in this run.</div>';
|
|
1633
|
-
return
|
|
1634
|
-
.map((test,
|
|
1826
|
+
return subset
|
|
1827
|
+
.map((test, i) => {
|
|
1828
|
+
const testIndex = baseIndex + i;
|
|
1635
1829
|
const browser = test.browser || "unknown";
|
|
1636
1830
|
const testFileParts = test.name.split(" > ");
|
|
1637
1831
|
const testTitle =
|
|
@@ -1669,6 +1863,42 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1669
1863
|
: ""
|
|
1670
1864
|
}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
|
|
1671
1865
|
: ""
|
|
1866
|
+
}${
|
|
1867
|
+
(() => {
|
|
1868
|
+
if (!step.attachments || step.attachments.length === 0) return "";
|
|
1869
|
+
return `<div class="attachments-section"><h4>Step Attachments</h4><div class="attachments-grid">${step.attachments
|
|
1870
|
+
.map((attachment) => {
|
|
1871
|
+
try {
|
|
1872
|
+
const attachmentPath = path.resolve(
|
|
1873
|
+
DEFAULT_OUTPUT_DIR,
|
|
1874
|
+
attachment.path
|
|
1875
|
+
);
|
|
1876
|
+
if (!fsExistsSync(attachmentPath)) {
|
|
1877
|
+
return `<div class="attachment-item error">Attachment not found: ${sanitizeHTML(
|
|
1878
|
+
attachment.name
|
|
1879
|
+
)}</div>`;
|
|
1880
|
+
}
|
|
1881
|
+
const attachmentBase64 = readFileSync(attachmentPath).toString("base64");
|
|
1882
|
+
const attachmentDataUri = `data:${attachment.contentType};base64,${attachmentBase64}`;
|
|
1883
|
+
return `<div class="attachment-item generic-attachment">
|
|
1884
|
+
<div class="attachment-icon">${getAttachmentIcon(attachment.contentType)}</div>
|
|
1885
|
+
<div class="attachment-caption">
|
|
1886
|
+
<span class="attachment-name" title="${sanitizeHTML(attachment.name)}">${sanitizeHTML(attachment.name)}</span>
|
|
1887
|
+
<span class="attachment-type">${sanitizeHTML(attachment.contentType)}</span>
|
|
1888
|
+
</div>
|
|
1889
|
+
<div class="attachment-info">
|
|
1890
|
+
<div class="trace-actions">
|
|
1891
|
+
<a href="#" data-href="${attachmentDataUri}" class="view-full lazy-load-attachment" target="_blank">View</a>
|
|
1892
|
+
<a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(attachment.name)}">Download</a>
|
|
1893
|
+
</div>
|
|
1894
|
+
</div>
|
|
1895
|
+
</div>`;
|
|
1896
|
+
} catch (e) {
|
|
1897
|
+
return `<div class="attachment-item error">Failed to load attachment: ${sanitizeHTML(attachment.name)}</div>`;
|
|
1898
|
+
}
|
|
1899
|
+
})
|
|
1900
|
+
.join("")}</div></div>`;
|
|
1901
|
+
})()
|
|
1672
1902
|
}${
|
|
1673
1903
|
hasNestedSteps
|
|
1674
1904
|
? `<div class="nested-steps">${generateStepsHTML(
|
|
@@ -1686,7 +1916,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1686
1916
|
test.tags || []
|
|
1687
1917
|
)
|
|
1688
1918
|
.join(",")
|
|
1689
|
-
.toLowerCase()}">
|
|
1919
|
+
.toLowerCase()}" data-test-id="${sanitizeHTML(String(test.id || testIndex))}">
|
|
1690
1920
|
<div class="test-case-header" role="button" aria-expanded="false"><div class="test-case-summary"><span class="status-badge ${getStatusClass(
|
|
1691
1921
|
test.status
|
|
1692
1922
|
)}">${String(
|
|
@@ -1722,6 +1952,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1722
1952
|
)}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
|
|
1723
1953
|
: ""
|
|
1724
1954
|
}
|
|
1955
|
+
${
|
|
1956
|
+
test.snippet
|
|
1957
|
+
? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
|
|
1958
|
+
test.snippet
|
|
1959
|
+
)}</code></pre></div>`
|
|
1960
|
+
: ""
|
|
1961
|
+
}
|
|
1725
1962
|
<h4>Steps</h4><div class="steps-list">${generateStepsHTML(
|
|
1726
1963
|
test.steps
|
|
1727
1964
|
)}</div>
|
|
@@ -1745,9 +1982,12 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1745
1982
|
})()}
|
|
1746
1983
|
${
|
|
1747
1984
|
test.stderr && test.stderr.length > 0
|
|
1748
|
-
?
|
|
1749
|
-
.
|
|
1750
|
-
|
|
1985
|
+
? (() => {
|
|
1986
|
+
const logId = `stderr-log-${test.id || testIndex}`;
|
|
1987
|
+
return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre id="${logId}" class="console-log stderr-log">${test.stderr
|
|
1988
|
+
.map((line) => sanitizeHTML(line))
|
|
1989
|
+
.join("\\n")}</pre></div>`;
|
|
1990
|
+
})()
|
|
1751
1991
|
: ""
|
|
1752
1992
|
}
|
|
1753
1993
|
|
|
@@ -1772,7 +2012,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1772
2012
|
readFileSync(imagePath).toString("base64");
|
|
1773
2013
|
return `<div class="attachment-item"><img src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="data:image/png;base64,${base64ImageData}" alt="Screenshot ${
|
|
1774
2014
|
index + 1
|
|
1775
|
-
}" class="lazy-load-image"><div class="attachment-info"><div class="trace-actions"><a href="data:image/png;base64,${base64ImageData}" target="_blank" download="screenshot-${index}.png">Download</a></div></div></div>`;
|
|
2015
|
+
}" class="lazy-load-image"><div class="attachment-info"><div class="trace-actions"><a href="#" data-href="data:image/png;base64,${base64ImageData}" class="lazy-load-attachment" target="_blank" download="screenshot-${index}.png">Download</a></div></div></div>`;
|
|
1776
2016
|
} catch (e) {
|
|
1777
2017
|
return `<div class="attachment-item error">Failed to load screenshot: ${sanitizeHTML(
|
|
1778
2018
|
screenshotPath
|
|
@@ -1813,7 +2053,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1813
2053
|
avi: "video/x-msvideo",
|
|
1814
2054
|
}[fileExtension] || "video/mp4";
|
|
1815
2055
|
const videoDataUri = `data:${mimeType};base64,${videoBase64}`;
|
|
1816
|
-
return `<div class="attachment-item video-item"><video controls preload="none" class="lazy-load-video"><source data-src="${videoDataUri}" type="${mimeType}"></video><div class="attachment-info"><div class="trace-actions"><a href="${videoDataUri}" target="_blank" download="video-${index}.${fileExtension}">Download</a></div></div></div>`;
|
|
2056
|
+
return `<div class="attachment-item video-item"><video controls preload="none" class="lazy-load-video"><source data-src="${videoDataUri}" type="${mimeType}"></video><div class="attachment-info"><div class="trace-actions"><a href="#" data-href="${videoDataUri}" class="lazy-load-attachment" target="_blank" download="video-${index}.${fileExtension}">Download</a></div></div></div>`;
|
|
1817
2057
|
} catch (e) {
|
|
1818
2058
|
return `<div class="attachment-item error">Failed to load video: ${sanitizeHTML(
|
|
1819
2059
|
videoPath
|
|
@@ -1889,8 +2129,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1889
2129
|
</div>
|
|
1890
2130
|
<div class="attachment-info">
|
|
1891
2131
|
<div class="trace-actions">
|
|
1892
|
-
<a href="${attachmentDataUri}"
|
|
1893
|
-
<a href="${attachmentDataUri}" download="${sanitizeHTML(
|
|
2132
|
+
<a href="#" data-href="${attachmentDataUri}" class="view-full lazy-load-attachment" target="_blank">View</a>
|
|
2133
|
+
<a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(
|
|
1894
2134
|
attachment.name
|
|
1895
2135
|
)}">Download</a>
|
|
1896
2136
|
</div>
|
|
@@ -1931,211 +2171,239 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1931
2171
|
<link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
|
|
1932
2172
|
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
1933
2173
|
<title>Playwright Pulse Report (Static Report)</title>
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2174
|
+
|
|
2175
|
+
<style>
|
|
2176
|
+
:root {
|
|
2177
|
+
--primary-color: #60a5fa; --secondary-color: #f472b6; --accent-color: #a78bfa; --accent-color-alt: #fb923c;
|
|
2178
|
+
--success-color: #34d399; --danger-color: #f87171; --warning-color: #fbbf24; --info-color: #60a5fa;
|
|
2179
|
+
--light-gray-color: #374151; --medium-gray-color: #4b5563; --dark-gray-color: #9ca3af;
|
|
2180
|
+
--text-color: #f9fafb; --text-color-secondary: #d1d5db; --border-color: #4b5563; --background-color: #111827;
|
|
2181
|
+
--card-background-color: #1f2937; --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
2182
|
+
--border-radius: 8px; --box-shadow: 0 5px 15px rgba(0,0,0,0.3); --box-shadow-light: 0 3px 8px rgba(0,0,0,0.2); --box-shadow-inset: inset 0 1px 3px rgba(0,0,0,0.3);
|
|
2183
|
+
}
|
|
2184
|
+
.trend-chart-container, .test-history-trend div[id^="testHistoryChart-"] { min-height: 100px; }
|
|
2185
|
+
.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); }
|
|
2186
|
+
.highcharts-background { fill: transparent; }
|
|
2187
|
+
.highcharts-title, .highcharts-subtitle { font-family: var(--font-family); }
|
|
2188
|
+
.highcharts-axis-labels text, .highcharts-legend-item text { fill: var(--text-color-secondary) !important; font-size: 12px !important; }
|
|
2189
|
+
.highcharts-axis-title { fill: var(--text-color) !important; }
|
|
2190
|
+
.highcharts-tooltip > span { background-color: rgba(31,41,55,0.95) !important; border-color: rgba(31,41,55,0.95) !important; color: #f9fafb !important; padding: 10px !important; border-radius: 6px !important; }
|
|
2191
|
+
body { font-family: var(--font-family); margin: 0; background-color: var(--background-color); color: var(--text-color); line-height: 1.65; font-size: 16px; }
|
|
2192
|
+
.container { padding: 30px; border-radius: var(--border-radius); box-shadow: var(--box-shadow); background: repeating-linear-gradient(#1f2937, #374151, #1f2937); }
|
|
2193
|
+
.header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; padding-bottom: 25px; border-bottom: 1px solid var(--border-color); margin-bottom: 25px; }
|
|
2194
|
+
.header-title { display: flex; align-items: center; gap: 15px; }
|
|
2195
|
+
.header h1 { margin: 0; font-size: 1.85em; font-weight: 600; color: var(--primary-color); }
|
|
2196
|
+
#report-logo { height: 40px; width: 55px; }
|
|
2197
|
+
.run-info { font-size: 0.9em; text-align: right; color: var(--text-color-secondary); line-height:1.5;}
|
|
2198
|
+
.run-info strong { color: var(--text-color); }
|
|
2199
|
+
.tabs { display: flex; border-bottom: 2px solid var(--border-color); margin-bottom: 30px; overflow-x: auto; }
|
|
2200
|
+
.tab-button { padding: 15px 25px; background: none; border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 1.1em; font-weight: 600; color: var(--text-color); transition: color 0.2s ease, border-color 0.2s ease; white-space: nowrap; }
|
|
2201
|
+
.tab-button:hover { color: var(--accent-color); }
|
|
2202
|
+
.tab-button.active { color: var(--primary-color); border-bottom-color: var(--primary-color); }
|
|
2203
|
+
.tab-content { display: none; animation: fadeIn 0.4s ease-out; }
|
|
2204
|
+
.tab-content.active { display: block; }
|
|
2205
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
2206
|
+
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 22px; margin-bottom: 35px; }
|
|
2207
|
+
.summary-card { background-color: var(--card-background-color); border: 1px solid var(--border-color); border-radius: var(--border-radius); padding: 22px; text-align: center; box-shadow: var(--box-shadow-light); transition: transform 0.2s ease, box-shadow 0.2s ease; }
|
|
2208
|
+
.summary-card:hover { transform: translateY(-5px); box-shadow: var(--box-shadow); }
|
|
2209
|
+
.summary-card h3 { margin: 0 0 10px; font-size: 1.05em; font-weight: 500; color: var(--text-color-secondary); }
|
|
2210
|
+
.summary-card .value { font-size: 2.4em; font-weight: 600; margin-bottom: 8px; }
|
|
2211
|
+
.summary-card .trend-percentage { font-size: 1em; color: var(--dark-gray-color); }
|
|
2212
|
+
.status-passed .value, .stat-passed svg { color: var(--success-color); }
|
|
2213
|
+
.status-failed .value, .stat-failed svg { color: var(--danger-color); }
|
|
2214
|
+
.status-skipped .value, .stat-skipped svg { color: var(--warning-color); }
|
|
2215
|
+
.dashboard-bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 28px; align-items: stretch; }
|
|
2216
|
+
.pie-chart-wrapper, .suites-widget, .trend-chart { background-color: var(--card-background-color); padding: 28px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
|
|
2217
|
+
.pie-chart-wrapper h3, .suites-header h2, .trend-chart h3 { text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color); }
|
|
2218
|
+
.trend-chart-container, .pie-chart-wrapper div[id^="pieChart-"] { flex-grow: 1; min-height: 250px; }
|
|
2219
|
+
.status-badge-small-tooltip { padding: 2px 5px; border-radius: 3px; font-size: 0.9em; font-weight: 600; color: white; text-transform: uppercase; }
|
|
2220
|
+
.status-badge-small-tooltip.status-passed { background-color: var(--success-color); }
|
|
2221
|
+
.status-badge-small-tooltip.status-failed { background-color: var(--danger-color); }
|
|
2222
|
+
.status-badge-small-tooltip.status-skipped { background-color: var(--warning-color); }
|
|
2223
|
+
.status-badge-small-tooltip.status-unknown { background-color: var(--dark-gray-color); }
|
|
2224
|
+
.suites-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
2225
|
+
.summary-badge { background-color: var(--light-gray-color); color: var(--text-color-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
|
|
2226
|
+
.suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|
2227
|
+
.suite-card { border: 1px solid var(--border-color); border-left-width: 5px; border-radius: calc(var(--border-radius) / 1.5); padding: 20px; background-color: var(--card-background-color); transition: box-shadow 0.2s ease, border-left-color 0.2s ease; }
|
|
2228
|
+
.suite-card:hover { box-shadow: var(--box-shadow); }
|
|
2229
|
+
.suite-card.status-passed { border-left-color: var(--success-color); }
|
|
2230
|
+
.suite-card.status-failed { border-left-color: var(--danger-color); }
|
|
2231
|
+
.suite-card.status-skipped { border-left-color: var(--warning-color); }
|
|
2232
|
+
.suite-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
|
2233
|
+
.suite-name { font-weight: 600; font-size: 1.05em; color: var(--text-color); margin-right: 10px; word-break: break-word;}
|
|
2234
|
+
.browser-tag { font-size: 0.8em; background-color: var(--medium-gray-color); color: var(--text-color-secondary); padding: 3px 8px; border-radius: 4px; white-space: nowrap;}
|
|
2235
|
+
.suite-card-body .test-count { font-size: 0.95em; color: var(--text-color-secondary); display: block; margin-bottom: 10px; }
|
|
2236
|
+
.suite-stats { display: flex; gap: 14px; font-size: 0.95em; align-items: center; }
|
|
2237
|
+
.suite-stats span { display: flex; align-items: center; gap: 6px; }
|
|
2238
|
+
.suite-stats svg { vertical-align: middle; font-size: 1.15em; }
|
|
2239
|
+
.filters { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 28px; padding: 20px; background-color: var(--light-gray-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-inset); border: 1px solid var(--border-color); }
|
|
2240
|
+
.filters input, .filters select, .filters button { padding: 11px 15px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 1em; background-color: var(--card-background-color); color: var(--text-color); }
|
|
2241
|
+
.filters input { flex-grow: 1; min-width: 240px;}
|
|
2242
|
+
.filters select {min-width: 180px;}
|
|
2243
|
+
.filters button { background-color: var(--primary-color); color: white; cursor: pointer; transition: background-color 0.2s ease, box-shadow 0.2s ease; border: none; }
|
|
2244
|
+
.filters button:hover { background-color: var(--accent-color); box-shadow: 0 2px 5px rgba(0,0,0,0.3);}
|
|
2245
|
+
.test-case { margin-bottom: 15px; border: 1px solid var(--border-color); border-radius: var(--border-radius); background-color: var(--card-background-color); box-shadow: var(--box-shadow-light); overflow: hidden; }
|
|
2246
|
+
.test-case-header { padding: 10px 15px; background-color: var(--card-background-color); cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid transparent; transition: background-color 0.2s ease; }
|
|
2247
|
+
.test-case-header:hover { background-color: var(--light-gray-color); }
|
|
2248
|
+
.test-case-header[aria-expanded="true"] { border-bottom-color: var(--border-color); background-color: var(--light-gray-color); }
|
|
2249
|
+
.test-case-summary { display: flex; align-items: center; gap: 14px; flex-grow: 1; flex-wrap: wrap;}
|
|
2250
|
+
.test-case-title { font-weight: 600; color: var(--text-color); font-size: 1em; }
|
|
2251
|
+
.test-case-browser { font-size: 0.9em; color: var(--text-color-secondary); }
|
|
2252
|
+
.test-case-meta { display: flex; align-items: center; gap: 12px; font-size: 0.9em; color: var(--text-color-secondary); flex-shrink: 0; }
|
|
2253
|
+
.test-duration { background-color: var(--light-gray-color); padding: 4px 10px; border-radius: 12px; font-size: 0.9em;}
|
|
2254
|
+
.status-badge { padding: 5px; border-radius: 6px; font-size: 0.8em; font-weight: 600; color: white; text-transform: uppercase; min-width: 70px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
|
|
2255
|
+
.status-badge.status-passed { background-color: var(--success-color); }
|
|
2256
|
+
.status-badge.status-failed { background-color: var(--danger-color); }
|
|
2257
|
+
.status-badge.status-skipped { background-color: var(--warning-color); }
|
|
2258
|
+
.status-badge.status-unknown { background-color: var(--dark-gray-color); }
|
|
2259
|
+
.tag { display: inline-block; background: linear-gradient(#4b5563, #1f2937, #111827); color: #f9fafb; padding: 3px 10px; border-radius: 12px; font-size: 0.85em; margin-right: 6px; font-weight: 400; }
|
|
2260
|
+
.test-case-content { display: none; padding: 20px; border-top: 1px solid var(--border-color); background-color: var(--light-gray-color); }
|
|
2261
|
+
.test-case-content h4 { margin-top: 22px; margin-bottom: 14px; font-size: 1.15em; color: var(--primary-color); }
|
|
2262
|
+
.test-case-content p { margin-bottom: 10px; font-size: 1em; }
|
|
2263
|
+
.test-error-summary { margin-bottom: 20px; padding: 14px; background-color: rgba(248,113,113,0.1); border: 1px solid rgba(248,113,113,0.3); border-left: 4px solid var(--danger-color); border-radius: 4px; }
|
|
2264
|
+
.test-error-summary h4 { color: var(--danger-color); margin-top:0;}
|
|
2265
|
+
.test-error-summary pre { white-space: pre-wrap; word-break: break-all; color: var(--danger-color); font-size: 0.95em;}
|
|
2266
|
+
.steps-list { margin: 18px 0; }
|
|
2267
|
+
@supports (content-visibility: auto) {
|
|
2268
|
+
.tab-content,
|
|
2269
|
+
#test-runs .test-case,
|
|
2270
|
+
.attachments-section,
|
|
2271
|
+
.test-history-card,
|
|
2272
|
+
.trend-chart,
|
|
2273
|
+
.suite-card {
|
|
2274
|
+
content-visibility: auto;
|
|
2275
|
+
contain-intrinsic-size: 1px 600px;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
.test-case,
|
|
2279
|
+
.test-history-card,
|
|
2280
|
+
.suite-card,
|
|
2281
|
+
.attachments-section {
|
|
2282
|
+
contain: content;
|
|
2283
|
+
}
|
|
2284
|
+
.attachments-grid .attachment-item img.lazy-load-image {
|
|
2285
|
+
width: 100%;
|
|
2286
|
+
aspect-ratio: 4 / 3;
|
|
2287
|
+
object-fit: cover;
|
|
2288
|
+
}
|
|
2289
|
+
.attachments-grid .attachment-item.video-item {
|
|
2290
|
+
aspect-ratio: 16 / 9;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
.step-item { margin-bottom: 8px; padding-left: calc(var(--depth, 0) * 28px); }
|
|
2294
|
+
.step-header { display: flex; align-items: center; cursor: pointer; padding: 10px 14px; border-radius: 6px; background-color: var(--card-background-color); border: 1px solid var(--light-gray-color); transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; }
|
|
2295
|
+
.step-header:hover { background-color: var(--light-gray-color); border-color: var(--medium-gray-color); box-shadow: var(--box-shadow-inset); }
|
|
2296
|
+
.step-icon { margin-right: 12px; width: 20px; text-align: center; font-size: 1.1em; }
|
|
2297
|
+
.step-title { flex: 1; font-size: 1em; }
|
|
2298
|
+
.step-duration { color: var(--dark-gray-color); font-size: 0.9em; }
|
|
2299
|
+
.step-details { display: none; padding: 14px; margin-top: 8px; background: var(--light-gray-color); border-radius: 6px; font-size: 0.95em; border: 1px solid var(--light-gray-color); }
|
|
2300
|
+
.step-info { margin-bottom: 8px; }
|
|
2301
|
+
.test-error-summary { color: var(--danger-color); margin-top: 12px; padding: 14px; background: rgba(248,113,113,0.1); border-radius: 4px; font-size: 0.95em; border-left: 3px solid var(--danger-color); }
|
|
2302
|
+
.test-error-summary pre.stack-trace { margin-top: 10px; padding: 12px; background-color: rgba(0,0,0,0.2); border-radius: 4px; font-size:0.9em; max-height: 280px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
|
|
2303
|
+
.step-hook { background-color: rgba(96,165,250,0.1); border-left: 3px solid var(--info-color) !important; }
|
|
2304
|
+
.step-hook .step-title { font-style: italic; color: var(--info-color)}
|
|
2305
|
+
.nested-steps { margin-top: 12px; }
|
|
2306
|
+
.attachments-section { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--light-gray-color); }
|
|
2307
|
+
.attachments-section h4 { margin-top: 0; margin-bottom: 20px; font-size: 1.1em; color: var(--text-color); }
|
|
2308
|
+
.attachments-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 22px; }
|
|
2309
|
+
.attachment-item { border: 1px solid var(--border-color); border-radius: var(--border-radius); background-color: var(--card-background-color); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
|
|
2310
|
+
.attachment-item:hover { transform: translateY(-4px); box-shadow: var(--box-shadow); }
|
|
2311
|
+
.attachment-item img, .attachment-item video { width: 100%; height: 180px; object-fit: cover; display: block; background-color: var(--medium-gray-color); border-bottom: 1px solid var(--border-color); transition: opacity 0.3s ease; }
|
|
2312
|
+
.attachment-info { padding: 12px; margin-top: auto; background-color: var(--light-gray-color);}
|
|
2313
|
+
.attachment-item a:hover img { opacity: 0.85; }
|
|
2314
|
+
.attachment-caption { padding: 12px 15px; font-size: 0.9em; text-align: center; color: var(--text-color-secondary); word-break: break-word; background-color: var(--light-gray-color); }
|
|
2315
|
+
.video-item a, .trace-item a { display: block; margin-bottom: 8px; color: var(--primary-color); text-decoration: none; font-weight: 500; }
|
|
2316
|
+
.video-item a:hover, .trace-item a:hover { text-decoration: underline; }
|
|
2317
|
+
.code-section pre { background-color: #111827; color: #f9fafb; padding: 20px; border-radius: 6px; overflow-x: auto; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 0.95em; line-height:1.6;}
|
|
2318
|
+
.trend-charts-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 28px; margin-bottom: 35px; }
|
|
2319
|
+
.test-history-container h2.tab-main-title { font-size: 1.6em; margin-bottom: 18px; color: var(--primary-color); border-bottom: 1px solid var(--border-color); padding-bottom: 12px;}
|
|
2320
|
+
.test-history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 22px; margin-top: 22px; }
|
|
2321
|
+
.test-history-card { background: var(--card-background-color); border: 1px solid var(--border-color); border-radius: var(--border-radius); padding: 22px; box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
|
|
2322
|
+
.test-history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid var(--light-gray-color); }
|
|
2323
|
+
.test-history-header h3 { margin: 0; font-size: 1.15em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2324
|
+
.test-history-header p { font-weight: 500 }
|
|
2325
|
+
.test-history-trend { margin-bottom: 20px; min-height: 110px; }
|
|
2326
|
+
.test-history-trend div[id^="testHistoryChart-"] { display: block; margin: 0 auto; max-width:100%; height: 100px; width: 320px; }
|
|
2327
|
+
.test-history-details-collapsible summary { cursor: pointer; font-size: 1em; color: var(--primary-color); margin-bottom: 10px; font-weight:500; }
|
|
2328
|
+
.test-history-details-collapsible summary:hover {text-decoration: underline;}
|
|
2329
|
+
.test-history-details table { width: 100%; border-collapse: collapse; font-size: 0.95em; }
|
|
2330
|
+
.test-history-details th, .test-history-details td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--light-gray-color); }
|
|
2331
|
+
.test-history-details th { background-color: var(--light-gray-color); font-weight: 600; }
|
|
2332
|
+
.status-badge-small { padding: 3px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 600; color: white; text-transform: uppercase; display: inline-block; }
|
|
2333
|
+
.status-badge-small.status-passed { background-color: var(--success-color); }
|
|
2334
|
+
.status-badge-small.status-failed { background-color: var(--danger-color); }
|
|
2335
|
+
.status-badge-small.status-skipped { background-color: var(--warning-color); }
|
|
2336
|
+
.status-badge-small.status-unknown { background-color: var(--dark-gray-color); }
|
|
2337
|
+
.no-data, .no-tests, .no-steps, .no-data-chart { padding: 28px; text-align: center; color: var(--dark-gray-color); font-style: italic; font-size:1.1em; background-color: var(--light-gray-color); border-radius: var(--border-radius); margin: 18px 0; border: 1px dashed var(--medium-gray-color); }
|
|
2338
|
+
.no-data-chart {font-size: 0.95em; padding: 18px;}
|
|
2339
|
+
.ai-failure-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 22px; }
|
|
2340
|
+
.ai-failure-card { background: var(--card-background-color); border: 1px solid var(--border-color); border-left: 5px solid var(--danger-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); display: flex; flex-direction: column; }
|
|
2341
|
+
.ai-failure-card-header { padding: 15px 20px; border-bottom: 1px solid var(--light-gray-color); display: flex; align-items: center; justify-content: space-between; gap: 15px; }
|
|
2342
|
+
.ai-failure-card-header h3 { margin: 0; font-size: 1.1em; color: var(--text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2343
|
+
.ai-failure-card-body { padding: 20px; }
|
|
2344
|
+
.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; }
|
|
2345
|
+
.ai-fix-btn:hover { background-color: var(--accent-color); transform: translateY(-2px); }
|
|
2346
|
+
.ai-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 1050; animation: fadeIn 0.3s; }
|
|
2347
|
+
.ai-modal-content { background-color: var(--card-background-color); color: var(--text-color); border-radius: var(--border-radius); width: 90%; max-width: 800px; max-height: 90vh; box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; }
|
|
2348
|
+
.ai-modal-header { padding: 18px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
|
2349
|
+
.ai-modal-header h3 { margin: 0; font-size: 1.25em; }
|
|
2350
|
+
.ai-modal-close { font-size: 2rem; font-weight: 300; cursor: pointer; color: var(--dark-gray-color); line-height: 1; transition: color 0.2s; }
|
|
2351
|
+
.ai-modal-close:hover { color: var(--danger-color); }
|
|
2352
|
+
.ai-modal-body { padding: 25px; overflow-y: auto; }
|
|
2353
|
+
.ai-modal-body h4 { margin-top: 18px; margin-bottom: 10px; font-size: 1.1em; color: var(--primary-color); }
|
|
2354
|
+
.ai-modal-body p { margin-bottom: 15px; }
|
|
2355
|
+
.ai-loader { margin: 40px auto; border: 5px solid var(--medium-gray-color); border-top: 5px solid var(--primary-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; }
|
|
2356
|
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
2357
|
+
.trace-preview { padding: 1rem; text-align: center; background: var(--light-gray-color); border-bottom: 1px solid var(--border-color); }
|
|
2358
|
+
.trace-icon { font-size: 2rem; display: block; margin-bottom: 0.5rem; }
|
|
2359
|
+
.trace-name { word-break: break-word; font-size: 0.9rem; }
|
|
2360
|
+
.trace-actions { display: flex; gap: 0.5rem; }
|
|
2361
|
+
.trace-actions a { flex: 1; text-align: center; padding: 0.25rem 0.5rem; font-size: 0.85rem; border-radius: 4px; text-decoration: none; background: var(--primary-color); color: white; }
|
|
2362
|
+
.view-trace { background: var(--primary-color); color: white; }
|
|
2363
|
+
.view-trace:hover { background: var(--accent-color); }
|
|
2364
|
+
.download-trace { background: var(--medium-gray-color); color: var(--text-color); }
|
|
2365
|
+
.download-trace:hover { background: var(--dark-gray-color); }
|
|
2366
|
+
.filters button.clear-filters-btn { background-color: var(--medium-gray-color); color: var(--text-color); }
|
|
2367
|
+
.filters button.clear-filters-btn:hover { background-color: var(--dark-gray-color); color: #fff; }
|
|
2368
|
+
.copy-btn {color: var(--primary-color); background: var(--card-background-color); border-radius: 8px; cursor: pointer; border-color: var(--primary-color); font-size: 1em; margin-left: 93%; font-weight: 600;}
|
|
2369
|
+
.ai-analyzer-stats { display: flex; gap: 20px; margin-bottom: 25px; padding: 20px; background: linear-gradient(135deg, #374151 0%, #1f2937 100%); border-radius: var(--border-radius); justify-content: center; }
|
|
2370
|
+
.stat-item { text-align: center; color: white; }
|
|
2371
|
+
.stat-number { display: block; font-size: 2em; font-weight: 700; line-height: 1;}
|
|
2372
|
+
.stat-label { font-size: 0.9em; opacity: 0.9; font-weight: 500;}
|
|
2373
|
+
.ai-analyzer-description { margin-bottom: 25px; font-size: 1em; color: var(--text-color-secondary); text-align: center; max-width: 600px; margin-left: auto; margin-right: auto;}
|
|
2374
|
+
.compact-failure-list { display: flex; flex-direction: column; gap: 15px; }
|
|
2375
|
+
.compact-failure-item { background: var(--card-background-color); border: 1px solid var(--border-color); border-left: 4px solid var(--danger-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-light); transition: transform 0.2s ease, box-shadow 0.2s ease;}
|
|
2376
|
+
.compact-failure-item:hover { transform: translateY(-2px); box-shadow: var(--box-shadow); }
|
|
2377
|
+
.failure-header { display: flex; justify-content: space-between; align-items: center; padding: 18px 20px; gap: 15px;}
|
|
2378
|
+
.failure-main-info { flex: 1; min-width: 0; }
|
|
2379
|
+
.failure-title { margin: 0 0 8px 0; font-size: 1.1em; font-weight: 600; color: var(--text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
|
|
2380
|
+
.failure-meta { display: flex; gap: 12px; align-items: center;}
|
|
2381
|
+
.browser-indicator, .duration-indicator { font-size: 0.85em; padding: 3px 8px; border-radius: 12px; font-weight: 500;}
|
|
2382
|
+
.browser-indicator { background: var(--info-color); color: white; }
|
|
2383
|
+
#load-more-tests { font-size: 16px; padding: 4px; background-color: var(--light-gray-color); border-radius: 4px; color: var(--text-color); }
|
|
2384
|
+
.duration-indicator { background: var(--medium-gray-color); color: var(--text-color); }
|
|
2385
|
+
.compact-ai-btn { background: linear-gradient(135deg, #374151 0%, #1f2937 100%); color: white; border: none; padding: 12px 18px; border-radius: 6px; cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; white-space: nowrap;}
|
|
2386
|
+
.compact-ai-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(55, 65, 81, 0.4); }
|
|
2387
|
+
.ai-text { font-size: 0.95em; }
|
|
2388
|
+
.failure-error-preview { padding: 0 20px 18px 20px; border-top: 1px solid var(--light-gray-color);}
|
|
2389
|
+
.error-snippet { background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: 6px; padding: 12px; margin-bottom: 12px; font-family: monospace; font-size: 0.9em; color: var(--danger-color); line-height: 1.4;}
|
|
2390
|
+
.expand-error-btn { background: none; border: 1px solid var(--border-color); color: var(--text-color-secondary); padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85em; display: flex; align-items: center; gap: 6px; transition: all 0.2s ease;}
|
|
2391
|
+
.expand-error-btn:hover { background: var(--light-gray-color); border-color: var(--medium-gray-color); }
|
|
2392
|
+
.expand-icon { transition: transform 0.2s ease; font-size: 0.8em;}
|
|
2393
|
+
.expand-error-btn.expanded .expand-icon { transform: rotate(180deg); }
|
|
2394
|
+
.full-error-details { padding: 0 20px 20px 20px; border-top: 1px solid var(--light-gray-color); margin-top: 0;}
|
|
2395
|
+
.full-error-content { background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: 6px; padding: 15px; font-family: monospace; font-size: 0.9em; color: var(--danger-color); line-height: 1.4; max-height: 300px; overflow-y: auto;}
|
|
2396
|
+
@media (max-width: 1200px) { .trend-charts-row { grid-template-columns: 1fr; } }
|
|
2397
|
+
@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; } }
|
|
2398
|
+
@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; } .ai-analyzer-stats { flex-direction: column; gap: 15px; text-align: center; } .failure-header { flex-direction: column; align-items: stretch; gap: 15px; } .failure-main-info { text-align: center; } .failure-meta { justify-content: center; } .compact-ai-btn { justify-content: center; padding: 12px 20px; } }
|
|
2399
|
+
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 45px; } .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;} .stat-item .stat-number { font-size: 1.5em; } .failure-header { padding: 15px; } .failure-error-preview, .full-error-details { padding-left: 15px; padding-right: 15px; } }
|
|
2400
|
+
.trace-actions a { text-decoration: none; color: var(--primary-color); font-weight: 500; font-size: 0.9em; }
|
|
2401
|
+
.generic-attachment { text-align: center; padding: 1rem; justify-content: center; }
|
|
2402
|
+
.attachment-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
|
|
2403
|
+
.attachment-caption { display: flex; flex-direction: column; align-items: center; justify-content: center; flex-grow: 1; }
|
|
2404
|
+
.attachment-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
2405
|
+
.attachment-type { font-size: 0.8rem; color: var(--text-color-secondary); }
|
|
2406
|
+
</style>
|
|
2139
2407
|
</head>
|
|
2140
2408
|
<body>
|
|
2141
2409
|
<div class="container">
|
|
@@ -2214,7 +2482,8 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2214
2482
|
.join("")}</select>
|
|
2215
2483
|
<button id="expand-all-tests">Expand All</button> <button id="collapse-all-tests">Collapse All</button> <button id="clear-run-summary-filters" class="clear-filters-btn">Clear Filters</button>
|
|
2216
2484
|
</div>
|
|
2217
|
-
<div class="test-cases-list">${generateTestCasesHTML()}</div>
|
|
2485
|
+
<div class="test-cases-list">${generateTestCasesHTML(results.slice(0, 50), 0)}</div>
|
|
2486
|
+
${results.length > 50 ? `<div class="load-more-wrapper"><button id="load-more-tests">Load more</button></div><script type="application/json" id="remaining-tests-b64">${Buffer.from(generateTestCasesHTML(results.slice(50), 50), 'utf8').toString('base64')}</script>` : ``}
|
|
2218
2487
|
</div>
|
|
2219
2488
|
<div id="test-history" class="tab-content">
|
|
2220
2489
|
<h2 class="tab-main-title">Execution Trends</h2>
|
|
@@ -2420,10 +2689,46 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2420
2689
|
});
|
|
2421
2690
|
// --- Test Run Summary Filters ---
|
|
2422
2691
|
const nameFilter = document.getElementById('filter-name');
|
|
2692
|
+
function ensureAllTestsAppended() {
|
|
2693
|
+
const node = document.getElementById('remaining-tests-b64');
|
|
2694
|
+
const loadMoreBtn = document.getElementById('load-more-tests');
|
|
2695
|
+
if (!node) return;
|
|
2696
|
+
const b64 = (node.textContent || '').trim();
|
|
2697
|
+
function b64ToUtf8(b64Str) {
|
|
2698
|
+
try { return decodeURIComponent(escape(window.atob(b64Str))); }
|
|
2699
|
+
catch (e) { return window.atob(b64Str); }
|
|
2700
|
+
}
|
|
2701
|
+
const html = b64ToUtf8(b64);
|
|
2702
|
+
const container = document.querySelector('#test-runs .test-cases-list');
|
|
2703
|
+
if (container) container.insertAdjacentHTML('beforeend', html);
|
|
2704
|
+
if (loadMoreBtn) loadMoreBtn.remove();
|
|
2705
|
+
node.remove();
|
|
2706
|
+
}
|
|
2707
|
+
const loadMoreBtn = document.getElementById('load-more-tests');
|
|
2708
|
+
if (loadMoreBtn) {
|
|
2709
|
+
loadMoreBtn.addEventListener('click', () => {
|
|
2710
|
+
const node = document.getElementById('remaining-tests-b64');
|
|
2711
|
+
if (!node) return;
|
|
2712
|
+
const b64 = (node.textContent || '').trim();
|
|
2713
|
+
function b64ToUtf8(b64Str) {
|
|
2714
|
+
try { return decodeURIComponent(escape(window.atob(b64Str))); }
|
|
2715
|
+
catch (e) { return window.atob(b64Str); }
|
|
2716
|
+
}
|
|
2717
|
+
const html = b64ToUtf8(b64);
|
|
2718
|
+
const container = document.querySelector('#test-runs .test-cases-list');
|
|
2719
|
+
if (container) container.insertAdjacentHTML('beforeend', html);
|
|
2720
|
+
loadMoreBtn.remove();
|
|
2721
|
+
node.remove();
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
|
|
2726
|
+
|
|
2423
2727
|
const statusFilter = document.getElementById('filter-status');
|
|
2424
2728
|
const browserFilter = document.getElementById('filter-browser');
|
|
2425
2729
|
const clearRunSummaryFiltersBtn = document.getElementById('clear-run-summary-filters');
|
|
2426
2730
|
function filterTestCases() {
|
|
2731
|
+
ensureAllTestsAppended();
|
|
2427
2732
|
const nameValue = nameFilter ? nameFilter.value.toLowerCase() : "";
|
|
2428
2733
|
const statusValue = statusFilter ? statusFilter.value : "";
|
|
2429
2734
|
const browserValue = browserFilter ? browserFilter.value : "";
|
|
@@ -2442,7 +2747,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2442
2747
|
if(statusFilter) statusFilter.addEventListener('change', filterTestCases);
|
|
2443
2748
|
if(browserFilter) browserFilter.addEventListener('change', filterTestCases);
|
|
2444
2749
|
if(clearRunSummaryFiltersBtn) clearRunSummaryFiltersBtn.addEventListener('click', () => {
|
|
2445
|
-
|
|
2750
|
+
ensureAllTestsAppended();
|
|
2751
|
+
if(nameFilter) nameFilter.value = '';
|
|
2752
|
+
if(statusFilter) statusFilter.value = '';
|
|
2753
|
+
if(browserFilter) browserFilter.value = '';
|
|
2446
2754
|
filterTestCases();
|
|
2447
2755
|
});
|
|
2448
2756
|
// --- Test History Filters ---
|
|
@@ -2483,12 +2791,6 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2483
2791
|
headerElement.setAttribute('aria-expanded', String(!isExpanded));
|
|
2484
2792
|
}
|
|
2485
2793
|
}
|
|
2486
|
-
document.querySelectorAll('#test-runs .test-case-header').forEach(header => {
|
|
2487
|
-
header.addEventListener('click', () => toggleElementDetails(header));
|
|
2488
|
-
});
|
|
2489
|
-
document.querySelectorAll('#test-runs .step-header').forEach(header => {
|
|
2490
|
-
header.addEventListener('click', () => toggleElementDetails(header, '.step-details'));
|
|
2491
|
-
});
|
|
2492
2794
|
const expandAllBtn = document.getElementById('expand-all-tests');
|
|
2493
2795
|
const collapseAllBtn = document.getElementById('collapse-all-tests');
|
|
2494
2796
|
function setAllTestRunDetailsVisibility(displayMode, ariaState) {
|
|
@@ -2499,31 +2801,89 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2499
2801
|
}
|
|
2500
2802
|
if (expandAllBtn) expandAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('block', 'true'));
|
|
2501
2803
|
if (collapseAllBtn) collapseAllBtn.addEventListener('click', () => setAllTestRunDetailsVisibility('none', 'false'));
|
|
2502
|
-
|
|
2503
|
-
|
|
2804
|
+
document.addEventListener('click', (e) => {
|
|
2805
|
+
const inHighcharts = e.target && e.target.closest && e.target.closest('.highcharts-container');
|
|
2806
|
+
if (inHighcharts) {
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
const header = e.target.closest('#test-runs .test-case-header');
|
|
2810
|
+
if (header) {
|
|
2811
|
+
let contentElement = header.parentElement.querySelector('.test-case-content');
|
|
2812
|
+
if (contentElement) {
|
|
2813
|
+
const isExpanded = contentElement.style.display === 'block';
|
|
2814
|
+
contentElement.style.display = isExpanded ? 'none' : 'block';
|
|
2815
|
+
header.setAttribute('aria-expanded', String(!isExpanded));
|
|
2816
|
+
}
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
const stepHeader = e.target.closest('#test-runs .step-header');
|
|
2820
|
+
if (stepHeader) {
|
|
2821
|
+
let details = stepHeader.nextElementSibling;
|
|
2822
|
+
if (details && details.matches('.step-details')) {
|
|
2823
|
+
const isExpanded = details.style.display === 'block';
|
|
2824
|
+
details.style.display = isExpanded ? 'none' : 'block';
|
|
2825
|
+
stepHeader.setAttribute('aria-expanded', String(!isExpanded));
|
|
2826
|
+
}
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
const img = e.target.closest('img.lazy-load-image');
|
|
2830
|
+
if (img && img.dataset && img.dataset.src) {
|
|
2831
|
+
if (e.preventDefault) e.preventDefault();
|
|
2832
|
+
img.src = img.dataset.src;
|
|
2833
|
+
img.removeAttribute('data-src');
|
|
2834
|
+
const parentLink = img.closest('a.lazy-load-attachment');
|
|
2835
|
+
if (parentLink && parentLink.dataset && parentLink.dataset.href) {
|
|
2836
|
+
parentLink.href = parentLink.dataset.href;
|
|
2837
|
+
parentLink.removeAttribute('data-href');
|
|
2838
|
+
}
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
const video = e.target.closest('video.lazy-load-video');
|
|
2842
|
+
if (video) {
|
|
2843
|
+
if (e.preventDefault) e.preventDefault();
|
|
2844
|
+
const s = video.querySelector('source');
|
|
2845
|
+
if (s && s.dataset && s.dataset.src && !s.src) {
|
|
2846
|
+
s.src = s.dataset.src;
|
|
2847
|
+
s.removeAttribute('data-src');
|
|
2848
|
+
video.load();
|
|
2849
|
+
} else if (video.dataset && video.dataset.src && !video.src) {
|
|
2850
|
+
video.src = video.dataset.src;
|
|
2851
|
+
video.removeAttribute('data-src');
|
|
2852
|
+
video.load();
|
|
2853
|
+
}
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
const a = e.target.closest('a.lazy-load-attachment');
|
|
2857
|
+
if (a && a.dataset && a.dataset.href) {
|
|
2858
|
+
e.preventDefault();
|
|
2859
|
+
a.href = a.dataset.href;
|
|
2860
|
+
a.removeAttribute('data-href');
|
|
2861
|
+
a.click();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
document.addEventListener('play', (e) => {
|
|
2866
|
+
const video = e.target && e.target.closest ? e.target.closest('video.lazy-load-video') : null;
|
|
2867
|
+
if (video) {
|
|
2868
|
+
const s = video.querySelector('source');
|
|
2869
|
+
if (s && s.dataset && s.dataset.src && !s.src) {
|
|
2870
|
+
s.src = s.dataset.src;
|
|
2871
|
+
s.removeAttribute('data-src');
|
|
2872
|
+
video.load();
|
|
2873
|
+
} else if (video.dataset && video.dataset.src && !video.src) {
|
|
2874
|
+
video.src = video.dataset.src;
|
|
2875
|
+
video.removeAttribute('data-src');
|
|
2876
|
+
video.load();
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}, true);
|
|
2880
|
+
const lazyLoadElements = document.querySelectorAll('.lazy-load-chart, .lazy-load-iframe');
|
|
2504
2881
|
if ('IntersectionObserver' in window) {
|
|
2505
2882
|
let lazyObserver = new IntersectionObserver((entries, observer) => {
|
|
2506
2883
|
entries.forEach(entry => {
|
|
2507
2884
|
if (entry.isIntersecting) {
|
|
2508
2885
|
const element = entry.target;
|
|
2509
|
-
if (element.classList.contains('lazy-load-
|
|
2510
|
-
if (element.dataset.src) {
|
|
2511
|
-
element.src = element.dataset.src;
|
|
2512
|
-
element.removeAttribute('data-src');
|
|
2513
|
-
}
|
|
2514
|
-
} else if (element.classList.contains('lazy-load-video')) {
|
|
2515
|
-
const source = element.querySelector('source');
|
|
2516
|
-
if (source && source.dataset.src) {
|
|
2517
|
-
source.src = source.dataset.src;
|
|
2518
|
-
source.removeAttribute('data-src');
|
|
2519
|
-
element.load();
|
|
2520
|
-
}
|
|
2521
|
-
} else if (element.classList.contains('lazy-load-attachment')) {
|
|
2522
|
-
if (element.dataset.href) {
|
|
2523
|
-
element.href = element.dataset.href;
|
|
2524
|
-
element.removeAttribute('data-href');
|
|
2525
|
-
}
|
|
2526
|
-
} else if (element.classList.contains('lazy-load-iframe')) {
|
|
2886
|
+
if (element.classList.contains('lazy-load-iframe')) {
|
|
2527
2887
|
if (element.dataset.src) {
|
|
2528
2888
|
element.src = element.dataset.src;
|
|
2529
2889
|
element.removeAttribute('data-src');
|
|
@@ -2539,13 +2899,10 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2539
2899
|
});
|
|
2540
2900
|
}, { rootMargin: "0px 0px 200px 0px" });
|
|
2541
2901
|
lazyLoadElements.forEach(el => lazyObserver.observe(el));
|
|
2542
|
-
} else {
|
|
2902
|
+
} else {
|
|
2543
2903
|
lazyLoadElements.forEach(element => {
|
|
2544
|
-
if (element.classList.contains('lazy-load-
|
|
2545
|
-
else if (element.classList.contains('lazy-load-
|
|
2546
|
-
else if (element.classList.contains('lazy-load-attachment') && element.dataset.href) element.href = element.dataset.href;
|
|
2547
|
-
else if (element.classList.contains('lazy-load-iframe') && element.dataset.src) element.src = element.dataset.src;
|
|
2548
|
-
else if (element.classList.contains('lazy-load-chart')) { const renderFn = element.dataset.renderFunctionName; if(renderFn && window[renderFn]) window[renderFn](); }
|
|
2904
|
+
if (element.classList.contains('lazy-load-iframe') && element.dataset.src) element.src = element.dataset.src;
|
|
2905
|
+
else if (element.classList.contains('lazy-load-chart')) { const renderFn = element.dataset.renderFunctionName; if (renderFn && window[renderFn]) window[renderFn](); }
|
|
2549
2906
|
});
|
|
2550
2907
|
}
|
|
2551
2908
|
}
|
|
@@ -2611,6 +2968,11 @@ async function runScript(scriptPath) {
|
|
|
2611
2968
|
});
|
|
2612
2969
|
});
|
|
2613
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* The main function that orchestrates the generation of the static HTML report.
|
|
2973
|
+
* It reads the latest test run data, loads historical data for trend analysis,
|
|
2974
|
+
* prepares the data, and then generates and writes the final HTML report file.
|
|
2975
|
+
*/
|
|
2614
2976
|
async function main() {
|
|
2615
2977
|
const __filename = fileURLToPath(import.meta.url);
|
|
2616
2978
|
const __dirname = path.dirname(__filename);
|