@@ -35,6 +35,7 @@ def test_anything(self):
35
35
import logging
36
36
import os
37
37
import re
38
+ import shutil
38
39
import sys
39
40
import time
40
41
import unittest
@@ -92,6 +93,7 @@ def __init__(self, *args, **kwargs):
92
93
self .__screenshot_count = 0
93
94
self .__will_be_skipped = False
94
95
self .__passed_then_skipped = False
96
+ self .__visual_baseline_copies = []
95
97
self .__last_url_of_deferred_assert = "data:,"
96
98
self .__last_page_load_url = "data:,"
97
99
self .__last_page_screenshot = None
@@ -9548,6 +9550,90 @@ def __assert_eq(self, *args, **kwargs):
9548
9550
if minified_exception :
9549
9551
raise Exception (minified_exception )
9550
9552
9553
+ def __process_visual_baseline_logs (self ):
9554
+ """ Save copies of baseline PNGs in "./latest_logs" during failures.
9555
+ Also create a side_by_side.html file for visual comparisons. """
9556
+ test_logpath = os .path .join (self .log_path , self .__get_test_id ())
9557
+ for baseline_copy_tuple in self .__visual_baseline_copies :
9558
+ baseline_path = baseline_copy_tuple [0 ]
9559
+ baseline_copy_path = baseline_copy_tuple [1 ]
9560
+ b_c_alt_path = baseline_copy_tuple [2 ]
9561
+ latest_png_path = baseline_copy_tuple [3 ]
9562
+ latest_copy_path = baseline_copy_tuple [4 ]
9563
+ l_c_alt_path = baseline_copy_tuple [5 ]
9564
+
9565
+ if len (self .__visual_baseline_copies ) == 1 :
9566
+ baseline_copy_path = b_c_alt_path
9567
+ latest_copy_path = l_c_alt_path
9568
+ if (
9569
+ os .path .exists (baseline_path )
9570
+ and not os .path .exists (baseline_copy_path )
9571
+ ):
9572
+ self .__create_log_path_as_needed (test_logpath )
9573
+ shutil .copy (baseline_path , baseline_copy_path )
9574
+ if (
9575
+ os .path .exists (latest_png_path )
9576
+ and not os .path .exists (latest_copy_path )
9577
+ ):
9578
+ self .__create_log_path_as_needed (test_logpath )
9579
+ shutil .copy (latest_png_path , latest_copy_path )
9580
+ if len (self .__visual_baseline_copies ) != 1 :
9581
+ return # Only possible when deferred visual asserts are used
9582
+ head = (
9583
+ '<head><meta charset="utf-8">'
9584
+ '<meta name="viewport" content="shrink-to-fit=no">'
9585
+ '<link rel="shortcut icon" href="%s">'
9586
+ "<title>Visual Comparison</title>"
9587
+ "</head>"
9588
+ % (constants .SideBySide .SIDE_BY_SIDE_PNG )
9589
+ )
9590
+ table_html = (
9591
+ '<table border="3px solid #E6E6E6;" width="100%;" padding: 12px;'
9592
+ ' font-size="16px;" text-align="left;" id="results-table"'
9593
+ ' style="background-color: #FAFAFA;">'
9594
+ '<thead id="results-table-head">'
9595
+ '<tr>'
9596
+ '<th style="background-color: rgba(0, 128, 0, 0.25);"'
9597
+ ' col="baseline">Baseline Screenshot</th>'
9598
+ '<th style="background-color: rgba(128, 0, 0, 0.25);"'
9599
+ ' col="failure">Visual Diff Failure Screenshot</th>'
9600
+ "</tr></thead>"
9601
+ )
9602
+ row = (
9603
+ '<tbody class="compare results-table-row">'
9604
+ '<tr style="background-color: #F4F4FE;">'
9605
+ '<td><img src="%s" width="100%%" /></td>'
9606
+ '<td><img src="%s" width="100%%" /></td>'
9607
+ "</tr></tbody>"
9608
+ "" % ("baseline.png" , "baseline_diff.png" )
9609
+ )
9610
+ header_text = "SeleniumBase Visual Comparison"
9611
+ header = '<h3 align="center">%s</h3>' % header_text
9612
+ table_html += row
9613
+ table_html += "</table>"
9614
+ footer = "<br /><b>Last updated:</b> "
9615
+ timestamp , the_date , the_time = log_helper .get_master_time ()
9616
+ last_updated = "%s at %s" % (the_date , the_time )
9617
+ footer = footer + "%s" % last_updated
9618
+ gen_by = (
9619
+ '<p><div>Generated by: <b><a href="https://seleniumbase.io/">'
9620
+ "SeleniumBase</a></b></div></p><p></p>"
9621
+ )
9622
+ footer = footer + gen_by
9623
+ the_html = (
9624
+ '<html lang="en">'
9625
+ + head
9626
+ + '<body style="background-color: #FCFCF4;">'
9627
+ + header
9628
+ + table_html
9629
+ + footer
9630
+ + "</body>"
9631
+ )
9632
+ file_path = os .path .join (test_logpath , constants .SideBySide .HTML_FILE )
9633
+ out_file = codecs .open (file_path , "w+" , encoding = "utf-8" )
9634
+ out_file .writelines (the_html )
9635
+ out_file .close ()
9636
+
9551
9637
def check_window (
9552
9638
self ,
9553
9639
name = "default" ,
@@ -9658,7 +9744,10 @@ def check_window(
9658
9744
baseline_dir = constants .VisualBaseline .STORAGE_FOLDER
9659
9745
visual_baseline_path = baseline_dir + "/" + test_id + "/" + name
9660
9746
page_url_file = visual_baseline_path + "/page_url.txt"
9661
- screenshot_file = visual_baseline_path + "/screenshot.png"
9747
+ baseline_png = "baseline.png"
9748
+ baseline_png_path = visual_baseline_path + "/%s" % baseline_png
9749
+ latest_png = "latest.png"
9750
+ latest_png_path = visual_baseline_path + "/%s" % latest_png
9662
9751
level_1_file = visual_baseline_path + "/tags_level_1.txt"
9663
9752
level_2_file = visual_baseline_path + "/tags_level_2.txt"
9664
9753
level_3_file = visual_baseline_path + "/tags_level_3.txt"
@@ -9674,7 +9763,7 @@ def check_window(
9674
9763
pass # Only reachable during multi-threaded test runs
9675
9764
if not os .path .exists (page_url_file ):
9676
9765
set_baseline = True
9677
- if not os .path .exists (screenshot_file ):
9766
+ if not os .path .exists (baseline_png_path ):
9678
9767
set_baseline = True
9679
9768
if not os .path .exists (level_1_file ):
9680
9769
set_baseline = True
@@ -9694,7 +9783,9 @@ def check_window(
9694
9783
level_3 = json .loads (json .dumps (level_3 )) # Tuples become lists
9695
9784
9696
9785
if set_baseline :
9697
- self .save_screenshot ("screenshot.png" , visual_baseline_path )
9786
+ self .save_screenshot (
9787
+ baseline_png , visual_baseline_path , selector = "body"
9788
+ )
9698
9789
out_file = codecs .open (page_url_file , "w+" , encoding = "utf-8" )
9699
9790
out_file .writelines (page_url )
9700
9791
out_file .close ()
@@ -9708,7 +9799,26 @@ def check_window(
9708
9799
out_file .writelines (json .dumps (level_3 ))
9709
9800
out_file .close ()
9710
9801
9802
+ test_logpath = os .path .join (self .log_path , self .__get_test_id ())
9803
+ baseline_path = os .path .join (visual_baseline_path , baseline_png )
9804
+ baseline_copy_name = "baseline_%s.png" % name
9805
+ baseline_copy_path = os .path .join (test_logpath , baseline_copy_name )
9806
+ b_c_alt_name = "baseline.png"
9807
+ b_c_alt_path = os .path .join (test_logpath , b_c_alt_name )
9808
+ latest_copy_name = "baseline_diff_%s.png" % name
9809
+ latest_copy_path = os .path .join (test_logpath , latest_copy_name )
9810
+ l_c_alt_name = "baseline_diff.png"
9811
+ l_c_alt_path = os .path .join (test_logpath , l_c_alt_name )
9812
+ baseline_copy_tuple = (
9813
+ baseline_path , baseline_copy_path , b_c_alt_path ,
9814
+ latest_png_path , latest_copy_path , l_c_alt_path ,
9815
+ )
9816
+ self .__visual_baseline_copies .append (baseline_copy_tuple )
9817
+
9711
9818
if not set_baseline :
9819
+ self .save_screenshot (
9820
+ latest_png , visual_baseline_path , selector = "body"
9821
+ )
9712
9822
f = open (page_url_file , "r" )
9713
9823
page_url_data = f .read ().strip ()
9714
9824
f .close ()
@@ -9805,6 +9915,8 @@ def check_window(
9805
9915
except Exception as e :
9806
9916
print (e ) # Level-0 Dry Run (Only print the differences)
9807
9917
unittest .TestCase .maxDiff = None # Reset unittest.TestCase.maxDiff
9918
+ # Since the check passed, do not save an extra copy of the baseline
9919
+ del self .__visual_baseline_copies [- 1 ] # .pop() returns the element
9808
9920
9809
9921
############
9810
9922
@@ -11579,14 +11691,18 @@ def tearDown(self):
11579
11691
settings .LARGE_TIMEOUT = sb_config ._LARGE_TIMEOUT
11580
11692
sb_config ._is_timeout_changed = False
11581
11693
self .__overrided_default_timeouts = False
11694
+ deferred_exception = None
11582
11695
if self .__deferred_assert_failures :
11583
11696
print (
11584
11697
"\n When using self.deferred_assert_*() methods in your tests, "
11585
11698
"remember to call self.process_deferred_asserts() afterwards. "
11586
11699
"Now calling in tearDown()...\n Failures Detected:"
11587
11700
)
11588
11701
if not has_exception :
11589
- self .process_deferred_asserts ()
11702
+ try :
11703
+ self .process_deferred_asserts ()
11704
+ except Exception as e :
11705
+ deferred_exception = e
11590
11706
else :
11591
11707
self .process_deferred_asserts (print_only = True )
11592
11708
if self .is_pytest :
@@ -11784,5 +11900,11 @@ def tearDown(self):
11784
11900
self ._last_page_url = self .get_current_url ()
11785
11901
except Exception :
11786
11902
self ._last_page_url = "(Error: Unknown URL)"
11787
- # Finally close all open browser windows
11903
+ # (Nosetests) Finally close all open browser windows
11788
11904
self .__quit_all_drivers ()
11905
+ # Resume tearDown() for both Pytest and Nosetests
11906
+ if has_exception and self .__visual_baseline_copies :
11907
+ self .__process_visual_baseline_logs ()
11908
+ if deferred_exception :
11909
+ # User forgot to call "self.process_deferred_asserts()" in test
11910
+ raise deferred_exception
0 commit comments