@@ -3261,6 +3261,9 @@ def set_time_limit(self, time_limit):
3261
3261
3262
3262
def skip (self , reason = "" ):
3263
3263
""" Mark the test as Skipped. """
3264
+ if self .dashboard :
3265
+ test_id = self .__get_test_id_2 ()
3266
+ sb_config ._results [test_id ] = "Skipped"
3264
3267
self .skipTest (reason )
3265
3268
3266
3269
############
@@ -4036,11 +4039,18 @@ def __create_highchart(
4036
4039
},
4037
4040
plotOptions: {
4038
4041
pie: {
4042
+ size: "95%",
4039
4043
allowPointSelect: true,
4044
+ animation: false,
4040
4045
cursor: 'pointer',
4041
4046
dataLabels: {
4042
- enabled: false,
4043
- format: '{point.name}: {point.y:.1f}%'
4047
+ // enabled: false,
4048
+ // format: '{point.name}: {point.y:.0f}',
4049
+ formatter: function() {
4050
+ if (this.y > 0) {
4051
+ return this.point.name + ': ' + this.point.y
4052
+ }
4053
+ }
4044
4054
},
4045
4055
states: {
4046
4056
hover: {
@@ -4069,7 +4079,10 @@ def __create_highchart(
4069
4079
plotOptions: {
4070
4080
series: {
4071
4081
showInLegend: true,
4072
- animation: true,
4082
+ animation: false,
4083
+ dataLabels: {
4084
+ enabled: true
4085
+ },
4073
4086
shadow: false,
4074
4087
lineWidth: 3,
4075
4088
fillOpacity: 0.5,
@@ -4158,13 +4171,15 @@ def add_data_point(self, label, value, color=None, chart_name=None):
4158
4171
if self ._chart_first_series [chart_name ]:
4159
4172
self ._chart_label [chart_name ].append (label )
4160
4173
4161
- def save_chart (self , chart_name = None , filename = None ):
4174
+ def save_chart (self , chart_name = None , filename = None , folder = None ):
4162
4175
""" Saves a SeleniumBase-generated chart to a file for later use.
4163
4176
@Params
4164
4177
chart_name - If creating multiple charts at the same time,
4165
4178
use this to select the one you wish to use.
4166
4179
filename - The name of the HTML file that you wish to
4167
4180
save the chart to. (filename must end in ".html")
4181
+ folder - The name of the folder where you wish to
4182
+ save the HTML file. (Default: "./saved_charts/")
4168
4183
"""
4169
4184
if not chart_name :
4170
4185
chart_name = "default"
@@ -4199,7 +4214,10 @@ def save_chart(self, chart_name=None, filename=None):
4199
4214
axis += "'%s'," % label
4200
4215
axis += "], crosshair: false},"
4201
4216
the_html = the_html .replace ("xAxis: { }," , axis )
4202
- saved_charts_folder = constants .Charts .SAVED_FOLDER
4217
+ if not folder :
4218
+ saved_charts_folder = constants .Charts .SAVED_FOLDER
4219
+ else :
4220
+ saved_charts_folder = folder
4203
4221
if saved_charts_folder .endswith ("/" ):
4204
4222
saved_charts_folder = saved_charts_folder [:- 1 ]
4205
4223
if not os .path .exists (saved_charts_folder ):
@@ -6268,6 +6286,8 @@ def setUp(self, masterqa_mode=False):
6268
6286
self .guest_mode = sb_config .guest_mode
6269
6287
self .devtools = sb_config .devtools
6270
6288
self .remote_debug = sb_config .remote_debug
6289
+ self .dashboard = sb_config .dashboard
6290
+ self ._dash_initialized = sb_config ._dashboard_initialized
6271
6291
self .swiftshader = sb_config .swiftshader
6272
6292
self .user_data_dir = sb_config .user_data_dir
6273
6293
self .extension_zip = sb_config .extension_zip
@@ -6385,6 +6405,16 @@ def setUp(self, masterqa_mode=False):
6385
6405
"AppleWebKit/537.36 (KHTML, like Gecko) "
6386
6406
"Chrome/76.0.3809.132 Mobile Safari/537.36" )
6387
6407
6408
+ # Dashboard pre-processing:
6409
+ if self .dashboard :
6410
+ sb_config ._sbase_detected = True
6411
+ sb_config ._only_unittest = False
6412
+ if not self ._dash_initialized :
6413
+ sb_config ._dashboard_initialized = True
6414
+ sb_config ._sbase_detected = True
6415
+ self ._dash_initialized = True
6416
+ self .__process_dashboard (False , init = True )
6417
+
6388
6418
has_url = False
6389
6419
if self ._reuse_session :
6390
6420
if not hasattr (sb_config , 'shared_driver' ):
@@ -6598,13 +6628,219 @@ def __get_test_id(self):
6598
6628
test_id = self ._sb_test_identifier
6599
6629
return test_id
6600
6630
6631
+ def __get_test_id_2 (self ):
6632
+ """ The id for SeleniumBase Dashboard entries. """
6633
+ test_id = "%s.%s.%s" % (self .__class__ .__module__ .split ('.' )[- 1 ],
6634
+ self .__class__ .__name__ ,
6635
+ self ._testMethodName )
6636
+ if self ._sb_test_identifier and len (str (self ._sb_test_identifier )) > 6 :
6637
+ test_id = self ._sb_test_identifier
6638
+ if test_id .count ('.' ) > 1 :
6639
+ test_id = '.' .join (test_id .split ('.' )[1 :])
6640
+ return test_id
6641
+
6642
+ def __get_display_id (self ):
6643
+ test_id = "%s.py::%s::%s" % (
6644
+ self .__class__ .__module__ .replace ('.' , '/' ),
6645
+ self .__class__ .__name__ ,
6646
+ self ._testMethodName )
6647
+ if self ._sb_test_identifier and len (str (self ._sb_test_identifier )) > 6 :
6648
+ test_id = self ._sb_test_identifier
6649
+ if hasattr (self , "_using_sb_fixture_class" ):
6650
+ if test_id .count ('.' ) >= 2 :
6651
+ parts = test_id .split ('.' )
6652
+ full = parts [- 3 ] + '.py::' + parts [- 2 ] + '::' + parts [- 1 ]
6653
+ test_id = full
6654
+ elif hasattr (self , "_using_sb_fixture_no_class" ):
6655
+ if test_id .count ('.' ) >= 1 :
6656
+ parts = test_id .split ('.' )
6657
+ full = parts [- 2 ] + '.py::' + parts [- 1 ]
6658
+ test_id = full
6659
+ return test_id
6660
+
6601
6661
def __create_log_path_as_needed (self , test_logpath ):
6602
6662
if not os .path .exists (test_logpath ):
6603
6663
try :
6604
6664
os .makedirs (test_logpath )
6605
6665
except Exception :
6606
6666
pass # Only reachable during multi-threaded runs
6607
6667
6668
+ def __process_dashboard (self , has_exception , init = False ):
6669
+ ''' SeleniumBase Dashboard Processing '''
6670
+ if len (sb_config ._extra_dash_entries ) > 1 :
6671
+ # First take care of existing entries from non-SeleniumBase tests
6672
+ for test_id in sb_config ._extra_dash_entries :
6673
+ if test_id in sb_config ._results .keys ():
6674
+ if sb_config ._results [test_id ] == "Skipped" :
6675
+ sb_config .item_count_skipped += 1
6676
+ sb_config .item_count_untested -= 1
6677
+ elif sb_config ._results [test_id ] == "Failed" :
6678
+ sb_config .item_count_failed += 1
6679
+ sb_config .item_count_untested -= 1
6680
+ elif sb_config ._results [test_id ] == "Passed" :
6681
+ sb_config .item_count_passed += 1
6682
+ sb_config .item_count_untested -= 1
6683
+ else : # Mark "Skipped" if unknown
6684
+ sb_config .item_count_skipped += 1
6685
+ sb_config .item_count_untested -= 1
6686
+ sb_config ._extra_dash_entries = [] # Reset the list to empty
6687
+ # Process new entries
6688
+ test_id = self .__get_test_id_2 ()
6689
+ dud = "seleniumbase/plugins/pytest_plugin.py::BaseClass::base_method"
6690
+ if not init :
6691
+ duration_ms = int (time .time () * 1000 ) - sb_config .start_time_ms
6692
+ duration = float (duration_ms ) / 1000.0
6693
+ sb_config ._duration [test_id ] = duration
6694
+ if test_id not in sb_config ._display_id .keys ():
6695
+ sb_config ._display_id [test_id ] = self .__get_display_id ()
6696
+ if sb_config ._display_id [test_id ] == dud :
6697
+ return
6698
+ if hasattr (self , "_using_sb_fixture" ) and (
6699
+ test_id not in sb_config ._results .keys ()):
6700
+ cwd = os .getcwd ()
6701
+ if '\\ ' in cwd :
6702
+ cwd = cwd .split ('\\ ' )[- 1 ]
6703
+ else :
6704
+ cwd = cwd .split ('/' )[- 1 ]
6705
+ if test_id .count ('.' ) > 1 :
6706
+ alt_test_id = '.' .join (test_id .split ('.' )[1 :])
6707
+ if alt_test_id in sb_config ._results .keys ():
6708
+ sb_config ._results .pop (alt_test_id )
6709
+ if test_id in sb_config ._results .keys () and (
6710
+ sb_config ._results [test_id ] == "Skipped" ):
6711
+ sb_config .item_count_skipped += 1
6712
+ sb_config .item_count_untested -= 1
6713
+ sb_config ._results [test_id ] = "Skipped"
6714
+ elif has_exception :
6715
+ sb_config ._results [test_id ] = "Failed"
6716
+ sb_config .item_count_failed += 1
6717
+ sb_config .item_count_untested -= 1
6718
+ else :
6719
+ sb_config ._results [test_id ] = "Passed"
6720
+ sb_config .item_count_passed += 1
6721
+ sb_config .item_count_untested -= 1
6722
+ num_passed = sb_config .item_count_passed
6723
+ num_failed = sb_config .item_count_failed
6724
+ num_skipped = sb_config .item_count_skipped
6725
+ num_untested = sb_config .item_count_untested
6726
+ self .create_pie_chart (title = constants .Dashboard .TITLE )
6727
+ self .add_data_point ("Passed" , num_passed , color = "#84d474" )
6728
+ self .add_data_point ("Untested" , num_untested , color = "#eaeaea" )
6729
+ self .add_data_point ("Skipped" , num_skipped , color = "#efd8b4" )
6730
+ self .add_data_point ("Failed" , num_failed , color = "#f17476" )
6731
+ style = (
6732
+ '<link rel="stylesheet" '
6733
+ 'href="%s">' % constants .Dashboard .STYLE_CSS )
6734
+ auto_refresh_html = ''
6735
+ if num_untested > 0 :
6736
+ # Refresh every X seconds when waiting for more test results
6737
+ auto_refresh_html = constants .Dashboard .META_REFRESH_HTML
6738
+ head = (
6739
+ '<head><meta charset="utf-8" />'
6740
+ '<meta property="og:image" '
6741
+ 'content="https://seleniumbase.io/img/dash_pie.png">'
6742
+ '<link rel="shortcut icon" '
6743
+ 'href="https://seleniumbase.io/img/dash_pie_2.png">'
6744
+ '%s'
6745
+ '<title>Dashboard</title>'
6746
+ '%s</head>' % (auto_refresh_html , style ))
6747
+ table_html = (
6748
+ '<div></div>'
6749
+ '<table border="1px solid #e6e6e6;" width="100%;" padding: 5px;'
6750
+ ' font-size="12px;" text-align="left;" id="results-table">'
6751
+ '<thead id="results-table-head"><tr>'
6752
+ '<th col="result">Result</th><th col="name">Test</th>'
6753
+ '<th col="duration">Duration</th><th col="links">Links</th>'
6754
+ '</tr></thead>' )
6755
+ the_failed = []
6756
+ the_skipped = []
6757
+ the_passed = []
6758
+ the_untested = []
6759
+ for key in sb_config ._results .keys ():
6760
+ t_res = sb_config ._results [key ]
6761
+ t_dur = sb_config ._duration [key ]
6762
+ t_d_id = sb_config ._display_id [key ]
6763
+ res_low = t_res .lower ()
6764
+ if sb_config ._results [key ] == "Failed" :
6765
+ the_failed .append ([res_low , t_res , t_d_id , t_dur ])
6766
+ if sb_config ._results [key ] == "Skipped" :
6767
+ the_skipped .append ([res_low , t_res , t_d_id , t_dur ])
6768
+ if sb_config ._results [key ] == "Passed" :
6769
+ the_passed .append ([res_low , t_res , t_d_id , t_dur ])
6770
+ if sb_config ._results [key ] == "Untested" :
6771
+ the_untested .append ([res_low , t_res , t_d_id , t_dur ])
6772
+ for row in the_failed :
6773
+ row = (
6774
+ '<tbody class="%s results-table-row"><tr>'
6775
+ '<td class="col-result">%s</td><td>%s</td><td>%s</td>'
6776
+ '<td><a href="latest_logs/">latest_logs/</a></td>'
6777
+ '</tr></tbody>' % (row [0 ], row [1 ], row [2 ], row [3 ]))
6778
+ table_html += row
6779
+ for row in the_skipped :
6780
+ row = (
6781
+ '<tbody class="%s results-table-row"><tr>'
6782
+ '<td class="col-result">%s</td><td>%s</td><td>%s</td>'
6783
+ '<td></td></tr></tbody>' % (row [0 ], row [1 ], row [2 ], row [3 ]))
6784
+ table_html += row
6785
+ for row in the_passed :
6786
+ row = (
6787
+ '<tbody class="%s results-table-row"><tr>'
6788
+ '<td class="col-result">%s</td><td>%s</td><td>%s</td>'
6789
+ '<td></td></tr></tbody>' % (row [0 ], row [1 ], row [2 ], row [3 ]))
6790
+ table_html += row
6791
+ for row in the_untested :
6792
+ row = (
6793
+ '<tbody class="%s results-table-row"><tr>'
6794
+ '<td class="col-result">%s</td><td>%s</td><td>%s</td>'
6795
+ '<td></td></tr></tbody>' % (row [0 ], row [1 ], row [2 ], row [3 ]))
6796
+ table_html += row
6797
+ table_html += "</table>"
6798
+ add_more = "<br /><b>Last updated:</b> "
6799
+ timestamp , the_date , the_time = log_helper .get_master_time ()
6800
+ last_updated = "%s at %s" % (the_date , the_time )
6801
+ add_more = add_more + "%s" % last_updated
6802
+ status = "<p></p><div><b>Status:</b> Awaiting results..."
6803
+ status += " (Refresh the page for updates)"
6804
+ if num_untested == 0 :
6805
+ status = "<p></p><div><b>Status:</b> Test Run Complete:"
6806
+ if num_failed == 0 :
6807
+ if num_passed > 0 :
6808
+ if num_skipped == 0 :
6809
+ status += " <b>Success!</b> (All tests passed)"
6810
+ else :
6811
+ status += " <b>Success!</b> (No failing tests)"
6812
+ else :
6813
+ status += " All tests were skipped!"
6814
+ else :
6815
+ latest_logs_dir = "latest_logs/"
6816
+ log_msg = "See latest logs for details"
6817
+ if num_failed == 1 :
6818
+ status += (
6819
+ ' <b>1 test failed!</b> (<a href="%s">%s</a>)'
6820
+ '' % (latest_logs_dir , log_msg ))
6821
+ else :
6822
+ status += (
6823
+ ' <b>%s tests failed!</b> (<a href="%s">%s</a>)'
6824
+ '' % (num_failed , latest_logs_dir , log_msg ))
6825
+ status += "</div><p></p>"
6826
+ add_more = add_more + status
6827
+ gen_by = (
6828
+ '<p><div>Generated by: <b><a href="https://seleniumbase.io/">'
6829
+ 'SeleniumBase</a></b></div></p><p></p>' )
6830
+ add_more = add_more + gen_by
6831
+ # Have dashboard auto-refresh on updates when using an http server
6832
+ refresh_line = (
6833
+ '<script type="text/javascript" src="%s">'
6834
+ '</script>' % constants .Dashboard .LIVE_JS )
6835
+ add_more = add_more + refresh_line
6836
+ the_html = head + self .extract_chart () + table_html + add_more
6837
+ abs_path = os .path .abspath ('.' )
6838
+ file_path = os .path .join (abs_path , "dashboard.html" )
6839
+ out_file = codecs .open (file_path , "w+" , encoding = "utf-8" )
6840
+ out_file .writelines (the_html )
6841
+ out_file .close ()
6842
+ time .sleep (0.05 ) # Add time for dashboard server to process updates
6843
+
6608
6844
def has_exception (self ):
6609
6845
""" (This method should ONLY be used in custom tearDown() methods.)
6610
6846
This method returns True if the test failed or raised an exception.
@@ -6691,6 +6927,7 @@ def tearDown(self):
6691
6927
# Save a screenshot if logging is on when an exception occurs
6692
6928
if has_exception :
6693
6929
self .__add_pytest_html_extra ()
6930
+ sb_config ._has_exception = True
6694
6931
if self .with_testing_base and not has_exception and (
6695
6932
self .save_screenshot_after_test ):
6696
6933
test_logpath = self .log_path + "/" + test_id
@@ -6742,6 +6979,8 @@ def tearDown(self):
6742
6979
log_helper .log_page_source (
6743
6980
test_logpath , self .driver ,
6744
6981
self .__last_page_source )
6982
+ if self .dashboard :
6983
+ self .__process_dashboard (has_exception )
6745
6984
# (Pytest) Finally close all open browser windows
6746
6985
self .__quit_all_drivers ()
6747
6986
if self .headless :
0 commit comments