Skip to content

Commit 8cd844f

Browse files
Merge pull request #138 from prashanth-sams/refactor
#115
2 parents 9c633be + 1112a42 commit 8cd844f

File tree

8 files changed

+211
-24
lines changed

8 files changed

+211
-24
lines changed

.gitignore

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ pytest_html_reporter/__pycache__/
99
pytest_html_reporter.egg-info/
1010
tests/__pycache__/
1111
dist/
12-
xtests/
13-
ytests/
14-
ztests/
15-
report/
12+
test_draft/
13+
report/
14+
__pycache__/

CHANGELOG.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Change Log
22
==========
33

4+
0.1.7 (08/09/2020)
5+
-------------------
6+
- Screenshots on failure
7+
- Updated loader
8+
49
0.1.6 (29/08/2020)
510
-------------------
611
- pytest-rerunfailures library support

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Features
3535
- Suite Highlights
3636
- Test suite details
3737
- Archives / History
38+
- Screenshots on failure
3839
- Test Rerun support
3940

4041
Installation
@@ -82,6 +83,10 @@ Alternate option is to add this snippet in the ``pytest.ini`` file::
8283
8384
.. image:: https://i.imgur.com/ge6OCM6.gif
8485

86+
|
87+
88+
.. image:: https://i.imgur.com/b1UFgHc.gif
89+
8590

8691
Is there a demo available for this gem?
8792
------------------------------------------------

pytest_html_reporter/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .plugin import screenshot as attach

pytest_html_reporter/plugin.py

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import glob
1010
from collections import Counter
1111
import codecs
12+
from PIL import Image
13+
from io import BytesIO
14+
import shutil
1215

1316
_total = _executed = 0
1417
_pass = _fail = 0
@@ -75,6 +78,9 @@
7578
_suite_error = 0
7679
_suite_fail = 0
7780
_pvalue = 0
81+
screen_base = ''
82+
screen_img = None
83+
_attach_screenshot_details = ''
7884

7985

8086
def pytest_addoption(parser):
@@ -90,6 +96,7 @@ def pytest_addoption(parser):
9096

9197
def pytest_configure(config):
9298
path = config.getoption("path")
99+
clean_screenshots(path)
93100

94101
config._html = HTMLReporter(path, config)
95102
config.pluginmanager.register(config._html)
@@ -155,8 +162,20 @@ def max_rerun():
155162
return None
156163

157164

158-
class HTMLReporter:
165+
def screenshot(data=None):
166+
global screen_base, screen_img
159167

168+
screen_base = HTMLReporter.base_path
169+
screen_img = Image.open(BytesIO(data))
170+
171+
172+
def clean_screenshots(path):
173+
screenshot_dir = os.path.abspath(os.path.expanduser(os.path.expandvars(path))) + '/pytest_screenshots'
174+
if os.path.isdir(screenshot_dir):
175+
shutil.rmtree(screenshot_dir)
176+
177+
178+
class HTMLReporter(object):
160179
def __init__(self, path, config):
161180
self.json_data = {'content': {'suites': {0: {'status': {}, 'tests': {0: {}}, }, }}}
162181
self.path = path
@@ -207,9 +226,11 @@ def report_path(self):
207226
path = '.' if '.html' in self.path.rsplit('/', 1)[0] else self.path.rsplit('/', 1)[0]
208227
if path == '': path = '.'
209228
logfile = os.path.expanduser(os.path.expandvars(path))
229+
HTMLReporter.base_path = os.path.abspath(logfile)
210230
return os.path.abspath(logfile), self.path.split('/')[-1]
211231
else:
212232
logfile = os.path.expanduser(os.path.expandvars(self.path))
233+
HTMLReporter.base_path = os.path.abspath(logfile)
213234
return os.path.abspath(logfile), 'pytest_html_report.html'
214235

215236
@pytest.hookimpl(hookwrapper=True)
@@ -328,7 +349,10 @@ def append_test_metrics_row(self):
328349
if (self.rerun is not None) and (max_rerun() is not None):
329350
if (_test_status == 'FAIL') or (_test_status == 'ERROR'): _pvalue += 1
330351

331-
if (_pvalue == max_rerun()+1) or (_test_status == 'PASS'):
352+
if (_pvalue == max_rerun() + 1) or (_test_status == 'PASS'):
353+
if ((_test_status == 'FAIL') or (_test_status == 'ERROR')) and (
354+
screen_base != ''): self.generate_screenshot_data()
355+
332356
test_row_text = test_row_text.replace("__sname__", str(_suite_name))
333357
test_row_text = test_row_text.replace("__name__", str(_test_name))
334358
test_row_text = test_row_text.replace("__stat__", str(_test_status))
@@ -337,7 +361,8 @@ def append_test_metrics_row(self):
337361

338362
_test_metrics_content += test_row_text
339363
_pvalue = 0
340-
elif (self.rerun is not None) and ((_test_status == 'xFAIL') or (_test_status == 'xPASS') or (_test_status == 'SKIP')):
364+
elif (self.rerun is not None) and (
365+
(_test_status == 'xFAIL') or (_test_status == 'xPASS') or (_test_status == 'SKIP')):
341366
test_row_text = test_row_text.replace("__sname__", str(_suite_name))
342367
test_row_text = test_row_text.replace("__name__", str(_test_name))
343368
test_row_text = test_row_text.replace("__stat__", str(_test_status))
@@ -347,6 +372,9 @@ def append_test_metrics_row(self):
347372
_test_metrics_content += test_row_text
348373

349374
elif (self.rerun is None) or (max_rerun() is None):
375+
if ((_test_status == 'FAIL') or (_test_status == 'ERROR')) and (
376+
screen_base != ''): self.generate_screenshot_data()
377+
350378
test_row_text = test_row_text.replace("__sname__", str(_suite_name))
351379
test_row_text = test_row_text.replace("__name__", str(_test_name))
352380
test_row_text = test_row_text.replace("__stat__", str(_test_status))
@@ -364,15 +392,36 @@ def append_test_metrics_row(self):
364392
len(_scenario) - 1, {})['test_name'] = str(_test_name)
365393

366394
if (self.rerun is not None) and (max_rerun() is not None):
367-
self.json_data['content']['suites'].setdefault(len(_test_suite_name), {}).setdefault('tests', {}).setdefault(
395+
self.json_data['content']['suites'].setdefault(len(_test_suite_name), {}).setdefault('tests',
396+
{}).setdefault(
368397
len(_scenario) - 1, {})['rerun'] = str(self.rerun)
369398
else:
370399
self.json_data['content']['suites'].setdefault(len(_test_suite_name), {}).setdefault('tests',
371400
{}).setdefault(
372401
len(_scenario) - 1, {})['rerun'] = '0'
373402

403+
def generate_screenshot_data(self):
404+
os.makedirs(screen_base + '/pytest_screenshots', exist_ok=True)
405+
406+
_screenshot_name = round(time.time())
407+
_screenshot_suite_name = _suite_name.split('/')[-1:][0].replace('.py', '')
408+
_screenshot_test_name = _test_name
409+
if len(_test_name) >= 19: _screenshot_test_name = _test_name[-17:]
410+
_screenshot_error = _current_error
411+
412+
screen_img.save(
413+
screen_base + '/pytest_screenshots/' + str(_screenshot_name) + '.png'
414+
)
415+
416+
# attach screenshots
417+
self.attach_screenshots(_screenshot_name, _screenshot_suite_name, _screenshot_test_name, _screenshot_error)
418+
_screenshot_name = ''
419+
_screenshot_suite_name = ''
420+
_screenshot_test_name = ''
421+
_screenshot_error = ''
422+
374423
def append_suite_metrics_row(self, name):
375-
global _spass_tests, _sfail_tests, _sskip_tests, _sxpass_tests, _sxfail_tests, _serror_tests, _srerun_tests,\
424+
global _spass_tests, _sfail_tests, _sskip_tests, _sxpass_tests, _sxfail_tests, _serror_tests, _srerun_tests, \
376425
_error, _suite_error, _suite_fail
377426

378427
self._test_names(_test_name, clear='yes')
@@ -602,6 +651,7 @@ def renew_template_text(self, logo_url):
602651
template_text = template_text.replace("__css_styles__",
603652
str(codecs.open('pytest_html_reporter/style.css',
604653
encoding='utf-8').read()))
654+
template_text = template_text.replace("__attach_screenshot_details__", str(_attach_screenshot_details))
605655
return template_text
606656

607657
def generate_json_data(self, base):
@@ -881,3 +931,37 @@ def update_trends(self, base):
881931
tskip.append(data['status_list']['skip'])
882932

883933
if i == 4: break
934+
935+
def attach_screenshots(self, screen_name, test_suite, test_case, test_error):
936+
global _attach_screenshot_details
937+
938+
_screenshot_details = """
939+
<div class="img-hover col-md-6 col-xl-3 p-3">
940+
<div>
941+
<a class="video" href="__screenshot_base__/pytest_screenshots/__screen_name__.png" data-toggle="lightbox" style="background-image: url('__screenshot_base__/pytest_screenshots/__screen_name__.png');" data-fancybox="images" data-caption="SUITE: __ts__ :: SCENARIO: __tc__">
942+
<span class="video-hover-desc video-hover-small"> <span style="font-size:23px;display: block;margin-bottom: 15px;"> __tc__</span>
943+
<span>__te__</span> </span>
944+
</a>
945+
<p class="text-desc"><strong>__ts__</strong><br />
946+
__te__</p>
947+
</div>
948+
</div>
949+
<div class="desc-video-none">
950+
<div class="desc-video" id="Video-desc-01">
951+
<h2>__tc__</h2>
952+
953+
<p><strong>__ts__</strong><br />
954+
__te__</p>
955+
</div>
956+
</div>
957+
"""
958+
959+
if len(test_case) == 17: test_case = '..' + test_case
960+
961+
_screenshot_details = _screenshot_details.replace("__screen_name__", str(screen_name))
962+
_screenshot_details = _screenshot_details.replace("__ts__", str(test_suite))
963+
_screenshot_details = _screenshot_details.replace("__tc__", str(test_case))
964+
_screenshot_details = _screenshot_details.replace("__te__", str(test_error))
965+
_screenshot_details = _screenshot_details.replace("__screenshot_base__", str(screen_base))
966+
967+
_attach_screenshot_details += _screenshot_details

pytest_html_reporter/style.css

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ th, td, tr {
7474
width: 100%;
7575
height: 100%;
7676
z-index: 9999;
77-
background: url('https://i.ibb.co/cXnKsNR/Cube-1s-200px.gif') 50% 50% no-repeat rgb(249, 249, 249);
77+
background: url('https://i.imgur.com/n3Tcoxz.gif') 50% 50% no-repeat rgb(249, 249, 249);
7878
}
7979

8080
.card-wrapper {
@@ -370,4 +370,67 @@ th, td, tr {
370370
margin: 0px 0 45px;
371371
color: dimgrey;
372372
float: right;
373+
}
374+
375+
.desc-video h2 {
376+
margin-top: 0px;
377+
}
378+
.desc-video-none {
379+
display: none;
380+
}
381+
.video {
382+
display: block;
383+
position: relative;
384+
padding-top: 60%;
385+
overflow: hidden;
386+
background-repeat: no-repeat;
387+
background-position: center center;
388+
background-size: cover;
389+
}
390+
.video:hover .video-hover-desc {
391+
margin-top: -170px;
392+
height: 170px;
393+
}
394+
.video:hover .video-hover-desc:before {
395+
background: linear-gradient(to top,rgba(35,72,133,0.82),rgba(35,72,133,0));
396+
transition: .3s;
397+
}
398+
.img-hover .text-desc {
399+
display: none;
400+
}
401+
.below-desc {
402+
display: block;
403+
height: 70px;
404+
width: 100%;
405+
padding: 10px;
406+
}
407+
.video-hover-desc {
408+
display: block;
409+
padding: 10px 20px;
410+
background-color: #234885;
411+
background-color: rgba(35,72,133,0.82);
412+
height: 90px;
413+
margin-top: -90px;
414+
transition: .3s;
415+
color: white;
416+
text-shadow: none;
417+
}
418+
.video-hover-desc:before {
419+
display: block;
420+
position: absolute;
421+
content: '';
422+
left: 0;
423+
right: 0;
424+
height: 116px;
425+
transition: .3s;
426+
margin-top: -126px;
427+
}
428+
.video-hover-desc h2 {
429+
color: white;
430+
margin-top: 0px;
431+
height: 50px;
432+
}
433+
.video-hover-desc.video-hover-small {
434+
height: 50px;
435+
margin-top: -50px;
373436
}

pytest_html_reporter/template.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,10 @@ def html_template():
1111
<link href="https://cdn.datatables.net/buttons/1.5.2/css/buttons.dataTables.min.css" rel="stylesheet" />
1212
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" />
1313
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
14-
<script src="https://code.jquery.com/jquery-3.3.1.js" type="text/javascript"></script>
15-
<!-- Bootstrap core Googleccharts -->
16-
<script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script>
17-
<script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script>
18-
<script type="text/javascript">
19-
google.charts.load('current', {
20-
packages: ['corechart']
21-
});
22-
</script>
14+
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
15+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
16+
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
17+
2318
<!-- Bootstrap core Datatable-->
2419
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script>
2520
<script src="https://cdn.datatables.net/buttons/1.5.2/js/dataTables.buttons.min.js" type="text/javascript"></script>
@@ -43,13 +38,20 @@ def html_template():
4338
<div class="sidenav">
4439
<a><img class="wrimagecard" src="__custom_logo__" style="max-width:98%;" /></a>
4540
<a class="tablink" href="#" id="defaultOpen" onclick="openPage('dashboard', this, 'white', '#565656', 'groove')">
46-
<i class="fa fa-home" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Dashboard</a>
41+
<i class="fa fa-home" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Dashboard
42+
</a>
4743
<a class="tablink" href="#" onclick="openPage('suiteMetrics', this, 'white', '#565656', 'groove'); executeDataTable('#sm',2)">
48-
<i class="fa fa-briefcase" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Suites</a>
44+
<i class="fa fa-briefcase" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Suites
45+
</a>
4946
<a class="tablink" href="#" onclick="openPage('testMetrics', this, 'white', '#565656', 'groove'); executeDataTable('#tm',3)">
50-
<i class="fa fa-server" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Test Metrics</a>
47+
<i class="fa fa-server" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Test Metrics
48+
</a>
5149
<a class="tablink" href="#" onclick="openPage('archives', this, 'white', '#565656', 'groove');">
52-
<i class="fa fa-history" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Archives</a>
50+
<i class="fa fa-history" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Archives
51+
</a>
52+
<a class="tablink" href="#" onclick="openPage('screenshots', this, 'white', '#565656', 'groove');">
53+
<i class="fa fa-camera" id="tablinkicon" style="color:currentcolor; margin:5% 5% 5% 10%"></i> Screenshots
54+
</a>
5355
</div>
5456
<div class="main col-md-9 ml-sm-auto col-lg-10 px-4" style="height: 100%;">
5557
<div class="tabcontent" id="dashboard">
@@ -177,6 +179,7 @@ def html_template():
177179
<script>
178180
window.onload = function() {
179181
alignTotalCount();
182+
failureScreenshots();
180183
executeDataTable('#sm', 2);
181184
executeDataTable('#tm', 3);
182185
};
@@ -232,6 +235,22 @@ def html_template():
232235
__archive_body_content__
233236
</div>
234237
</div>
238+
<div class="tabcontent" id="screenshots">
239+
<div id="content">
240+
<div class="fold-main">
241+
<div class="container-fluid">
242+
<div id="main-content">
243+
<div class="bg-highlight p-4 mt-3">
244+
<div class="row">
245+
__attach_screenshot_details__
246+
<div class="below-desc"></div>
247+
</div>
248+
</div>
249+
</div>
250+
</div>
251+
</div>
252+
</div>
253+
</div>
235254
<script>
236255
function createBarGraph(tableID, keyword_column, time_column, limit, ChartID, Label, type) {
237256
var status = [];
@@ -750,6 +769,17 @@ def html_template():
750769
}
751770
});
752771
</script>
772+
<script>
773+
function failureScreenshots() {
774+
$('.video').hover(function (e) {
775+
var hoverText = $(this).siblings('.text-desc').html();
776+
777+
$(e.target).closest('.bg-highlight').find('.below-desc').first().html(hoverText);
778+
}, function (e) {
779+
$(e.target).closest('.bg-highlight').find('.below-desc').first().html('');
780+
});
781+
}
782+
</script>
753783
754784
</body>
755785
"""

0 commit comments

Comments
 (0)