robotframework-failuresummary 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.
- RobotFailureSummary/__init__.py +10 -0
- RobotFailureSummary/js/expander.js +34 -0
- RobotFailureSummary/listener.py +250 -0
- RobotFailureSummary/templates/summary.html +30 -0
- robotframework_failuresummary-1.0.0.data/data/RobotFailureSummary/js/expander.js +34 -0
- robotframework_failuresummary-1.0.0.data/data/RobotFailureSummary/templates/summary.html +30 -0
- robotframework_failuresummary-1.0.0.dist-info/METADATA +15 -0
- robotframework_failuresummary-1.0.0.dist-info/RECORD +9 -0
- robotframework_failuresummary-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function openLogTab(url) {
|
|
2
|
+
var tab = window.open('', 'rf_log_viewer');
|
|
3
|
+
if (tab) {
|
|
4
|
+
var parts = url.split('#');
|
|
5
|
+
var base = parts[0];
|
|
6
|
+
var hash = parts[1] ? parts[1] : '';
|
|
7
|
+
var separator = base.indexOf('?') !== -1 ? '&' : '?';
|
|
8
|
+
|
|
9
|
+
// Cache-buster forces log.html to evaluate fresh parameters even on repetitive clicks
|
|
10
|
+
tab.location.href = base + separator + 'cb=' + new Date().getTime() + (hash ? '#' + hash : '');
|
|
11
|
+
|
|
12
|
+
if (hash) {
|
|
13
|
+
var checks = 0;
|
|
14
|
+
var interval = setInterval(function() {
|
|
15
|
+
checks++;
|
|
16
|
+
if (tab.util && tab.util.expandElementWithId) {
|
|
17
|
+
clearInterval(interval);
|
|
18
|
+
try {
|
|
19
|
+
// Direct call to RF internal framework to force full ancestral layout tree expansion
|
|
20
|
+
tab.util.expandElementWithId(hash);
|
|
21
|
+
var element = tab.document.getElementById(hash);
|
|
22
|
+
if (element) {
|
|
23
|
+
element.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
24
|
+
}
|
|
25
|
+
} catch(e) {
|
|
26
|
+
console.log("Deep expand injection waiting...", e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (checks > 50) clearInterval(interval);
|
|
30
|
+
}, 100);
|
|
31
|
+
}
|
|
32
|
+
tab.focus();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from robot.api import ExecutionResult
|
|
5
|
+
import html as html_escaper
|
|
6
|
+
import sys
|
|
7
|
+
from urllib3.util.retry import Retry
|
|
8
|
+
import glob
|
|
9
|
+
|
|
10
|
+
Retry.DEFAULT = Retry(0)
|
|
11
|
+
ROBOT_LISTENER_API_VERSION = 3
|
|
12
|
+
|
|
13
|
+
_output_dir = None
|
|
14
|
+
_is_pipeline = os.environ.get('RF_SUMMARY_PIPELINE_MODE', 'false').lower() == 'true'
|
|
15
|
+
_close_called = False
|
|
16
|
+
|
|
17
|
+
_SCREENSHOT_PATTERNS = [
|
|
18
|
+
'selenium-screenshot-*.png', 'screenshot-*.png', '*-screenshot-*.png', 'selenium-*.png',
|
|
19
|
+
'browser-screenshot-*.png', 'playwright-screenshot-*.png', 'playwright-*.png',
|
|
20
|
+
'robotframework-browser-screenshot-*.png', 'browser-*.png', '*.webm', 'trace-*.zip',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
_SELENIUM_LOGGERS = ['SeleniumLibrary', 'selenium.webdriver.remote.remote_connection', 'urllib3.connectionpool']
|
|
24
|
+
_BROWSER_LOGGERS = ['Browser', 'Browser.utils', 'Browser.playwright', 'grpc', 'asyncio']
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_output_dir_from_argv():
|
|
28
|
+
global _output_dir
|
|
29
|
+
if _output_dir:
|
|
30
|
+
return _output_dir
|
|
31
|
+
args = sys.argv
|
|
32
|
+
for i, arg in enumerate(args):
|
|
33
|
+
if arg in ('-d', '--outputdir') and i + 1 < len(args):
|
|
34
|
+
resolved = os.path.abspath(args[i + 1])
|
|
35
|
+
_output_dir = resolved
|
|
36
|
+
return resolved
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_deepest_failures(item):
|
|
41
|
+
failures = []
|
|
42
|
+
if hasattr(item, 'body') and item.body:
|
|
43
|
+
failing_children = [k for k in item.body if hasattr(k, 'status') and k.status == 'FAIL']
|
|
44
|
+
if not failing_children:
|
|
45
|
+
if hasattr(item, 'status') and item.status == 'FAIL':
|
|
46
|
+
failures.append(item)
|
|
47
|
+
else:
|
|
48
|
+
for child in failing_children:
|
|
49
|
+
failures.extend(find_deepest_failures(child))
|
|
50
|
+
elif hasattr(item, 'status') and item.status == 'FAIL':
|
|
51
|
+
failures.append(item)
|
|
52
|
+
return failures
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def extract_fail_details(failure):
|
|
56
|
+
fail_msg = None
|
|
57
|
+
exception_lines = []
|
|
58
|
+
raw_messages = []
|
|
59
|
+
if hasattr(failure, 'messages'):
|
|
60
|
+
raw_messages.extend(list(failure.messages))
|
|
61
|
+
if hasattr(failure, 'body'):
|
|
62
|
+
for item in failure.body:
|
|
63
|
+
if hasattr(item, 'message') and hasattr(item, 'level'):
|
|
64
|
+
raw_messages.append(item)
|
|
65
|
+
|
|
66
|
+
for msg in raw_messages:
|
|
67
|
+
level = getattr(msg, 'level', '') or ''
|
|
68
|
+
text = getattr(msg, 'message', '') or ''
|
|
69
|
+
if level == 'FAIL' and not fail_msg:
|
|
70
|
+
fail_msg = text.strip()
|
|
71
|
+
for raw_line in text.splitlines():
|
|
72
|
+
line = raw_line.strip()
|
|
73
|
+
if not line or line == 'None':
|
|
74
|
+
continue
|
|
75
|
+
if any(x in line for x in ('Error:', 'Exception:', 'Fault:', 'Warning:')):
|
|
76
|
+
if line not in exception_lines:
|
|
77
|
+
exception_lines.append(line)
|
|
78
|
+
return fail_msg, exception_lines
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _generate_summary():
|
|
82
|
+
global _close_called, _output_dir
|
|
83
|
+
if _close_called:
|
|
84
|
+
return
|
|
85
|
+
_close_called = True
|
|
86
|
+
|
|
87
|
+
print("##[section]Generating failure summary...")
|
|
88
|
+
_resolve_output_dir_from_argv()
|
|
89
|
+
output_dir = _output_dir if _output_dir else '.'
|
|
90
|
+
output_xml = os.path.join(output_dir, 'output.xml')
|
|
91
|
+
summary_path = os.path.join(output_dir, 'failure_summary.html')
|
|
92
|
+
|
|
93
|
+
if not os.path.exists(output_xml):
|
|
94
|
+
print("##[warning]Could not find output.xml at: " + output_xml)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
result = ExecutionResult(output_xml)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print("##[error]Could not read output.xml. Error: " + str(e))
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
final_report_data = []
|
|
104
|
+
|
|
105
|
+
def collect_failures_from_suite(suite):
|
|
106
|
+
for test in suite.tests:
|
|
107
|
+
if test.status == 'FAIL':
|
|
108
|
+
deepest_failure_objects = []
|
|
109
|
+
if test.setup and test.setup.status == 'FAIL':
|
|
110
|
+
deepest_failure_objects.extend(find_deepest_failures(test.setup))
|
|
111
|
+
for item in test.body:
|
|
112
|
+
if hasattr(item, 'status') and item.status == 'FAIL':
|
|
113
|
+
deepest_failure_objects.extend(find_deepest_failures(item))
|
|
114
|
+
if test.teardown and test.teardown.status == 'FAIL':
|
|
115
|
+
deepest_failure_objects.extend(find_deepest_failures(test.teardown))
|
|
116
|
+
|
|
117
|
+
unique_failures = {failure.id: failure for failure in deepest_failure_objects}.values()
|
|
118
|
+
|
|
119
|
+
for failure in unique_failures:
|
|
120
|
+
path = []
|
|
121
|
+
current = failure
|
|
122
|
+
while current and hasattr(current, 'parent') and current.id != test.id:
|
|
123
|
+
name_or_type = getattr(current, 'name', None) or getattr(current, 'type', None)
|
|
124
|
+
if name_or_type:
|
|
125
|
+
path.insert(0, str(name_or_type))
|
|
126
|
+
current = current.parent
|
|
127
|
+
|
|
128
|
+
fail_msg, exception_lines = extract_fail_details(failure)
|
|
129
|
+
final_report_data.append({
|
|
130
|
+
'test_name': test.name,
|
|
131
|
+
'test_id': test.id,
|
|
132
|
+
'failure_path': ' > '.join(path),
|
|
133
|
+
'failure_id': failure.id,
|
|
134
|
+
'fail_msg': fail_msg,
|
|
135
|
+
'exception_lines': exception_lines
|
|
136
|
+
})
|
|
137
|
+
for child_suite in suite.suites:
|
|
138
|
+
collect_failures_from_suite(child_suite)
|
|
139
|
+
|
|
140
|
+
collect_failures_from_suite(result.suite)
|
|
141
|
+
|
|
142
|
+
if not final_report_data:
|
|
143
|
+
print("##[section]No failures found - all tests passed!")
|
|
144
|
+
if os.path.exists(summary_path):
|
|
145
|
+
try: os.remove(summary_path)
|
|
146
|
+
except Exception: pass
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Modern package asset extraction via importlib
|
|
150
|
+
try:
|
|
151
|
+
from importlib.resources import files
|
|
152
|
+
html_template = files("RobotFailureSummary").joinpath("templates", "summary.html").read_text(encoding="utf-8")
|
|
153
|
+
javascript_code = files("RobotFailureSummary").joinpath("js", "expander.js").read_text(encoding="utf-8")
|
|
154
|
+
except Exception:
|
|
155
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
156
|
+
with open(os.path.join(current_dir, 'templates', 'summary.html'), 'r', encoding='utf-8') as tmpl:
|
|
157
|
+
html_template = tmpl.read()
|
|
158
|
+
with open(os.path.join(current_dir, 'js', 'expander.js'), 'r', encoding='utf-8') as js_file:
|
|
159
|
+
javascript_code = js_file.read()
|
|
160
|
+
|
|
161
|
+
cards_html = ""
|
|
162
|
+
for f in final_report_data:
|
|
163
|
+
path_display = " → ".join([f'<span>{html_escaper.escape(p)}</span>' for p in f['failure_path'].split(' > ') if p])
|
|
164
|
+
link = f"log.html?expand={f['failure_id']}#{f['failure_id']}"
|
|
165
|
+
fail_block = f'<div class="detail-block"><div class="detail-label">⛔ Fail Message:</div><div class="fail-text">{html_escaper.escape(f["fail_msg"])}</div></div>' if f.get('fail_msg') else ""
|
|
166
|
+
|
|
167
|
+
exception_block = ""
|
|
168
|
+
if f.get('exception_lines'):
|
|
169
|
+
exception_rows = "".join(f'<div class="exception-text">{html_escaper.escape(line)}</div>' for line in f['exception_lines'])
|
|
170
|
+
exception_block = f'<div class="detail-block"><div class="detail-label">🔴 Exception / Error:</div>{exception_rows}</div>'
|
|
171
|
+
|
|
172
|
+
cards_html += f"""
|
|
173
|
+
<div class="failure-card">
|
|
174
|
+
<span class="test-name">{html_escaper.escape(f['test_name'])}</span>
|
|
175
|
+
<div class="path">{path_display}</div>
|
|
176
|
+
{fail_block}
|
|
177
|
+
{exception_block}
|
|
178
|
+
<a class="jump-btn" onclick="openLogTab('{link}')">Jump to Failing Keyword ↗</a>
|
|
179
|
+
</div>
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
final_html = html_template.replace("{{JAVASCRIPT}}", javascript_code).replace("{{CARDS}}", cards_html)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
with open(summary_path, "w", encoding="utf-8") as f:
|
|
186
|
+
f.write(final_html)
|
|
187
|
+
print("Failure Summary generated successfully: " + summary_path)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print("##[error]Failed to write failure_summary.html: " + str(e))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
atexit.register(_generate_summary)
|
|
193
|
+
|
|
194
|
+
def _mute_noisy_loggers():
|
|
195
|
+
for name in _SELENIUM_LOGGERS + _BROWSER_LOGGERS:
|
|
196
|
+
logging.getLogger(name).setLevel(logging.INFO)
|
|
197
|
+
|
|
198
|
+
def start_suite(data, result):
|
|
199
|
+
global _output_dir
|
|
200
|
+
from robot.running.context import EXECUTION_CONTEXTS
|
|
201
|
+
if EXECUTION_CONTEXTS.current:
|
|
202
|
+
EXECUTION_CONTEXTS.current.output.set_log_level('TRACE')
|
|
203
|
+
if hasattr(result, 'suite') and hasattr(result.suite, 'source'):
|
|
204
|
+
_output_dir = os.path.dirname(result.suite.source)
|
|
205
|
+
if not _output_dir:
|
|
206
|
+
_output_dir = os.environ.get('ROBOT_OUTPUT_DIR', None)
|
|
207
|
+
if not _is_pipeline:
|
|
208
|
+
cleanup_old_files()
|
|
209
|
+
_mute_noisy_loggers()
|
|
210
|
+
|
|
211
|
+
def cleanup_old_files():
|
|
212
|
+
global _output_dir
|
|
213
|
+
output_dir = _output_dir if _output_dir else '.'
|
|
214
|
+
if not os.path.exists(output_dir): return
|
|
215
|
+
try:
|
|
216
|
+
for pattern in _SCREENSHOT_PATTERNS:
|
|
217
|
+
for file_path in glob.glob(os.path.join(output_dir, pattern)):
|
|
218
|
+
try: os.remove(file_path)
|
|
219
|
+
except Exception: pass
|
|
220
|
+
for sub in ('browser', 'screenshots', 'playwright-report'):
|
|
221
|
+
sub_path = os.path.join(output_dir, sub)
|
|
222
|
+
if os.path.isdir(sub_path):
|
|
223
|
+
for pattern in _SCREENSHOT_PATTERNS:
|
|
224
|
+
for file_path in glob.glob(os.path.join(sub_path, pattern)):
|
|
225
|
+
try: os.remove(file_path)
|
|
226
|
+
except Exception: pass
|
|
227
|
+
summary_path = os.path.join(output_dir, 'failure_summary.html')
|
|
228
|
+
if os.path.exists(summary_path): os.remove(summary_path)
|
|
229
|
+
except Exception: pass
|
|
230
|
+
|
|
231
|
+
def start_test(data, result):
|
|
232
|
+
from robot.running.context import EXECUTION_CONTEXTS
|
|
233
|
+
if EXECUTION_CONTEXTS.current:
|
|
234
|
+
EXECUTION_CONTEXTS.current.output.set_log_level('TRACE')
|
|
235
|
+
|
|
236
|
+
def output_file(path):
|
|
237
|
+
global _output_dir
|
|
238
|
+
_output_dir = os.path.dirname(path)
|
|
239
|
+
|
|
240
|
+
def log_file(path):
|
|
241
|
+
try:
|
|
242
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
243
|
+
content = f.read()
|
|
244
|
+
content = content.replace('"minLevel":"INFO"', '"minLevel":"TRACE"').replace("'minLevel':'INFO'", "'minLevel':'TRACE'")
|
|
245
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
246
|
+
f.write(content)
|
|
247
|
+
except Exception: pass
|
|
248
|
+
|
|
249
|
+
def close():
|
|
250
|
+
_generate_summary()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Failure Summary</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: Arial, sans-serif; padding: 20px; background: #1a1a2e; color: #eee; }
|
|
8
|
+
h2 { color: #e94560; }
|
|
9
|
+
.failure-card { background: #16213e; border-left: 4px solid #e94560; padding: 12px 16px; margin: 10px 0; border-radius: 4px; }
|
|
10
|
+
.test-name { font-weight: bold; color: #0f3460; background: #e94560; padding: 2px 8px; border-radius: 3px; font-size: 12px; }
|
|
11
|
+
.path { margin: 8px 0; color: #aaa; font-size: 13px; word-break: break-all; }
|
|
12
|
+
.path span { color: #fff; }
|
|
13
|
+
a.jump-btn { display: inline-block; margin-top: 6px; background: #e94560; color: white; padding: 4px 12px; border-radius: 4px; text-decoration: none; font-size: 13px; cursor: pointer; }
|
|
14
|
+
a.jump-btn:hover { background: #c73652; }
|
|
15
|
+
.detail-block { margin-top: 10px; padding: 8px 12px; background: #0f1b33; border-radius: 4px; font-size: 13px; border: 1px solid #2a2a4a; }
|
|
16
|
+
.detail-label { color: #e94560; font-weight: bold; font-size: 11px; letter-spacing: 0.8px; text-transform: uppercase; margin-bottom: 4px; }
|
|
17
|
+
.fail-text { color: #ffcdd2; font-family: monospace; white-space: pre-wrap; word-break: break-all; }
|
|
18
|
+
.exception-text { color: #ff6b6b; font-family: monospace; white-space: pre-wrap; word-break: break-all; margin: 2px 0; }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h2>⚠ Failure Summary — Click to Jump to Root Cause</h2>
|
|
23
|
+
|
|
24
|
+
{{CARDS}}
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
{{JAVASCRIPT}}
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function openLogTab(url) {
|
|
2
|
+
var tab = window.open('', 'rf_log_viewer');
|
|
3
|
+
if (tab) {
|
|
4
|
+
var parts = url.split('#');
|
|
5
|
+
var base = parts[0];
|
|
6
|
+
var hash = parts[1] ? parts[1] : '';
|
|
7
|
+
var separator = base.indexOf('?') !== -1 ? '&' : '?';
|
|
8
|
+
|
|
9
|
+
// Cache-buster forces log.html to evaluate fresh parameters even on repetitive clicks
|
|
10
|
+
tab.location.href = base + separator + 'cb=' + new Date().getTime() + (hash ? '#' + hash : '');
|
|
11
|
+
|
|
12
|
+
if (hash) {
|
|
13
|
+
var checks = 0;
|
|
14
|
+
var interval = setInterval(function() {
|
|
15
|
+
checks++;
|
|
16
|
+
if (tab.util && tab.util.expandElementWithId) {
|
|
17
|
+
clearInterval(interval);
|
|
18
|
+
try {
|
|
19
|
+
// Direct call to RF internal framework to force full ancestral layout tree expansion
|
|
20
|
+
tab.util.expandElementWithId(hash);
|
|
21
|
+
var element = tab.document.getElementById(hash);
|
|
22
|
+
if (element) {
|
|
23
|
+
element.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
24
|
+
}
|
|
25
|
+
} catch(e) {
|
|
26
|
+
console.log("Deep expand injection waiting...", e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (checks > 50) clearInterval(interval);
|
|
30
|
+
}, 100);
|
|
31
|
+
}
|
|
32
|
+
tab.focus();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Failure Summary</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: Arial, sans-serif; padding: 20px; background: #1a1a2e; color: #eee; }
|
|
8
|
+
h2 { color: #e94560; }
|
|
9
|
+
.failure-card { background: #16213e; border-left: 4px solid #e94560; padding: 12px 16px; margin: 10px 0; border-radius: 4px; }
|
|
10
|
+
.test-name { font-weight: bold; color: #0f3460; background: #e94560; padding: 2px 8px; border-radius: 3px; font-size: 12px; }
|
|
11
|
+
.path { margin: 8px 0; color: #aaa; font-size: 13px; word-break: break-all; }
|
|
12
|
+
.path span { color: #fff; }
|
|
13
|
+
a.jump-btn { display: inline-block; margin-top: 6px; background: #e94560; color: white; padding: 4px 12px; border-radius: 4px; text-decoration: none; font-size: 13px; cursor: pointer; }
|
|
14
|
+
a.jump-btn:hover { background: #c73652; }
|
|
15
|
+
.detail-block { margin-top: 10px; padding: 8px 12px; background: #0f1b33; border-radius: 4px; font-size: 13px; border: 1px solid #2a2a4a; }
|
|
16
|
+
.detail-label { color: #e94560; font-weight: bold; font-size: 11px; letter-spacing: 0.8px; text-transform: uppercase; margin-bottom: 4px; }
|
|
17
|
+
.fail-text { color: #ffcdd2; font-family: monospace; white-space: pre-wrap; word-break: break-all; }
|
|
18
|
+
.exception-text { color: #ff6b6b; font-family: monospace; white-space: pre-wrap; word-break: break-all; margin: 2px 0; }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h2>⚠ Failure Summary — Click to Jump to Root Cause</h2>
|
|
23
|
+
|
|
24
|
+
{{CARDS}}
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
{{JAVASCRIPT}}
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-failuresummary
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A standard Robot Framework listener that mutes background engine noise and generates an interactive, deep-linking failure summary report.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Srinivasan2802/robotframework-failuresummary
|
|
6
|
+
Author-email: Srinivasan A <sriniagt.cse@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Framework :: Robot Framework
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: robotframework>=5.0
|
|
15
|
+
Requires-Dist: urllib3
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
RobotFailureSummary/__init__.py,sha256=ojyMGL_hoyXzyqOdJ9eoH8Wy2tTftzC_keX3WuGpGzc,245
|
|
2
|
+
RobotFailureSummary/listener.py,sha256=rJqaK5P0SJ3K8Xqh8NL06nQragnrVM2WvbBqDsAXjBc,10263
|
|
3
|
+
RobotFailureSummary/js/expander.js,sha256=HzjMMIfHS8dSrby2VhDYW5eXeJmhmXwFwFqx_uNIMM8,1440
|
|
4
|
+
RobotFailureSummary/templates/summary.html,sha256=0xPlByOF9Ps_SPekVg--_PjeN6lzMPFWzvNO8f-ENE8,1619
|
|
5
|
+
robotframework_failuresummary-1.0.0.data/data/RobotFailureSummary/js/expander.js,sha256=HzjMMIfHS8dSrby2VhDYW5eXeJmhmXwFwFqx_uNIMM8,1440
|
|
6
|
+
robotframework_failuresummary-1.0.0.data/data/RobotFailureSummary/templates/summary.html,sha256=0xPlByOF9Ps_SPekVg--_PjeN6lzMPFWzvNO8f-ENE8,1619
|
|
7
|
+
robotframework_failuresummary-1.0.0.dist-info/METADATA,sha256=zjUVEjFGQKnrZgs1Qytbtwzqj1h2y5HGOOOx7ncyxws,693
|
|
8
|
+
robotframework_failuresummary-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
robotframework_failuresummary-1.0.0.dist-info/RECORD,,
|