robo-automation-test-kit 1.0.0__py3-none-any.whl
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.
- robo_automation_test_kit/__init__.py +8 -0
- robo_automation_test_kit/hookspec.py +36 -0
- robo_automation_test_kit/plugin.py +867 -0
- robo_automation_test_kit/templates/email_report/email_template.html +0 -0
- robo_automation_test_kit/templates/html_report/components/category-chart.html +148 -0
- robo_automation_test_kit/templates/html_report/components/center-chart.html +97 -0
- robo_automation_test_kit/templates/html_report/components/phase-chart.html +148 -0
- robo_automation_test_kit/templates/html_report/components/results-table.html +240 -0
- robo_automation_test_kit/templates/html_report/components/status-center-chart.html +148 -0
- robo_automation_test_kit/templates/html_report/components/summary-chart.html +94 -0
- robo_automation_test_kit/templates/html_report/html_template.html +62 -0
- robo_automation_test_kit/templates/html_report/scripts/css/material-icons.css +20 -0
- robo_automation_test_kit/templates/html_report/scripts/css/report.css +714 -0
- robo_automation_test_kit/templates/html_report/scripts/css/robo-fonts.css +24 -0
- robo_automation_test_kit/templates/html_report/scripts/js/chart.js +14 -0
- robo_automation_test_kit/templates/html_report/scripts/js/report.js +319 -0
- robo_automation_test_kit/utils/RoboHelper.py +420 -0
- robo_automation_test_kit/utils/__init__.py +19 -0
- robo_automation_test_kit/utils/reports/EmailReportUtils.py +0 -0
- robo_automation_test_kit/utils/reports/HtmlReportUtils.py +154 -0
- robo_automation_test_kit/utils/reports/__init__.py +3 -0
- robo_automation_test_kit-1.0.0.dist-info/METADATA +132 -0
- robo_automation_test_kit-1.0.0.dist-info/RECORD +26 -0
- robo_automation_test_kit-1.0.0.dist-info/WHEEL +4 -0
- robo_automation_test_kit-1.0.0.dist-info/entry_points.txt +3 -0
- robo_automation_test_kit-1.0.0.dist-info/licenses/LICENSE +201 -0
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!-- Status by Request Category Chart Component -->
|
|
2
|
+
<div class="chart-container">
|
|
3
|
+
<canvas id="statusByCategoryChart" width="360" height="288"></canvas>
|
|
4
|
+
</div>
|
|
5
|
+
<script>
|
|
6
|
+
function initializeCategoryChart() {
|
|
7
|
+
const ctx = document.getElementById('statusByCategoryChart');
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
setTimeout(initializeCategoryChart, 100);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof window.statusColors === 'undefined' || typeof window.add3DEffect === 'undefined' || typeof Chart === 'undefined') {
|
|
14
|
+
setTimeout(initializeCategoryChart, 100);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const categoryStatusData = [
|
|
19
|
+
{% for result in report_rows %}
|
|
20
|
+
{
|
|
21
|
+
category: `{{ result['Request Category']|e }}`,
|
|
22
|
+
status: `{{ result.test_status|e }}`
|
|
23
|
+
}{% if not loop.last %},{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const statusTypes = window.statusTypes || ['PASSED', 'FAILED', 'SKIPPED'];
|
|
28
|
+
const statusColors = window.statusColors;
|
|
29
|
+
const add3DEffect = window.add3DEffect;
|
|
30
|
+
|
|
31
|
+
const categorySet = new Set();
|
|
32
|
+
categoryStatusData.forEach(r => {
|
|
33
|
+
if (r.category && r.category !== '-') categorySet.add(r.category);
|
|
34
|
+
});
|
|
35
|
+
const categoryLabels = Array.from(categorySet);
|
|
36
|
+
|
|
37
|
+
const statusByCategory = {};
|
|
38
|
+
categoryStatusData.forEach(r => {
|
|
39
|
+
const category = r.category;
|
|
40
|
+
let status = (r.status || '').toUpperCase();
|
|
41
|
+
if (status === 'ERROR') status = 'FAILED';
|
|
42
|
+
if (!category || category === '-' || !statusTypes.includes(status)) return;
|
|
43
|
+
if (!statusByCategory[category]) statusByCategory[category] = { PASSED: 0, FAILED: 0, SKIPPED: 0 };
|
|
44
|
+
if (statusByCategory[category][status] !== undefined) statusByCategory[category][status]++;
|
|
45
|
+
if (statusByCategory[category][status] !== undefined) statusByCategory[category][status]++;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const categoryDatasets = statusTypes.map(st => ({
|
|
49
|
+
label: st.charAt(0) + st.slice(1).toLowerCase(),
|
|
50
|
+
data: categoryLabels.map(c => (statusByCategory[c] ? statusByCategory[c][st] : 0)),
|
|
51
|
+
backgroundColor: statusColors[st],
|
|
52
|
+
borderWidth: 1
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const ctxObj = ctx.getContext('2d');
|
|
56
|
+
const cat3dDatasets = categoryDatasets.map(ds => ({
|
|
57
|
+
...ds,
|
|
58
|
+
backgroundColor: add3DEffect(ctxObj, 'bar', [statusColors[ds.label.toUpperCase()]])
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Destroy existing chart instance if it exists
|
|
62
|
+
const existingChart = Chart.getChart('statusByCategoryChart');
|
|
63
|
+
if (existingChart) {
|
|
64
|
+
existingChart.destroy();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
new Chart(ctxObj, {
|
|
68
|
+
type: 'bar',
|
|
69
|
+
data: {
|
|
70
|
+
labels: categoryLabels,
|
|
71
|
+
datasets: cat3dDatasets
|
|
72
|
+
},
|
|
73
|
+
options: {
|
|
74
|
+
indexAxis: 'y',
|
|
75
|
+
responsive: false,
|
|
76
|
+
plugins: {
|
|
77
|
+
legend: {
|
|
78
|
+
display: true,
|
|
79
|
+
position: 'top',
|
|
80
|
+
align: 'center',
|
|
81
|
+
margin: 0,
|
|
82
|
+
labels: {
|
|
83
|
+
font: { size: 10 },
|
|
84
|
+
usePointStyle: true,
|
|
85
|
+
boxHeight: 12,
|
|
86
|
+
pointStyle: 'rect',
|
|
87
|
+
generateLabels: function(chart) {
|
|
88
|
+
return chart.data.datasets.map((ds, i) => {
|
|
89
|
+
const label = ds.label;
|
|
90
|
+
const color = statusColors[label.toUpperCase()] || '#888';
|
|
91
|
+
return {
|
|
92
|
+
text: label,
|
|
93
|
+
fillStyle: color,
|
|
94
|
+
strokeStyle: color,
|
|
95
|
+
lineWidth: 1,
|
|
96
|
+
pointStyle: 'rect',
|
|
97
|
+
hidden: false,
|
|
98
|
+
index: i
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
title: { display: true, text: 'Status by Request Category', font: { size: 18 }, padding: { top: 0, bottom: 4 } }
|
|
105
|
+
},
|
|
106
|
+
scales: {
|
|
107
|
+
x: {
|
|
108
|
+
stacked: true,
|
|
109
|
+
title: {
|
|
110
|
+
display: true,
|
|
111
|
+
text: 'Test Cases',
|
|
112
|
+
font: { size: 12, weight: 'bold' },
|
|
113
|
+
color: '#212121'
|
|
114
|
+
},
|
|
115
|
+
ticks: {
|
|
116
|
+
font: { size: 10, weight: 'bold' },
|
|
117
|
+
color: '#212121',
|
|
118
|
+
stepSize: 1,
|
|
119
|
+
callback: function(value) {
|
|
120
|
+
if (Number.isInteger(value)) return value;
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
y: {
|
|
126
|
+
stacked: true,
|
|
127
|
+
title: { display: false },
|
|
128
|
+
ticks: {
|
|
129
|
+
font: { size: 10, weight: 'bold' },
|
|
130
|
+
color: '#212121',
|
|
131
|
+
stepSize: 1,
|
|
132
|
+
callback: function(value, index, ticks) {
|
|
133
|
+
return categoryLabels && categoryLabels[index] ? categoryLabels[index] : value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
(function() {
|
|
142
|
+
if (document.readyState === 'loading') {
|
|
143
|
+
document.addEventListener('DOMContentLoaded', initializeCategoryChart);
|
|
144
|
+
} else {
|
|
145
|
+
initializeCategoryChart();
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
</script>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!-- Distribution by Center Chart Component -->
|
|
2
|
+
<div class="chart-container">
|
|
3
|
+
<canvas id="centerChart" width="360" height="288"></canvas>
|
|
4
|
+
</div>
|
|
5
|
+
<script>
|
|
6
|
+
function initializeCenterChart() {
|
|
7
|
+
const ctx = document.getElementById('centerChart');
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
setTimeout(initializeCenterChart, 100);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof window.centerPalette === 'undefined' || typeof window.add3DEffect === 'undefined' || typeof Chart === 'undefined') {
|
|
14
|
+
setTimeout(initializeCenterChart, 100);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const centerData = [
|
|
19
|
+
{% for result in report_rows %}
|
|
20
|
+
`{{ result.Center|e }}`{% if not loop.last %},{% endif %}
|
|
21
|
+
{% endfor %}
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const centerCounts = {};
|
|
25
|
+
centerData.forEach(c => {
|
|
26
|
+
if (c && c !== '-') {
|
|
27
|
+
centerCounts[c] = (centerCounts[c] || 0) + 1;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const centerLabels = Object.keys(centerCounts);
|
|
31
|
+
const centerValues = centerLabels.map(c => centerCounts[c]);
|
|
32
|
+
|
|
33
|
+
// Destroy existing chart instance if it exists
|
|
34
|
+
const existingChart = Chart.getChart('centerChart');
|
|
35
|
+
if (existingChart) {
|
|
36
|
+
existingChart.destroy();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ctxObj = ctx.getContext('2d');
|
|
40
|
+
const centerColors = window.centerPalette.slice(0, centerLabels.length);
|
|
41
|
+
const center3dDatasets = [{
|
|
42
|
+
data: centerValues,
|
|
43
|
+
backgroundColor: window.add3DEffect(ctxObj, 'doughnut', centerColors),
|
|
44
|
+
borderWidth: 1
|
|
45
|
+
}];
|
|
46
|
+
|
|
47
|
+
new Chart(ctxObj, {
|
|
48
|
+
type: 'doughnut',
|
|
49
|
+
data: {
|
|
50
|
+
labels: centerLabels,
|
|
51
|
+
datasets: center3dDatasets
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: false,
|
|
55
|
+
plugins: {
|
|
56
|
+
title: {
|
|
57
|
+
display: true,
|
|
58
|
+
text: 'Distribution by Center',
|
|
59
|
+
font: { size: 16, weight: 'bold' },
|
|
60
|
+
color: '#212121',
|
|
61
|
+
padding: { top: 0, bottom: 12 }
|
|
62
|
+
},
|
|
63
|
+
legend: {
|
|
64
|
+
display: true,
|
|
65
|
+
position: 'right',
|
|
66
|
+
align: 'center',
|
|
67
|
+
labels: {
|
|
68
|
+
font: { size: 14 },
|
|
69
|
+
usePointStyle: true,
|
|
70
|
+
pointStyle: 'circle',
|
|
71
|
+
generateLabels: function(chart) {
|
|
72
|
+
const data = chart.data;
|
|
73
|
+
return data.labels.map((label, i) => {
|
|
74
|
+
return {
|
|
75
|
+
text: label,
|
|
76
|
+
fillStyle: centerColors[i],
|
|
77
|
+
strokeStyle: centerColors[i],
|
|
78
|
+
lineWidth: 1,
|
|
79
|
+
hidden: false,
|
|
80
|
+
index: i
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
(function() {
|
|
91
|
+
if (document.readyState === 'loading') {
|
|
92
|
+
document.addEventListener('DOMContentLoaded', initializeCenterChart);
|
|
93
|
+
} else {
|
|
94
|
+
initializeCenterChart();
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
</script>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!-- Status by Phase Chart Component -->
|
|
2
|
+
<div class="chart-container">
|
|
3
|
+
<canvas id="statusByPhaseChart" width="360" height="288"></canvas>
|
|
4
|
+
</div>
|
|
5
|
+
<script>
|
|
6
|
+
function initializePhaseChart() {
|
|
7
|
+
const ctx = document.getElementById('statusByPhaseChart');
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
setTimeout(initializePhaseChart, 100);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof window.statusColors === 'undefined' || typeof window.add3DEffect === 'undefined' || typeof Chart === 'undefined') {
|
|
14
|
+
setTimeout(initializePhaseChart, 100);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const phaseStatusData = [
|
|
19
|
+
{% for result in report_rows %}
|
|
20
|
+
{
|
|
21
|
+
phase: `{{ result.Phase|e }}`,
|
|
22
|
+
status: `{{ result.test_status|e }}`
|
|
23
|
+
}{% if not loop.last %},{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const statusTypes = window.statusTypes || ['PASSED', 'FAILED', 'SKIPPED'];
|
|
28
|
+
const statusColors = window.statusColors;
|
|
29
|
+
const add3DEffect = window.add3DEffect;
|
|
30
|
+
|
|
31
|
+
const phaseSet = new Set();
|
|
32
|
+
phaseStatusData.forEach(r => {
|
|
33
|
+
if (r.phase && r.phase !== '-') phaseSet.add(r.phase);
|
|
34
|
+
});
|
|
35
|
+
const phaseLabels = Array.from(phaseSet);
|
|
36
|
+
|
|
37
|
+
const statusByPhase = {};
|
|
38
|
+
phaseStatusData.forEach(r => {
|
|
39
|
+
const phase = r.phase;
|
|
40
|
+
let status = (r.status || '').toUpperCase();
|
|
41
|
+
if (status === 'ERROR') status = 'FAILED';
|
|
42
|
+
if (!phase || phase === '-' || !statusTypes.includes(status)) return;
|
|
43
|
+
if (!statusByPhase[phase]) statusByPhase[phase] = { PASSED: 0, FAILED: 0, SKIPPED: 0 };
|
|
44
|
+
if (statusByPhase[phase][status] !== undefined) statusByPhase[phase][status]++;
|
|
45
|
+
if (statusByPhase[phase][status] !== undefined) statusByPhase[phase][status]++;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const phaseDatasets = statusTypes.map(st => ({
|
|
49
|
+
label: st.charAt(0) + st.slice(1).toLowerCase(),
|
|
50
|
+
data: phaseLabels.map(p => (statusByPhase[p] ? statusByPhase[p][st] : 0)),
|
|
51
|
+
backgroundColor: statusColors[st],
|
|
52
|
+
borderWidth: 1
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const ctxObj = ctx.getContext('2d');
|
|
56
|
+
const phase3dDatasets = phaseDatasets.map(ds => ({
|
|
57
|
+
...ds,
|
|
58
|
+
backgroundColor: add3DEffect(ctxObj, 'bar', [statusColors[ds.label.toUpperCase()]])
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Destroy existing chart instance if it exists
|
|
62
|
+
const existingChart = Chart.getChart('statusByPhaseChart');
|
|
63
|
+
if (existingChart) {
|
|
64
|
+
existingChart.destroy();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
new Chart(ctxObj, {
|
|
68
|
+
type: 'bar',
|
|
69
|
+
data: {
|
|
70
|
+
labels: phaseLabels,
|
|
71
|
+
datasets: phase3dDatasets
|
|
72
|
+
},
|
|
73
|
+
options: {
|
|
74
|
+
responsive: false,
|
|
75
|
+
plugins: {
|
|
76
|
+
legend: {
|
|
77
|
+
display: true,
|
|
78
|
+
position: 'top',
|
|
79
|
+
align: 'center',
|
|
80
|
+
margin: 0,
|
|
81
|
+
labels: {
|
|
82
|
+
font: { size: 10 },
|
|
83
|
+
usePointStyle: true,
|
|
84
|
+
boxHeight: 12,
|
|
85
|
+
pointStyle: 'rect',
|
|
86
|
+
generateLabels: function(chart) {
|
|
87
|
+
return chart.data.datasets.map((ds, i) => {
|
|
88
|
+
const label = ds.label;
|
|
89
|
+
const color = statusColors[label.toUpperCase()] || '#888';
|
|
90
|
+
return {
|
|
91
|
+
text: label,
|
|
92
|
+
fillStyle: color,
|
|
93
|
+
strokeStyle: color,
|
|
94
|
+
lineWidth: 1,
|
|
95
|
+
pointStyle: 'rect',
|
|
96
|
+
hidden: false,
|
|
97
|
+
index: i
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
title: { display: true, text: 'Status by Phase', font: { size: 18 }, padding: { top: 0, bottom: 4 } }
|
|
104
|
+
},
|
|
105
|
+
scales: {
|
|
106
|
+
x: {
|
|
107
|
+
stacked: true,
|
|
108
|
+
title: { display: false },
|
|
109
|
+
ticks: {
|
|
110
|
+
font: { size: 10, weight: 'bold' },
|
|
111
|
+
color: '#212121',
|
|
112
|
+
stepSize: 1,
|
|
113
|
+
callback: function(value, index) {
|
|
114
|
+
return phaseLabels && phaseLabels[index] ? phaseLabels[index] : value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
y: {
|
|
119
|
+
stacked: true,
|
|
120
|
+
beginAtZero: true,
|
|
121
|
+
title: {
|
|
122
|
+
display: true,
|
|
123
|
+
text: 'Test Cases',
|
|
124
|
+
font: { size: 12, weight: 'bold' },
|
|
125
|
+
color: '#212121'
|
|
126
|
+
},
|
|
127
|
+
ticks: {
|
|
128
|
+
font: { size: 10, weight: 'bold' },
|
|
129
|
+
color: '#212121',
|
|
130
|
+
stepSize: 1,
|
|
131
|
+
callback: function(value) {
|
|
132
|
+
if (Number.isInteger(value)) return value;
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
(function() {
|
|
142
|
+
if (document.readyState === 'loading') {
|
|
143
|
+
document.addEventListener('DOMContentLoaded', initializePhaseChart);
|
|
144
|
+
} else {
|
|
145
|
+
initializePhaseChart();
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
</script>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<!-- Results Table Component -->
|
|
2
|
+
<div class="execution-summary">
|
|
3
|
+
{% set has_passed = report_rows|selectattr('test_status', 'equalto', 'PASSED')|list|length > 0 %}
|
|
4
|
+
{% set has_failed = report_rows|selectattr('test_status', 'equalto', 'FAILED')|list|length > 0 %}
|
|
5
|
+
{% set has_skipped = report_rows|selectattr('test_status', 'equalto', 'SKIPPED')|list|length > 0 %}
|
|
6
|
+
<span class="execution-label">
|
|
7
|
+
{% if has_passed %}
|
|
8
|
+
<input type="checkbox" class="status-filter" value="PASSED" checked>
|
|
9
|
+
<label for="status-passed" class="passed">PASSED</label>
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if has_failed %}
|
|
12
|
+
<input type="checkbox" class="status-filter" value="FAILED" checked>
|
|
13
|
+
<label for="status-failed" class="failed">FAILED</label>
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if has_skipped %}
|
|
16
|
+
<input type="checkbox" class="status-filter" value="SKIPPED" checked>
|
|
17
|
+
<label for="status-skipped" class="skipped">SKIPPED</label>
|
|
18
|
+
{% endif %}
|
|
19
|
+
</span>
|
|
20
|
+
<input type="text" class="filter-input" id="resultsTableFilter" placeholder="Filter results...">
|
|
21
|
+
<span class="execution-details">
|
|
22
|
+
Executed <span class="execution-count">{{ summary.total }}</span> test cases in <span class="execution-count">{{
|
|
23
|
+
summary.duration }}</span> (HH:MM:SS)
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<table class="results-table" id="resultsTable">
|
|
28
|
+
<thead>
|
|
29
|
+
<tr>
|
|
30
|
+
<th class="row_number" data-column-type="row_number">#</th>
|
|
31
|
+
<th class="sortable test_status" data-column-type="test_status">Status</th>
|
|
32
|
+
<th class="sortable test_case_name" data-column-type="test_case_name">Test Case Name</th>
|
|
33
|
+
<th class="sortable phase" data-column-type="phase">Phase</th>
|
|
34
|
+
<th class="sortable request_category" data-column-type="request_category">Request Category</th>
|
|
35
|
+
<th class="sortable request_sub_category" data-column-type="request_sub_category">Request Sub-Category</th>
|
|
36
|
+
<th class="sortable center" data-column-type="center">Center</th>
|
|
37
|
+
<th class="sortable duration" data-column-type="duration">Duration</th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
{% for row in report_rows %}
|
|
42
|
+
<tr data-test-name="{{ row.test_case_name|e }}" data-outcome="{{ row.test_status|e }}"
|
|
43
|
+
data-error="{{ row.error_log|e }}">
|
|
44
|
+
<td>{{ loop.index }}</td>
|
|
45
|
+
<td class="status-{{ row.test_status|lower }}">
|
|
46
|
+
{{ row.test_status }}
|
|
47
|
+
</td>
|
|
48
|
+
<td>{{ row.test_case_name or '' }}</td>
|
|
49
|
+
<td>{{ row['Phase'] or '' }}</td>
|
|
50
|
+
<td>{{ row['Request Category'] or '' }}</td>
|
|
51
|
+
<td>{{ row['Request Sub-Category'] or '' }}</td>
|
|
52
|
+
<td>{{ row['Center'] or '' }}</td>
|
|
53
|
+
<td data-value="{{ row['duration']|default(0, true)|int }}">{{ format_duration(row['duration'])|default('',
|
|
54
|
+
true) }}</td>
|
|
55
|
+
</tr>
|
|
56
|
+
{% endfor %}
|
|
57
|
+
</tbody>
|
|
58
|
+
</table>
|
|
59
|
+
|
|
60
|
+
<!-- Error Modal Markup -->
|
|
61
|
+
<div id="errorModal" class="error-modal">
|
|
62
|
+
<div id="errorModalContent" class="error-modal-content">
|
|
63
|
+
<div class="error-modal-header">
|
|
64
|
+
<span id="errorModalTitle" class="error-modal-title"></span>
|
|
65
|
+
<div class="error-modal-controls">
|
|
66
|
+
<!-- Material Icons Expand/Minimize Buttons -->
|
|
67
|
+
<span id="expandIcon" class="modal-header-icon material-icons" title="Maximize"
|
|
68
|
+
onclick="toggleFullscreen()">open_in_full</span>
|
|
69
|
+
<span id="minimizeIcon" class="modal-header-icon material-icons" title="Minimize" style="display:none;"
|
|
70
|
+
onclick="toggleFullscreen()">minimize</span>
|
|
71
|
+
<button class="modal-header-icon error-modal-btn" title="Close"
|
|
72
|
+
onclick="closeErrorModal()">×</button>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div id="errorModalBody">
|
|
76
|
+
<div id="errorMessage" class="error-message"></div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<script>
|
|
82
|
+
// Results Table: Filtering and Error Modal Logic
|
|
83
|
+
|
|
84
|
+
// Reindex the row numbers based on visible rows
|
|
85
|
+
function reindexVisibleRows(table) {
|
|
86
|
+
if (!table) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const tbody = table.querySelector('tbody');
|
|
91
|
+
if (!tbody) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const rows = tbody.querySelectorAll('tr');
|
|
96
|
+
let visibleIndex = 1;
|
|
97
|
+
|
|
98
|
+
rows.forEach((row, i) => {
|
|
99
|
+
// Check if row is visible (display is not 'none')
|
|
100
|
+
const computedStyle = window.getComputedStyle(row);
|
|
101
|
+
const isVisible = computedStyle.display !== 'none';
|
|
102
|
+
|
|
103
|
+
if (isVisible && row.cells && row.cells[0]) {
|
|
104
|
+
const oldValue = row.cells[0].textContent;
|
|
105
|
+
row.cells[0].textContent = visibleIndex;
|
|
106
|
+
visibleIndex++;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Filter table based on status checkboxes and search input
|
|
112
|
+
function setupTableFilters() {
|
|
113
|
+
const statusFilters = document.querySelectorAll('.status-filter');
|
|
114
|
+
const searchInput = document.getElementById('resultsTableFilter');
|
|
115
|
+
const table = document.getElementById('resultsTable');
|
|
116
|
+
|
|
117
|
+
if (!table || statusFilters.length === 0) return;
|
|
118
|
+
|
|
119
|
+
function filterTable() {
|
|
120
|
+
const selectedStatuses = Array.from(statusFilters)
|
|
121
|
+
.filter(f => f.checked)
|
|
122
|
+
.map(f => f.value.toUpperCase());
|
|
123
|
+
|
|
124
|
+
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
|
125
|
+
const rows = table.querySelectorAll('tbody tr');
|
|
126
|
+
|
|
127
|
+
rows.forEach((row, index) => {
|
|
128
|
+
const statusCell = row.cells[1];
|
|
129
|
+
if (!statusCell) return;
|
|
130
|
+
|
|
131
|
+
const status = statusCell.textContent.trim().toUpperCase();
|
|
132
|
+
|
|
133
|
+
// Only show rows that match CHECKED statuses (if any are checked)
|
|
134
|
+
// If no statuses are checked, hide all rows
|
|
135
|
+
const matchesStatus = selectedStatuses.length > 0 && selectedStatuses.includes(status);
|
|
136
|
+
|
|
137
|
+
// Check if row text matches search term
|
|
138
|
+
const rowText = Array.from(row.cells).map(c => c.textContent.toLowerCase()).join(' ');
|
|
139
|
+
const matchesSearch = searchTerm === '' || rowText.includes(searchTerm);
|
|
140
|
+
|
|
141
|
+
const shouldShow = matchesStatus && matchesSearch;
|
|
142
|
+
row.style.display = shouldShow ? '' : 'none';
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Reindex visible rows after filtering
|
|
146
|
+
reindexVisibleRows(table);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Attach change listeners to checkboxes
|
|
150
|
+
statusFilters.forEach(filter => {
|
|
151
|
+
filter.addEventListener('change', filterTable);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Attach input listener to search box
|
|
155
|
+
if (searchInput) {
|
|
156
|
+
searchInput.addEventListener('input', filterTable);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Apply filter on initial load
|
|
160
|
+
filterTable();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Set up filters when DOM is ready
|
|
164
|
+
if (document.readyState === 'loading') {
|
|
165
|
+
document.addEventListener('DOMContentLoaded', setupTableFilters);
|
|
166
|
+
} else {
|
|
167
|
+
setupTableFilters();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Ensure reindexing happens after page fully loads and filters are applied
|
|
171
|
+
window.addEventListener('load', function () {
|
|
172
|
+
const table = document.getElementById('resultsTable');
|
|
173
|
+
if (table) {
|
|
174
|
+
// Delay to ensure filters have been applied
|
|
175
|
+
setTimeout(function () {
|
|
176
|
+
reindexVisibleRows(table);
|
|
177
|
+
}, 200);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Error modal logic
|
|
182
|
+
window.showErrorDetails = function (row) {
|
|
183
|
+
document.querySelectorAll('.results-table tr.selected').forEach(tr => tr.classList.remove('selected'));
|
|
184
|
+
row.classList.add('selected');
|
|
185
|
+
const testName = row.getAttribute('data-test-name') || 'Unknown Test';
|
|
186
|
+
const outcome = row.getAttribute('data-outcome') || 'Unknown';
|
|
187
|
+
const error = row.getAttribute('data-error') || 'No error details available';
|
|
188
|
+
document.getElementById('errorModalTitle').textContent = `${testName} - ${outcome.toUpperCase()}`;
|
|
189
|
+
document.getElementById('errorMessage').textContent = error;
|
|
190
|
+
document.getElementById('errorModal').style.display = 'block';
|
|
191
|
+
document.getElementById('expandIcon').style.display = '';
|
|
192
|
+
document.getElementById('minimizeIcon').style.display = 'none';
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
window.closeErrorModal = function () {
|
|
196
|
+
document.getElementById('errorModal').style.display = 'none';
|
|
197
|
+
document.getElementById('errorModalContent').classList.remove('fullscreen');
|
|
198
|
+
document.querySelectorAll('.results-table tr.selected').forEach(tr => tr.classList.remove('selected'));
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
window.toggleFullscreen = function () {
|
|
202
|
+
const modalContent = document.getElementById('errorModalContent');
|
|
203
|
+
const expandIcon = document.getElementById('expandIcon');
|
|
204
|
+
const minimizeIcon = document.getElementById('minimizeIcon');
|
|
205
|
+
const isFullscreen = modalContent.classList.toggle('fullscreen');
|
|
206
|
+
if (isFullscreen) {
|
|
207
|
+
expandIcon.style.display = 'none';
|
|
208
|
+
minimizeIcon.style.display = '';
|
|
209
|
+
} else {
|
|
210
|
+
expandIcon.style.display = '';
|
|
211
|
+
minimizeIcon.style.display = 'none';
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
window.onclick = function (event) {
|
|
216
|
+
const modal = document.getElementById('errorModal');
|
|
217
|
+
if (event.target === modal) {
|
|
218
|
+
window.closeErrorModal();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
document.addEventListener('keydown', function (event) {
|
|
223
|
+
if (event.key === 'Escape') {
|
|
224
|
+
const modal = document.getElementById('errorModal');
|
|
225
|
+
if (modal && modal.style.display === 'block') {
|
|
226
|
+
window.closeErrorModal();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Attach click event to table rows for error popup
|
|
232
|
+
var table = document.getElementById('resultsTable');
|
|
233
|
+
if (table) {
|
|
234
|
+
table.querySelectorAll('tbody tr').forEach(function (row) {
|
|
235
|
+
row.addEventListener('click', function () {
|
|
236
|
+
showErrorDetails(row);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
</script>
|