Best Python code snippet using SeleniumBase
base_case.py
Source:base_case.py
...3314 def skip(self, reason=""):3315 """ Mark the test as Skipped. """3316 self.__check_scope()3317 if self.dashboard or (self.is_pytest and self.with_db_reporting):3318 test_id = self.__get_test_id_2()3319 sb_config._results[test_id] = "Skipped"3320 self.__skip_reason = reason3321 elif reason and not self.is_pytest:3322 # Only needed for nosetest db reporting3323 self._nose_skip_reason = reason3324 self.skipTest(reason)3325 ############3326 # Application "Local Storage" controls3327 def set_local_storage_item(self, key, value):3328 self.__check_scope()3329 self.execute_script(3330 "window.localStorage.setItem('{}', '{}');".format(key, value))3331 def get_local_storage_item(self, key):3332 self.__check_scope()3333 return self.execute_script(3334 "return window.localStorage.getItem('{}');".format(key))3335 def remove_local_storage_item(self, key):3336 self.__check_scope()3337 self.execute_script(3338 "window.localStorage.removeItem('{}');".format(key))3339 def clear_local_storage(self):3340 self.__check_scope()3341 self.execute_script("window.localStorage.clear();")3342 def get_local_storage_keys(self):3343 self.__check_scope()3344 return self.execute_script(3345 "var ls = window.localStorage, keys = []; "3346 "for (var i = 0; i < ls.length; ++i) "3347 " keys[i] = ls.key(i); "3348 "return keys;")3349 def get_local_storage_items(self):3350 self.__check_scope()3351 return self.execute_script(3352 r"var ls = window.localStorage, items = {}; "3353 "for (var i = 0, k; i < ls.length; ++i) "3354 " items[k = ls.key(i)] = ls.getItem(k); "3355 "return items;")3356 # Application "Session Storage" controls3357 def set_session_storage_item(self, key, value):3358 self.__check_scope()3359 self.execute_script(3360 "window.sessionStorage.setItem('{}', '{}');".format(key, value))3361 def get_session_storage_item(self, key):3362 self.__check_scope()3363 return self.execute_script(3364 "return window.sessionStorage.getItem('{}');".format(key))3365 def remove_session_storage_item(self, key):3366 self.__check_scope()3367 self.execute_script(3368 "window.sessionStorage.removeItem('{}');".format(key))3369 def clear_session_storage(self):3370 self.__check_scope()3371 self.execute_script("window.sessionStorage.clear();")3372 def get_session_storage_keys(self):3373 self.__check_scope()3374 return self.execute_script(3375 "var ls = window.sessionStorage, keys = []; "3376 "for (var i = 0; i < ls.length; ++i) "3377 " keys[i] = ls.key(i); "3378 "return keys;")3379 def get_session_storage_items(self):3380 self.__check_scope()3381 return self.execute_script(3382 r"var ls = window.sessionStorage, items = {}; "3383 "for (var i = 0, k; i < ls.length; ++i) "3384 " items[k = ls.key(i)] = ls.getItem(k); "3385 "return items;")3386 ############3387 # Duplicates (Avoids name confusion when migrating from other frameworks.)3388 def open_url(self, url):3389 """ Same as self.open() """3390 self.open(url)3391 def visit(self, url):3392 """ Same as self.open() """3393 self.open(url)3394 def visit_url(self, url):3395 """ Same as self.open() """3396 self.open(url)3397 def goto(self, url):3398 """ Same as self.open() """3399 self.open(url)3400 def go_to(self, url):3401 """ Same as self.open() """3402 self.open(url)3403 def reload(self):3404 """ Same as self.refresh_page() """3405 self.refresh_page()3406 def reload_page(self):3407 """ Same as self.refresh_page() """3408 self.refresh_page()3409 def input(self, selector, text, by=By.CSS_SELECTOR,3410 timeout=None, retry=False):3411 """ Same as self.update_text() """3412 self.__check_scope()3413 if not timeout:3414 timeout = settings.LARGE_TIMEOUT3415 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:3416 timeout = self.__get_new_timeout(timeout)3417 selector, by = self.__recalculate_selector(selector, by)3418 self.update_text(selector, text, by=by, timeout=timeout, retry=retry)3419 def write(self, selector, text, by=By.CSS_SELECTOR,3420 timeout=None, retry=False):3421 """ Same as self.update_text() """3422 self.__check_scope()3423 if not timeout:3424 timeout = settings.LARGE_TIMEOUT3425 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:3426 timeout = self.__get_new_timeout(timeout)3427 selector, by = self.__recalculate_selector(selector, by)3428 self.update_text(selector, text, by=by, timeout=timeout, retry=retry)3429 def send_keys(self, selector, text, by=By.CSS_SELECTOR, timeout=None):3430 """ Same as self.add_text() """3431 self.__check_scope()3432 if not timeout:3433 timeout = settings.LARGE_TIMEOUT3434 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:3435 timeout = self.__get_new_timeout(timeout)3436 selector, by = self.__recalculate_selector(selector, by)3437 self.add_text(selector, text, by=by, timeout=timeout)3438 def click_link(self, link_text, timeout=None):3439 """ Same as self.click_link_text() """3440 self.__check_scope()3441 if not timeout:3442 timeout = settings.SMALL_TIMEOUT3443 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:3444 timeout = self.__get_new_timeout(timeout)3445 self.click_link_text(link_text, timeout=timeout)3446 def click_partial_link(self, partial_link_text, timeout=None):3447 """ Same as self.click_partial_link_text() """3448 self.__check_scope()3449 if not timeout:3450 timeout = settings.SMALL_TIMEOUT3451 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:3452 timeout = self.__get_new_timeout(timeout)3453 self.click_partial_link_text(partial_link_text, timeout=timeout)3454 def wait_for_element_visible(self, selector, by=By.CSS_SELECTOR,3455 timeout=None):3456 """ Same as self.wait_for_element() """3457 self.__check_scope()3458 if not timeout:3459 timeout = settings.LARGE_TIMEOUT3460 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:3461 timeout = self.__get_new_timeout(timeout)3462 selector, by = self.__recalculate_selector(selector, by)3463 return page_actions.wait_for_element_visible(3464 self.driver, selector, by, timeout)3465 def wait_for_element_not_present(self, selector, by=By.CSS_SELECTOR,3466 timeout=None):3467 """ Same as self.wait_for_element_absent()3468 Waits for an element to no longer appear in the HTML of a page.3469 A hidden element still counts as appearing in the page HTML.3470 If an element with "hidden" status is acceptable,3471 use wait_for_element_not_visible() instead. """3472 self.__check_scope()3473 if not timeout:3474 timeout = settings.LARGE_TIMEOUT3475 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:3476 timeout = self.__get_new_timeout(timeout)3477 selector, by = self.__recalculate_selector(selector, by)3478 return page_actions.wait_for_element_absent(3479 self.driver, selector, by, timeout)3480 def assert_element_not_present(self, selector, by=By.CSS_SELECTOR,3481 timeout=None):3482 """ Same as self.assert_element_absent()3483 Will raise an exception if the element stays present.3484 Returns True if successful. Default timeout = SMALL_TIMEOUT. """3485 self.__check_scope()3486 if not timeout:3487 timeout = settings.SMALL_TIMEOUT3488 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:3489 timeout = self.__get_new_timeout(timeout)3490 self.wait_for_element_absent(selector, by=by, timeout=timeout)3491 return True3492 def assert_no_broken_links(self, multithreaded=True):3493 """ Same as self.assert_no_404_errors() """3494 self.assert_no_404_errors(multithreaded=multithreaded)3495 def wait(self, seconds):3496 """ Same as self.sleep() - Some JS frameworks use this method name. """3497 self.sleep(seconds)3498 def block_ads(self):3499 """ Same as self.ad_block() """3500 self.ad_block()3501 def _print(self, msg):3502 """ Same as Python's print() """3503 print(msg)3504 def start_tour(self, name=None, interval=0):3505 self.play_tour(name=name, interval=interval)3506 ############3507 def add_css_link(self, css_link):3508 self.__check_scope()3509 js_utils.add_css_link(self.driver, css_link)3510 def add_js_link(self, js_link):3511 self.__check_scope()3512 js_utils.add_js_link(self.driver, js_link)3513 def add_css_style(self, css_style):3514 self.__check_scope()3515 js_utils.add_css_style(self.driver, css_style)3516 def add_js_code_from_link(self, js_link):3517 self.__check_scope()3518 js_utils.add_js_code_from_link(self.driver, js_link)3519 def add_js_code(self, js_code):3520 self.__check_scope()3521 js_utils.add_js_code(self.driver, js_code)3522 def add_meta_tag(self, http_equiv=None, content=None):3523 self.__check_scope()3524 js_utils.add_meta_tag(3525 self.driver, http_equiv=http_equiv, content=content)3526 ############3527 def create_presentation(3528 self, name=None, theme="default", transition="default"):3529 """ Creates a Reveal-JS presentation that you can add slides to.3530 @Params3531 name - If creating multiple presentations at the same time,3532 use this to specify the name of the current presentation.3533 theme - Set a theme with a unique style for the presentation.3534 Valid themes: "serif" (default), "sky", "white", "black",3535 "simple", "league", "moon", "night",3536 "beige", "blood", and "solarized".3537 transition - Set a transition between slides.3538 Valid transitions: "none" (default), "slide", "fade",3539 "zoom", "convex", and "concave".3540 """3541 if not name:3542 name = "default"3543 if not theme or theme == "default":3544 theme = "serif"3545 valid_themes = (["serif", "white", "black", "beige", "simple", "sky",3546 "league", "moon", "night", "blood", "solarized"])3547 theme = theme.lower()3548 if theme not in valid_themes:3549 raise Exception(3550 "Theme {%s} not found! Valid themes: %s"3551 "" % (theme, valid_themes))3552 if not transition or transition == "default":3553 transition = "none"3554 valid_transitions = (3555 ["none", "slide", "fade", "zoom", "convex", "concave"])3556 transition = transition.lower()3557 if transition not in valid_transitions:3558 raise Exception(3559 "Transition {%s} not found! Valid transitions: %s"3560 "" % (transition, valid_transitions))3561 reveal_theme_css = None3562 if theme == "serif":3563 reveal_theme_css = constants.Reveal.SERIF_MIN_CSS3564 elif theme == "sky":3565 reveal_theme_css = constants.Reveal.SKY_MIN_CSS3566 elif theme == "white":3567 reveal_theme_css = constants.Reveal.WHITE_MIN_CSS3568 elif theme == "black":3569 reveal_theme_css = constants.Reveal.BLACK_MIN_CSS3570 elif theme == "simple":3571 reveal_theme_css = constants.Reveal.SIMPLE_MIN_CSS3572 elif theme == "league":3573 reveal_theme_css = constants.Reveal.LEAGUE_MIN_CSS3574 elif theme == "moon":3575 reveal_theme_css = constants.Reveal.MOON_MIN_CSS3576 elif theme == "night":3577 reveal_theme_css = constants.Reveal.NIGHT_MIN_CSS3578 elif theme == "beige":3579 reveal_theme_css = constants.Reveal.BEIGE_MIN_CSS3580 elif theme == "blood":3581 reveal_theme_css = constants.Reveal.BLOOD_MIN_CSS3582 elif theme == "solarized":3583 reveal_theme_css = constants.Reveal.SOLARIZED_MIN_CSS3584 else:3585 # Use the default if unable to determine the theme3586 reveal_theme_css = constants.Reveal.SERIF_MIN_CSS3587 new_presentation = (3588 '<html>\n'3589 '<head>\n'3590 '<meta charset="utf-8">\n'3591 '<meta http-equiv="Content-Type" '3592 'content="text/html, charset=utf-8;">\n'3593 '<meta name="viewport" content="shrink-to-fit=no">\n'3594 '<link rel="stylesheet" href="%s">\n'3595 '<link rel="stylesheet" href="%s">\n'3596 '<style>\n'3597 'pre{background-color:#fbe8d4;border-radius:8px;}\n'3598 'div[flex_div]{height:68vh;margin:0;align-items:center;'3599 'justify-content:center;}\n'3600 'img[rounded]{border-radius:16px;max-width:64%%;}\n'3601 '</style>\n'3602 '</head>\n\n'3603 '<body>\n'3604 '<!-- Generated by SeleniumBase - https://seleniumbase.io -->\n'3605 '<div class="reveal">\n'3606 '<div class="slides">\n'3607 '' % (constants.Reveal.MIN_CSS, reveal_theme_css))3608 self._presentation_slides[name] = []3609 self._presentation_slides[name].append(new_presentation)3610 self._presentation_transition[name] = transition3611 def add_slide(self, content=None, image=None, code=None, iframe=None,3612 content2=None, notes=None, transition=None, name=None):3613 """ Allows the user to add slides to a presentation.3614 @Params3615 content - The HTML content to display on the presentation slide.3616 image - Attach an image (from a URL link) to the slide.3617 code - Attach code of any programming language to the slide.3618 Language-detection will be used to add syntax formatting.3619 iframe - Attach an iFrame (from a URL link) to the slide.3620 content2 - HTML content to display after adding an image or code.3621 notes - Additional notes to include with the slide.3622 ONLY SEEN if show_notes is set for the presentation.3623 transition - Set a transition between slides. (overrides previous)3624 Valid transitions: "none" (default), "slide", "fade",3625 "zoom", "convex", and "concave".3626 name - If creating multiple presentations at the same time,3627 use this to select the presentation to add slides to.3628 """3629 if not name:3630 name = "default"3631 if name not in self._presentation_slides:3632 # Create a presentation if it doesn't already exist3633 self.create_presentation(name=name)3634 if not content:3635 content = ""3636 if not content2:3637 content2 = ""3638 if not notes:3639 notes = ""3640 if not transition:3641 transition = self._presentation_transition[name]3642 elif transition == "default":3643 transition = "none"3644 valid_transitions = (3645 ["none", "slide", "fade", "zoom", "convex", "concave"])3646 transition = transition.lower()3647 if transition not in valid_transitions:3648 raise Exception(3649 "Transition {%s} not found! Valid transitions: %s"3650 "" % (transition, valid_transitions))3651 add_line = ""3652 if content.startswith("<"):3653 add_line = "\n"3654 html = (3655 '\n<section data-transition="%s">%s%s' % (3656 transition, add_line, content))3657 if image:3658 html += '\n<div flex_div><img rounded src="%s" /></div>' % image3659 if code:3660 html += '\n<div></div>'3661 html += '\n<pre class="prettyprint">\n%s</pre>' % code3662 if iframe:3663 html += ('\n<div></div>'3664 '\n<iframe src="%s" style="width:92%%;height:550px;" '3665 'title="iframe content"></iframe>' % iframe)3666 add_line = ""3667 if content2.startswith("<"):3668 add_line = "\n"3669 if content2:3670 html += '%s%s' % (add_line, content2)3671 html += '\n<aside class="notes">%s</aside>' % notes3672 html += '\n</section>\n'3673 self._presentation_slides[name].append(html)3674 def save_presentation(3675 self, name=None, filename=None, show_notes=False, interval=0):3676 """ Saves a Reveal-JS Presentation to a file for later use.3677 @Params3678 name - If creating multiple presentations at the same time,3679 use this to select the one you wish to use.3680 filename - The name of the HTML file that you wish to3681 save the presentation to. (filename must end in ".html")3682 show_notes - When set to True, the Notes feature becomes enabled,3683 which allows presenters to see notes next to slides.3684 interval - The delay time between autoplaying slides. (in seconds)3685 If set to 0 (default), autoplay is disabled.3686 """3687 if not name:3688 name = "default"3689 if not filename:3690 filename = "my_presentation.html"3691 if name not in self._presentation_slides:3692 raise Exception("Presentation {%s} does not exist!" % name)3693 if not filename.endswith('.html'):3694 raise Exception('Presentation file must end in ".html"!')3695 if not interval:3696 interval = 03697 if not type(interval) is int and not type(interval) is float:3698 raise Exception('Expecting a numeric value for "interval"!')3699 if interval < 0:3700 raise Exception('The "interval" cannot be a negative number!')3701 interval_ms = float(interval) * 1000.03702 show_notes_str = "false"3703 if show_notes:3704 show_notes_str = "true"3705 the_html = ""3706 for slide in self._presentation_slides[name]:3707 the_html += slide3708 the_html += (3709 '\n</div>\n'3710 '</div>\n'3711 '<script src="%s"></script>\n'3712 '<script src="%s"></script>\n'3713 '<script>Reveal.initialize('3714 '{showNotes: %s, slideNumber: true, hash: false, '3715 'autoSlide: %s,});'3716 '</script>\n'3717 '</body>\n'3718 '</html>\n'3719 '' % (constants.Reveal.MIN_JS,3720 constants.PrettifyJS.RUN_PRETTIFY_JS,3721 show_notes_str,3722 interval_ms))3723 saved_presentations_folder = constants.Presentations.SAVED_FOLDER3724 if saved_presentations_folder.endswith("/"):3725 saved_presentations_folder = saved_presentations_folder[:-1]3726 if not os.path.exists(saved_presentations_folder):3727 try:3728 os.makedirs(saved_presentations_folder)3729 except Exception:3730 pass3731 file_path = saved_presentations_folder + "/" + filename3732 out_file = codecs.open(file_path, "w+", encoding="utf-8")3733 out_file.writelines(the_html)3734 out_file.close()3735 print('\n>>> [%s] was saved!\n' % file_path)3736 return file_path3737 def begin_presentation(3738 self, name=None, filename=None, show_notes=False, interval=0):3739 """ Begin a Reveal-JS Presentation in the web browser.3740 @Params3741 name - If creating multiple presentations at the same time,3742 use this to select the one you wish to use.3743 filename - The name of the HTML file that you wish to3744 save the presentation to. (filename must end in ".html")3745 show_notes - When set to True, the Notes feature becomes enabled,3746 which allows presenters to see notes next to slides.3747 interval - The delay time between autoplaying slides. (in seconds)3748 If set to 0 (default), autoplay is disabled.3749 """3750 if self.headless:3751 return # Presentations should not run in headless mode.3752 if not name:3753 name = "default"3754 if not filename:3755 filename = "my_presentation.html"3756 if name not in self._presentation_slides:3757 raise Exception("Presentation {%s} does not exist!" % name)3758 if not filename.endswith('.html'):3759 raise Exception('Presentation file must end in ".html"!')3760 if not interval:3761 interval = 03762 if not type(interval) is int and not type(interval) is float:3763 raise Exception('Expecting a numeric value for "interval"!')3764 if interval < 0:3765 raise Exception('The "interval" cannot be a negative number!')3766 end_slide = (3767 '\n<section data-transition="none">\n'3768 '<p class="End_Presentation_Now"> </p>\n</section>\n')3769 self._presentation_slides[name].append(end_slide)3770 file_path = self.save_presentation(3771 name=name, filename=filename,3772 show_notes=show_notes, interval=interval)3773 self._presentation_slides[name].pop()3774 self.open_html_file(file_path)3775 presentation_folder = constants.Presentations.SAVED_FOLDER3776 try:3777 while (len(self.driver.window_handles) > 0 and (3778 presentation_folder in self.get_current_url())):3779 time.sleep(0.05)3780 if self.is_element_visible(3781 "section.present p.End_Presentation_Now"):3782 break3783 time.sleep(0.05)3784 except Exception:3785 pass3786 ############3787 def create_pie_chart(3788 self, chart_name=None, title=None, subtitle=None,3789 data_name=None, unit=None, libs=True):3790 """ Creates a JavaScript pie chart using "HighCharts".3791 @Params3792 chart_name - If creating multiple charts,3793 use this to select which one.3794 title - The title displayed for the chart.3795 subtitle - The subtitle displayed for the chart.3796 data_name - Set the series name. Useful for multi-series charts.3797 unit - The description label given to the chart's y-axis values.3798 libs - The option to include Chart libraries (JS and CSS files).3799 Should be set to True (default) for the first time creating3800 a chart on a web page. If creating multiple charts on the3801 same web page, you won't need to re-import the libraries3802 when creating additional charts.3803 """3804 if not chart_name:3805 chart_name = "default"3806 if not data_name:3807 data_name = ""3808 style = "pie"3809 self.__create_highchart(3810 chart_name=chart_name, title=title, subtitle=subtitle,3811 style=style, data_name=data_name, unit=unit, libs=libs)3812 def create_bar_chart(3813 self, chart_name=None, title=None, subtitle=None,3814 data_name=None, unit=None, libs=True):3815 """ Creates a JavaScript bar chart using "HighCharts".3816 @Params3817 chart_name - If creating multiple charts,3818 use this to select which one.3819 title - The title displayed for the chart.3820 subtitle - The subtitle displayed for the chart.3821 data_name - Set the series name. Useful for multi-series charts.3822 unit - The description label given to the chart's y-axis values.3823 libs - The option to include Chart libraries (JS and CSS files).3824 Should be set to True (default) for the first time creating3825 a chart on a web page. If creating multiple charts on the3826 same web page, you won't need to re-import the libraries3827 when creating additional charts.3828 """3829 if not chart_name:3830 chart_name = "default"3831 if not data_name:3832 data_name = ""3833 style = "bar"3834 self.__create_highchart(3835 chart_name=chart_name, title=title, subtitle=subtitle,3836 style=style, data_name=data_name, unit=unit, libs=libs)3837 def create_column_chart(3838 self, chart_name=None, title=None, subtitle=None,3839 data_name=None, unit=None, libs=True):3840 """ Creates a JavaScript column chart using "HighCharts".3841 @Params3842 chart_name - If creating multiple charts,3843 use this to select which one.3844 title - The title displayed for the chart.3845 subtitle - The subtitle displayed for the chart.3846 data_name - Set the series name. Useful for multi-series charts.3847 unit - The description label given to the chart's y-axis values.3848 libs - The option to include Chart libraries (JS and CSS files).3849 Should be set to True (default) for the first time creating3850 a chart on a web page. If creating multiple charts on the3851 same web page, you won't need to re-import the libraries3852 when creating additional charts.3853 """3854 if not chart_name:3855 chart_name = "default"3856 if not data_name:3857 data_name = ""3858 style = "column"3859 self.__create_highchart(3860 chart_name=chart_name, title=title, subtitle=subtitle,3861 style=style, data_name=data_name, unit=unit, libs=libs)3862 def create_line_chart(3863 self, chart_name=None, title=None, subtitle=None,3864 data_name=None, unit=None, zero=False, libs=True):3865 """ Creates a JavaScript line chart using "HighCharts".3866 @Params3867 chart_name - If creating multiple charts,3868 use this to select which one.3869 title - The title displayed for the chart.3870 subtitle - The subtitle displayed for the chart.3871 data_name - Set the series name. Useful for multi-series charts.3872 unit - The description label given to the chart's y-axis values.3873 zero - If True, the y-axis always starts at 0. (Default: False).3874 libs - The option to include Chart libraries (JS and CSS files).3875 Should be set to True (default) for the first time creating3876 a chart on a web page. If creating multiple charts on the3877 same web page, you won't need to re-import the libraries3878 when creating additional charts.3879 """3880 if not chart_name:3881 chart_name = "default"3882 if not data_name:3883 data_name = ""3884 style = "line"3885 self.__create_highchart(3886 chart_name=chart_name, title=title, subtitle=subtitle,3887 style=style, data_name=data_name, unit=unit, zero=zero, libs=libs)3888 def create_area_chart(3889 self, chart_name=None, title=None, subtitle=None,3890 data_name=None, unit=None, zero=False, libs=True):3891 """ Creates a JavaScript area chart using "HighCharts".3892 @Params3893 chart_name - If creating multiple charts,3894 use this to select which one.3895 title - The title displayed for the chart.3896 subtitle - The subtitle displayed for the chart.3897 data_name - Set the series name. Useful for multi-series charts.3898 unit - The description label given to the chart's y-axis values.3899 zero - If True, the y-axis always starts at 0. (Default: False).3900 libs - The option to include Chart libraries (JS and CSS files).3901 Should be set to True (default) for the first time creating3902 a chart on a web page. If creating multiple charts on the3903 same web page, you won't need to re-import the libraries3904 when creating additional charts.3905 """3906 if not chart_name:3907 chart_name = "default"3908 if not data_name:3909 data_name = ""3910 style = "area"3911 self.__create_highchart(3912 chart_name=chart_name, title=title, subtitle=subtitle,3913 style=style, data_name=data_name, unit=unit, zero=zero, libs=libs)3914 def __create_highchart(3915 self, chart_name=None, title=None, subtitle=None,3916 style=None, data_name=None, unit=None, zero=False, libs=True):3917 """ Creates a JavaScript chart using the "HighCharts" library. """3918 if not chart_name:3919 chart_name = "default"3920 if not title:3921 title = ""3922 if not subtitle:3923 subtitle = ""3924 if not style:3925 style = "pie"3926 if not data_name:3927 data_name = "Series 1"3928 if not unit:3929 unit = "Values"3930 title = title.replace("'", "\\'")3931 subtitle = subtitle.replace("'", "\\'")3932 unit = unit.replace("'", "\\'")3933 self._chart_count += 13934 chart_libs = (3935 """3936 <script src="%s"></script>3937 <script src="%s"></script>3938 <script src="%s"></script>3939 <script src="%s"></script>3940 <script src="%s"></script>3941 """ % (3942 constants.JQuery.MIN_JS,3943 constants.HighCharts.HC_JS,3944 constants.HighCharts.EXPORTING_JS,3945 constants.HighCharts.EXPORT_DATA_JS,3946 constants.HighCharts.ACCESSIBILITY_JS))3947 if not libs:3948 chart_libs = ""3949 chart_css = (3950 """3951 <style>3952 .highcharts-figure, .highcharts-data-table table {3953 min-width: 320px;3954 max-width: 660px;3955 margin: 1em auto;3956 }3957 .highcharts-data-table table {3958 font-family: Verdana, sans-serif;3959 border-collapse: collapse;3960 border: 1px solid #EBEBEB;3961 margin: 10px auto;3962 text-align: center;3963 width: 100%;3964 max-width: 500px;3965 }3966 .highcharts-data-table caption {3967 padding: 1em 0;3968 font-size: 1.2em;3969 color: #555;3970 }3971 .highcharts-data-table th {3972 font-weight: 600;3973 padding: 0.5em;3974 }3975 .highcharts-data-table td, .highcharts-data-table th,3976 .highcharts-data-table caption {3977 padding: 0.5em;3978 }3979 .highcharts-data-table thead tr,3980 .highcharts-data-table tr:nth-child(even) {3981 background: #f8f8f8;3982 }3983 .highcharts-data-table tr:hover {3984 background: #f1f7ff;3985 }3986 </style>3987 """)3988 if not libs:3989 chart_css = ""3990 chart_description = ""3991 chart_figure = (3992 """3993 <figure class="highcharts-figure">3994 <div id="chartcontainer%s"></div>3995 <p class="highcharts-description">%s</p>3996 </figure>3997 """ % (self._chart_count, chart_description))3998 min_zero = ""3999 if zero:4000 min_zero = "min: 0,"4001 chart_init_1 = (4002 """4003 <script>4004 // Build the chart4005 Highcharts.chart('chartcontainer%s', {4006 credits: {4007 enabled: false4008 },4009 title: {4010 text: '%s'4011 },4012 subtitle: {4013 text: '%s'4014 },4015 xAxis: { },4016 yAxis: {4017 %s4018 title: {4019 text: '%s',4020 style: {4021 fontSize: '14px'4022 }4023 },4024 labels: {4025 useHTML: true,4026 style: {4027 fontSize: '14px'4028 }4029 }4030 },4031 chart: {4032 renderTo: 'statusChart',4033 plotBackgroundColor: null,4034 plotBorderWidth: null,4035 plotShadow: false,4036 type: '%s'4037 },4038 """ % (self._chart_count, title, subtitle, min_zero, unit, style))4039 # "{series.name}:"4040 point_format = (r'<b>{point.y}</b><br />'4041 r'<b>{point.percentage:.1f}%</b>')4042 if style != "pie":4043 point_format = (r'<b>{point.y}</b>')4044 chart_init_2 = (4045 """4046 tooltip: {4047 enabled: true,4048 useHTML: true,4049 style: {4050 padding: '6px',4051 fontSize: '14px'4052 },4053 pointFormat: '%s'4054 },4055 """ % point_format)4056 chart_init_3 = (4057 r"""4058 accessibility: {4059 point: {4060 valueSuffix: '%'4061 }4062 },4063 plotOptions: {4064 pie: {4065 size: "95%",4066 allowPointSelect: true,4067 animation: false,4068 cursor: 'pointer',4069 dataLabels: {4070 // enabled: false,4071 // format: '{point.name}: {point.y:.0f}',4072 formatter: function() {4073 if (this.y > 0) {4074 return this.point.name + ': ' + this.point.y4075 }4076 }4077 },4078 states: {4079 hover: {4080 enabled: true4081 }4082 },4083 showInLegend: true4084 }4085 },4086 """)4087 if style != "pie":4088 chart_init_3 = (4089 """4090 allowPointSelect: true,4091 cursor: 'pointer',4092 legend: {4093 layout: 'vertical',4094 align: 'right',4095 verticalAlign: 'middle'4096 },4097 states: {4098 hover: {4099 enabled: true4100 }4101 },4102 plotOptions: {4103 series: {4104 showInLegend: true,4105 animation: false,4106 dataLabels: {4107 enabled: true4108 },4109 shadow: false,4110 lineWidth: 3,4111 fillOpacity: 0.5,4112 marker: {4113 enabled: true4114 }4115 }4116 },4117 """)4118 chart_init = chart_init_1 + chart_init_2 + chart_init_34119 color_by_point = "true"4120 if style != "pie":4121 color_by_point = "false"4122 series = (4123 """4124 series: [{4125 name: '%s',4126 colorByPoint: %s,4127 data: [4128 """ % (data_name, color_by_point))4129 new_chart = chart_libs + chart_css + chart_figure + chart_init + series4130 self._chart_data[chart_name] = []4131 self._chart_label[chart_name] = []4132 self._chart_data[chart_name].append(new_chart)4133 self._chart_first_series[chart_name] = True4134 self._chart_series_count[chart_name] = 14135 def add_series_to_chart(self, data_name=None, chart_name=None):4136 """ Add a new data series to an existing chart.4137 This allows charts to have multiple data sets.4138 @Params4139 data_name - Set the series name. Useful for multi-series charts.4140 chart_name - If creating multiple charts,4141 use this to select which one.4142 """4143 if not chart_name:4144 chart_name = "default"4145 self._chart_series_count[chart_name] += 14146 if not data_name:4147 data_name = "Series %s" % self._chart_series_count[chart_name]4148 series = (4149 """4150 ]4151 },4152 {4153 name: '%s',4154 colorByPoint: false,4155 data: [4156 """ % data_name)4157 self._chart_data[chart_name].append(series)4158 self._chart_first_series[chart_name] = False4159 def add_data_point(self, label, value, color=None, chart_name=None):4160 """ Add a data point to a SeleniumBase-generated chart.4161 @Params4162 label - The label name for the data point.4163 value - The numeric value of the data point.4164 color - The HTML color of the data point.4165 Can be an RGB color. Eg: "#55ACDC".4166 Can also be a named color. Eg: "Teal".4167 chart_name - If creating multiple charts,4168 use this to select which one.4169 """4170 if not chart_name:4171 chart_name = "default"4172 if chart_name not in self._chart_data:4173 # Create a chart if it doesn't already exist4174 self.create_pie_chart(chart_name=chart_name)4175 if not value:4176 value = 04177 if not type(value) is int and not type(value) is float:4178 raise Exception('Expecting a numeric value for "value"!')4179 if not color:4180 color = ""4181 label = label.replace("'", "\\'")4182 color = color.replace("'", "\\'")4183 data_point = (4184 """4185 {4186 name: '%s',4187 y: %s,4188 color: '%s'4189 },4190 """ % (label, value, color))4191 self._chart_data[chart_name].append(data_point)4192 if self._chart_first_series[chart_name]:4193 self._chart_label[chart_name].append(label)4194 def save_chart(self, chart_name=None, filename=None, folder=None):4195 """ Saves a SeleniumBase-generated chart to a file for later use.4196 @Params4197 chart_name - If creating multiple charts at the same time,4198 use this to select the one you wish to use.4199 filename - The name of the HTML file that you wish to4200 save the chart to. (filename must end in ".html")4201 folder - The name of the folder where you wish to4202 save the HTML file. (Default: "./saved_charts/")4203 """4204 if not chart_name:4205 chart_name = "default"4206 if not filename:4207 filename = "my_chart.html"4208 if chart_name not in self._chart_data:4209 raise Exception("Chart {%s} does not exist!" % chart_name)4210 if not filename.endswith('.html'):4211 raise Exception('Chart file must end in ".html"!')4212 the_html = '<meta charset="utf-8">\n'4213 the_html += '<meta http-equiv="Content-Type" '4214 the_html += 'content="text/html, charset=utf-8;">\n'4215 the_html += '<meta name="viewport" content="shrink-to-fit=no">\n'4216 for chart_data_point in self._chart_data[chart_name]:4217 the_html += chart_data_point4218 the_html += (4219 """4220 ]4221 }]4222 });4223 </script>4224 """)4225 axis = "xAxis: {\n"4226 axis += " labels: {\n"4227 axis += " useHTML: true,\n"4228 axis += " style: {\n"4229 axis += " fontSize: '14px',\n"4230 axis += " },\n"4231 axis += " },\n"4232 axis += " categories: ["4233 for label in self._chart_label[chart_name]:4234 axis += "'%s'," % label4235 axis += "], crosshair: false},"4236 the_html = the_html.replace("xAxis: { },", axis)4237 if not folder:4238 saved_charts_folder = constants.Charts.SAVED_FOLDER4239 else:4240 saved_charts_folder = folder4241 if saved_charts_folder.endswith("/"):4242 saved_charts_folder = saved_charts_folder[:-1]4243 if not os.path.exists(saved_charts_folder):4244 try:4245 os.makedirs(saved_charts_folder)4246 except Exception:4247 pass4248 file_path = saved_charts_folder + "/" + filename4249 out_file = codecs.open(file_path, "w+", encoding="utf-8")4250 out_file.writelines(the_html)4251 out_file.close()4252 print('\n>>> [%s] was saved!' % file_path)4253 return file_path4254 def display_chart(self, chart_name=None, filename=None, interval=0):4255 """ Displays a SeleniumBase-generated chart in the browser window.4256 @Params4257 chart_name - If creating multiple charts at the same time,4258 use this to select the one you wish to use.4259 filename - The name of the HTML file that you wish to4260 save the chart to. (filename must end in ".html")4261 interval - The delay time for auto-advancing charts. (in seconds)4262 If set to 0 (default), auto-advancing is disabled.4263 """4264 if self.headless:4265 interval = 1 # Race through chart if running in headless mode4266 if not chart_name:4267 chart_name = "default"4268 if not filename:4269 filename = "my_chart.html"4270 if not interval:4271 interval = 04272 if not type(interval) is int and not type(interval) is float:4273 raise Exception('Expecting a numeric value for "interval"!')4274 if interval < 0:4275 raise Exception('The "interval" cannot be a negative number!')4276 if chart_name not in self._chart_data:4277 raise Exception("Chart {%s} does not exist!" % chart_name)4278 if not filename.endswith('.html'):4279 raise Exception('Chart file must end in ".html"!')4280 file_path = self.save_chart(chart_name=chart_name, filename=filename)4281 self.open_html_file(file_path)4282 chart_folder = constants.Charts.SAVED_FOLDER4283 if interval == 0:4284 try:4285 print("\n*** Close the browser window to continue ***")4286 # Will also continue if manually navigating to a new page4287 while (len(self.driver.window_handles) > 0 and (4288 chart_folder in self.get_current_url())):4289 time.sleep(0.05)4290 except Exception:4291 pass4292 else:4293 try:4294 start_ms = time.time() * 1000.04295 stop_ms = start_ms + (interval * 1000.0)4296 for x in range(int(interval * 10)):4297 now_ms = time.time() * 1000.04298 if now_ms >= stop_ms:4299 break4300 if len(self.driver.window_handles) == 0:4301 break4302 if chart_folder not in self.get_current_url():4303 break4304 time.sleep(0.1)4305 except Exception:4306 pass4307 def extract_chart(self, chart_name=None):4308 """ Extracts the HTML from a SeleniumBase-generated chart.4309 @Params4310 chart_name - If creating multiple charts at the same time,4311 use this to select the one you wish to use.4312 """4313 if not chart_name:4314 chart_name = "default"4315 if chart_name not in self._chart_data:4316 raise Exception("Chart {%s} does not exist!" % chart_name)4317 the_html = ""4318 for chart_data_point in self._chart_data[chart_name]:4319 the_html += chart_data_point4320 the_html += (4321 """4322 ]4323 }]4324 });4325 </script>4326 """)4327 axis = "xAxis: {\n"4328 axis += " labels: {\n"4329 axis += " useHTML: true,\n"4330 axis += " style: {\n"4331 axis += " fontSize: '14px',\n"4332 axis += " },\n"4333 axis += " },\n"4334 axis += " categories: ["4335 for label in self._chart_label[chart_name]:4336 axis += "'%s'," % label4337 axis += "], crosshair: false},"4338 the_html = the_html.replace("xAxis: { },", axis)4339 return the_html4340 ############4341 def create_tour(self, name=None, theme=None):4342 """ Creates a tour for a website. By default, the Shepherd JavaScript4343 Library is used with the Shepherd "Light" / "Arrows" theme.4344 @Params4345 name - If creating multiple tours at the same time,4346 use this to select the tour you wish to add steps to.4347 theme - Sets the default theme for the tour.4348 Choose from "light"/"arrows", "dark", "default", "square",4349 and "square-dark". ("arrows" is used if None is selected.)4350 Alternatively, you may use a different JavaScript Library4351 as the theme. Those include "IntroJS", "DriverJS",4352 "Hopscotch", and "Bootstrap".4353 """4354 if not name:4355 name = "default"4356 if theme:4357 if theme.lower() == "bootstrap":4358 self.create_bootstrap_tour(name)4359 elif theme.lower() == "hopscotch":4360 self.create_hopscotch_tour(name)4361 elif theme.lower() == "intro":4362 self.create_introjs_tour(name)4363 elif theme.lower() == "introjs":4364 self.create_introjs_tour(name)4365 elif theme.lower() == "driver":4366 self.create_driverjs_tour(name)4367 elif theme.lower() == "driverjs":4368 self.create_driverjs_tour(name)4369 elif theme.lower() == "shepherd":4370 self.create_shepherd_tour(name, theme="light")4371 elif theme.lower() == "light":4372 self.create_shepherd_tour(name, theme="light")4373 elif theme.lower() == "dark":4374 self.create_shepherd_tour(name, theme="dark")4375 elif theme.lower() == "arrows":4376 self.create_shepherd_tour(name, theme="light")4377 elif theme.lower() == "square":4378 self.create_shepherd_tour(name, theme="square")4379 elif theme.lower() == "square-dark":4380 self.create_shepherd_tour(name, theme="square-dark")4381 elif theme.lower() == "default":4382 self.create_shepherd_tour(name, theme="default")4383 else:4384 self.create_shepherd_tour(name, theme)4385 else:4386 self.create_shepherd_tour(name, theme="light")4387 def create_shepherd_tour(self, name=None, theme=None):4388 """ Creates a Shepherd JS website tour.4389 @Params4390 name - If creating multiple tours at the same time,4391 use this to select the tour you wish to add steps to.4392 theme - Sets the default theme for the tour.4393 Choose from "light"/"arrows", "dark", "default", "square",4394 and "square-dark". ("light" is used if None is selected.)4395 """4396 shepherd_theme = "shepherd-theme-arrows"4397 if theme:4398 if theme.lower() == "default":4399 shepherd_theme = "shepherd-theme-default"4400 elif theme.lower() == "dark":4401 shepherd_theme = "shepherd-theme-dark"4402 elif theme.lower() == "light":4403 shepherd_theme = "shepherd-theme-arrows"4404 elif theme.lower() == "arrows":4405 shepherd_theme = "shepherd-theme-arrows"4406 elif theme.lower() == "square":4407 shepherd_theme = "shepherd-theme-square"4408 elif theme.lower() == "square-dark":4409 shepherd_theme = "shepherd-theme-square-dark"4410 if not name:4411 name = "default"4412 new_tour = (4413 """4414 // Shepherd Tour4415 var tour = new Shepherd.Tour({4416 defaults: {4417 classes: '%s',4418 scrollTo: true4419 }4420 });4421 var allButtons = {4422 skip: {4423 text: "Skip",4424 action: tour.cancel,4425 classes: 'shepherd-button-secondary tour-button-left'4426 },4427 back: {4428 text: "Back",4429 action: tour.back,4430 classes: 'shepherd-button-secondary'4431 },4432 next: {4433 text: "Next",4434 action: tour.next,4435 classes: 'shepherd-button-primary tour-button-right'4436 },4437 };4438 var firstStepButtons = [allButtons.skip, allButtons.next];4439 var midTourButtons = [allButtons.back, allButtons.next];4440 """ % shepherd_theme)4441 self._tour_steps[name] = []4442 self._tour_steps[name].append(new_tour)4443 def create_bootstrap_tour(self, name=None):4444 """ Creates a Bootstrap tour for a website.4445 @Params4446 name - If creating multiple tours at the same time,4447 use this to select the tour you wish to add steps to.4448 """4449 if not name:4450 name = "default"4451 new_tour = (4452 """4453 // Bootstrap Tour4454 var tour = new Tour({4455 });4456 tour.addSteps([4457 """)4458 self._tour_steps[name] = []4459 self._tour_steps[name].append(new_tour)4460 def create_driverjs_tour(self, name=None):4461 """ Creates a DriverJS tour for a website.4462 @Params4463 name - If creating multiple tours at the same time,4464 use this to select the tour you wish to add steps to.4465 """4466 if not name:4467 name = "default"4468 new_tour = (4469 """4470 // DriverJS Tour4471 var tour = new Driver({4472 opacity: 0.24, // Background opacity (0: no popover / overlay)4473 padding: 6, // Distance of element from around the edges4474 allowClose: false, // Whether clicking on overlay should close4475 overlayClickNext: false, // Move to next step on overlay click4476 doneBtnText: 'Done', // Text that appears on the Done button4477 closeBtnText: 'Close', // Text appearing on the Close button4478 nextBtnText: 'Next', // Text that appears on the Next button4479 prevBtnText: 'Previous', // Text appearing on Previous button4480 showButtons: true, // This shows control buttons in the footer4481 keyboardControl: true, // (escape to close, arrow keys to move)4482 animate: true, // Animate while changing highlighted element4483 });4484 tour.defineSteps([4485 """)4486 self._tour_steps[name] = []4487 self._tour_steps[name].append(new_tour)4488 def create_hopscotch_tour(self, name=None):4489 """ Creates a Hopscotch tour for a website.4490 @Params4491 name - If creating multiple tours at the same time,4492 use this to select the tour you wish to add steps to.4493 """4494 if not name:4495 name = "default"4496 new_tour = (4497 """4498 // Hopscotch Tour4499 var tour = {4500 id: "hopscotch_tour",4501 steps: [4502 """)4503 self._tour_steps[name] = []4504 self._tour_steps[name].append(new_tour)4505 def create_introjs_tour(self, name=None):4506 """ Creates an IntroJS tour for a website.4507 @Params4508 name - If creating multiple tours at the same time,4509 use this to select the tour you wish to add steps to.4510 """4511 if not name:4512 name = "default"4513 new_tour = (4514 """4515 // IntroJS Tour4516 function startIntro(){4517 var intro = introJs();4518 intro.setOptions({4519 steps: [4520 """)4521 self._tour_steps[name] = []4522 self._tour_steps[name].append(new_tour)4523 def add_tour_step(self, message, selector=None, name=None,4524 title=None, theme=None, alignment=None, duration=None):4525 """ Allows the user to add tour steps for a website.4526 @Params4527 message - The message to display.4528 selector - The CSS Selector of the Element to attach to.4529 name - If creating multiple tours at the same time,4530 use this to select the tour you wish to add steps to.4531 title - Additional header text that appears above the message.4532 theme - (NON-Bootstrap Tours ONLY) The styling of the tour step.4533 Choose from "light"/"arrows", "dark", "default", "square",4534 and "square-dark". ("arrows" is used if None is selected.)4535 alignment - Choose from "top", "bottom", "left", and "right".4536 ("top" is default, except for Hopscotch and DriverJS).4537 duration - (Bootstrap Tours ONLY) The amount of time, in seconds,4538 before automatically advancing to the next tour step.4539 """4540 if not selector:4541 selector = "html"4542 if page_utils.is_name_selector(selector):4543 name = page_utils.get_name_from_selector(selector)4544 selector = '[name="%s"]' % name4545 if page_utils.is_xpath_selector(selector):4546 selector = self.convert_to_css_selector(selector, By.XPATH)4547 selector = self.__escape_quotes_if_needed(selector)4548 if not name:4549 name = "default"4550 if name not in self._tour_steps:4551 # By default, will create an IntroJS tour if no tours exist4552 self.create_tour(name=name, theme="introjs")4553 if not title:4554 title = ""4555 title = self.__escape_quotes_if_needed(title)4556 if message:4557 message = self.__escape_quotes_if_needed(message)4558 else:4559 message = ""4560 if not alignment or (4561 alignment not in ["top", "bottom", "left", "right"]):4562 t_name = self._tour_steps[name][0]4563 if "Hopscotch" not in t_name and "DriverJS" not in t_name:4564 alignment = "top"4565 else:4566 alignment = "bottom"4567 if "Bootstrap" in self._tour_steps[name][0]:4568 self.__add_bootstrap_tour_step(4569 message, selector=selector, name=name, title=title,4570 alignment=alignment, duration=duration)4571 elif "DriverJS" in self._tour_steps[name][0]:4572 self.__add_driverjs_tour_step(4573 message, selector=selector, name=name, title=title,4574 alignment=alignment)4575 elif "Hopscotch" in self._tour_steps[name][0]:4576 self.__add_hopscotch_tour_step(4577 message, selector=selector, name=name, title=title,4578 alignment=alignment)4579 elif "IntroJS" in self._tour_steps[name][0]:4580 self.__add_introjs_tour_step(4581 message, selector=selector, name=name, title=title,4582 alignment=alignment)4583 else:4584 self.__add_shepherd_tour_step(4585 message, selector=selector, name=name, title=title,4586 theme=theme, alignment=alignment)4587 def __add_shepherd_tour_step(self, message, selector=None, name=None,4588 title=None, theme=None, alignment=None):4589 """ Allows the user to add tour steps for a website.4590 @Params4591 message - The message to display.4592 selector - The CSS Selector of the Element to attach to.4593 name - If creating multiple tours at the same time,4594 use this to select the tour you wish to add steps to.4595 title - Additional header text that appears above the message.4596 theme - (NON-Bootstrap Tours ONLY) The styling of the tour step.4597 Choose from "light"/"arrows", "dark", "default", "square",4598 and "square-dark". ("arrows" is used if None is selected.)4599 alignment - Choose from "top", "bottom", "left", and "right".4600 ("top" is the default alignment).4601 """4602 if theme == "default":4603 shepherd_theme = "shepherd-theme-default"4604 elif theme == "dark":4605 shepherd_theme = "shepherd-theme-dark"4606 elif theme == "light":4607 shepherd_theme = "shepherd-theme-arrows"4608 elif theme == "arrows":4609 shepherd_theme = "shepherd-theme-arrows"4610 elif theme == "square":4611 shepherd_theme = "shepherd-theme-square"4612 elif theme == "square-dark":4613 shepherd_theme = "shepherd-theme-square-dark"4614 else:4615 shepherd_base_theme = re.search(4616 r"[\S\s]+classes: '([\S\s]+)',[\S\s]+",4617 self._tour_steps[name][0]).group(1)4618 shepherd_theme = shepherd_base_theme4619 shepherd_classes = shepherd_theme4620 if selector == "html":4621 shepherd_classes += " shepherd-orphan"4622 buttons = "firstStepButtons"4623 if len(self._tour_steps[name]) > 1:4624 buttons = "midTourButtons"4625 step = ("""4626 tour.addStep('%s', {4627 title: '%s',4628 classes: '%s',4629 text: '%s',4630 attachTo: {element: '%s', on: '%s'},4631 buttons: %s,4632 advanceOn: '.docs-link click'4633 });""" % (4634 name, title, shepherd_classes, message, selector, alignment,4635 buttons))4636 self._tour_steps[name].append(step)4637 def __add_bootstrap_tour_step(self, message, selector=None, name=None,4638 title=None, alignment=None, duration=None):4639 """ Allows the user to add tour steps for a website.4640 @Params4641 message - The message to display.4642 selector - The CSS Selector of the Element to attach to.4643 name - If creating multiple tours at the same time,4644 use this to select the tour you wish to add steps to.4645 title - Additional header text that appears above the message.4646 alignment - Choose from "top", "bottom", "left", and "right".4647 ("top" is the default alignment).4648 duration - (Bootstrap Tours ONLY) The amount of time, in seconds,4649 before automatically advancing to the next tour step.4650 """4651 if selector != "html":4652 selector = self.__make_css_match_first_element_only(selector)4653 element_row = "element: '%s'," % selector4654 else:4655 element_row = ""4656 if not duration:4657 duration = "0"4658 else:4659 duration = str(float(duration) * 1000.0)4660 step = ("""{4661 %s4662 title: '%s',4663 content: '%s',4664 orphan: true,4665 placement: 'auto %s',4666 smartPlacement: true,4667 duration: %s,4668 },""" % (element_row, title, message, alignment, duration))4669 self._tour_steps[name].append(step)4670 def __add_driverjs_tour_step(self, message, selector=None, name=None,4671 title=None, alignment=None):4672 """ Allows the user to add tour steps for a website.4673 @Params4674 message - The message to display.4675 selector - The CSS Selector of the Element to attach to.4676 name - If creating multiple tours at the same time,4677 use this to select the tour you wish to add steps to.4678 title - Additional header text that appears above the message.4679 alignment - Choose from "top", "bottom", "left", and "right".4680 ("top" is the default alignment).4681 """4682 message = (4683 '<font size=\"3\" color=\"#33477B\"><b>' + message + '</b></font>')4684 title_row = ""4685 if not title:4686 title_row = "title: '%s'," % message4687 message = ""4688 else:4689 title_row = "title: '%s'," % title4690 align_row = "position: '%s'," % alignment4691 ani_row = "animate: true,"4692 if not selector or selector == "html" or selector == "body":4693 selector = "body"4694 ani_row = "animate: false,"4695 align_row = "position: '%s'," % "mid-center"4696 element_row = "element: '%s'," % selector4697 desc_row = "description: '%s'," % message4698 step = ("""{4699 %s4700 %s4701 popover: {4702 className: 'popover-class',4703 %s4704 %s4705 %s4706 }4707 },""" % (element_row, ani_row, title_row, desc_row, align_row))4708 self._tour_steps[name].append(step)4709 def __add_hopscotch_tour_step(self, message, selector=None, name=None,4710 title=None, alignment=None):4711 """ Allows the user to add tour steps for a website.4712 @Params4713 message - The message to display.4714 selector - The CSS Selector of the Element to attach to.4715 name - If creating multiple tours at the same time,4716 use this to select the tour you wish to add steps to.4717 title - Additional header text that appears above the message.4718 alignment - Choose from "top", "bottom", "left", and "right".4719 ("bottom" is the default alignment).4720 """4721 arrow_offset_row = None4722 if not selector or selector == "html":4723 selector = "head"4724 alignment = "bottom"4725 arrow_offset_row = "arrowOffset: '200',"4726 else:4727 arrow_offset_row = ""4728 step = ("""{4729 target: '%s',4730 title: '%s',4731 content: '%s',4732 %s4733 showPrevButton: 'true',4734 scrollDuration: '550',4735 placement: '%s'},4736 """ % (selector, title, message, arrow_offset_row, alignment))4737 self._tour_steps[name].append(step)4738 def __add_introjs_tour_step(self, message, selector=None, name=None,4739 title=None, alignment=None):4740 """ Allows the user to add tour steps for a website.4741 @Params4742 message - The message to display.4743 selector - The CSS Selector of the Element to attach to.4744 name - If creating multiple tours at the same time,4745 use this to select the tour you wish to add steps to.4746 title - Additional header text that appears above the message.4747 alignment - Choose from "top", "bottom", "left", and "right".4748 ("top" is the default alignment).4749 """4750 if selector != "html":4751 element_row = "element: '%s'," % selector4752 else:4753 element_row = ""4754 if title:4755 message = "<center><b>" + title + "</b></center><hr>" + message4756 message = '<font size=\"3\" color=\"#33477B\">' + message + '</font>'4757 step = ("""{%s4758 intro: '%s',4759 position: '%s'},4760 """ % (element_row, message, alignment))4761 self._tour_steps[name].append(step)4762 def play_tour(self, name=None, interval=0):4763 """ Plays a tour on the current website.4764 @Params4765 name - If creating multiple tours at the same time,4766 use this to select the tour you wish to add steps to.4767 interval - The delay time between autoplaying tour steps. (Seconds)4768 If set to 0 (default), the tour is fully manual control.4769 """4770 if self.headless:4771 return # Tours should not run in headless mode.4772 if not name:4773 name = "default"4774 if name not in self._tour_steps:4775 raise Exception("Tour {%s} does not exist!" % name)4776 if "Bootstrap" in self._tour_steps[name][0]:4777 tour_helper.play_bootstrap_tour(4778 self.driver, self._tour_steps, self.browser,4779 self.message_duration, name=name, interval=interval)4780 elif "DriverJS" in self._tour_steps[name][0]:4781 tour_helper.play_driverjs_tour(4782 self.driver, self._tour_steps, self.browser,4783 self.message_duration, name=name, interval=interval)4784 elif "Hopscotch" in self._tour_steps[name][0]:4785 tour_helper.play_hopscotch_tour(4786 self.driver, self._tour_steps, self.browser,4787 self.message_duration, name=name, interval=interval)4788 elif "IntroJS" in self._tour_steps[name][0]:4789 tour_helper.play_introjs_tour(4790 self.driver, self._tour_steps, self.browser,4791 self.message_duration, name=name, interval=interval)4792 else:4793 # "Shepherd"4794 tour_helper.play_shepherd_tour(4795 self.driver, self._tour_steps,4796 self.message_duration, name=name, interval=interval)4797 def export_tour(self, name=None, filename="my_tour.js", url=None):4798 """ Exports a tour as a JS file.4799 You can call self.export_tour() anywhere where you would4800 normally use self.play_tour() to play a website tour.4801 It will include necessary resources as well, such as jQuery.4802 You'll be able to copy the tour directly into the Console of4803 any web browser to play the tour outside of SeleniumBase runs.4804 @Params4805 name - If creating multiple tours at the same time,4806 use this to select the tour you wish to add steps to.4807 filename - The name of the JavaScript file that you wish to4808 save the tour to.4809 url - The URL where the tour starts. If not specified, the URL4810 of the current page will be used. """4811 if not url:4812 url = self.get_current_url()4813 tour_helper.export_tour(4814 self._tour_steps, name=name, filename=filename, url=url)4815 def activate_jquery_confirm(self):4816 """ See https://craftpip.github.io/jquery-confirm/ for usage. """4817 self.__check_scope()4818 js_utils.activate_jquery_confirm(self.driver)4819 self.wait_for_ready_state_complete()4820 def activate_messenger(self):4821 self.__check_scope()4822 js_utils.activate_messenger(self.driver)4823 self.wait_for_ready_state_complete()4824 def set_messenger_theme(self, theme="default", location="default",4825 max_messages="default"):4826 """ Sets a theme for posting messages.4827 Themes: ["flat", "future", "block", "air", "ice"]4828 Locations: ["top_left", "top_center", "top_right",4829 "bottom_left", "bottom_center", "bottom_right"]4830 max_messages is the limit of concurrent messages to display. """4831 self.__check_scope()4832 if not theme:4833 theme = "default" # "flat"4834 if not location:4835 location = "default" # "bottom_right"4836 if not max_messages:4837 max_messages = "default" # "8"4838 else:4839 max_messages = str(max_messages) # Value must be in string format4840 js_utils.set_messenger_theme(4841 self.driver, theme=theme,4842 location=location, max_messages=max_messages)4843 def post_message(self, message, duration=None, pause=True, style="info"):4844 """ Post a message on the screen with Messenger.4845 Arguments:4846 message: The message to display.4847 duration: The time until the message vanishes. (Default: 2.55s)4848 pause: If True, the program waits until the message completes.4849 style: "info", "success", or "error".4850 You can also post messages by using =>4851 self.execute_script('Messenger().post("My Message")')4852 """4853 self.__check_scope()4854 if style not in ["info", "success", "error"]:4855 style = "info"4856 if not duration:4857 if not self.message_duration:4858 duration = settings.DEFAULT_MESSAGE_DURATION4859 else:4860 duration = self.message_duration4861 try:4862 js_utils.post_message(self.driver, message, duration, style=style)4863 except Exception:4864 print(" * %s message: %s" % (style.upper(), message))4865 if pause:4866 duration = float(duration) + 0.154867 time.sleep(float(duration))4868 def post_message_and_highlight(4869 self, message, selector, by=By.CSS_SELECTOR):4870 """ Post a message on the screen and highlight an element.4871 Arguments:4872 message: The message to display.4873 selector: The selector of the Element to highlight.4874 by: The type of selector to search by. (Default: CSS Selector)4875 """4876 self.__check_scope()4877 self.__highlight_with_assert_success(message, selector, by=by)4878 def post_success_message(self, message, duration=None, pause=True):4879 """ Post a success message on the screen with Messenger.4880 Arguments:4881 message: The success message to display.4882 duration: The time until the message vanishes. (Default: 2.55s)4883 pause: If True, the program waits until the message completes.4884 """4885 self.__check_scope()4886 if not duration:4887 if not self.message_duration:4888 duration = settings.DEFAULT_MESSAGE_DURATION4889 else:4890 duration = self.message_duration4891 try:4892 js_utils.post_message(4893 self.driver, message, duration, style="success")4894 except Exception:4895 print(" * SUCCESS message: %s" % message)4896 if pause:4897 duration = float(duration) + 0.154898 time.sleep(float(duration))4899 def post_error_message(self, message, duration=None, pause=True):4900 """ Post an error message on the screen with Messenger.4901 Arguments:4902 message: The error message to display.4903 duration: The time until the message vanishes. (Default: 2.55s)4904 pause: If True, the program waits until the message completes.4905 """4906 self.__check_scope()4907 if not duration:4908 if not self.message_duration:4909 duration = settings.DEFAULT_MESSAGE_DURATION4910 else:4911 duration = self.message_duration4912 try:4913 js_utils.post_message(4914 self.driver, message, duration, style="error")4915 except Exception:4916 print(" * ERROR message: %s" % message)4917 if pause:4918 duration = float(duration) + 0.154919 time.sleep(float(duration))4920 ############4921 def generate_referral(self, start_page, destination_page):4922 """ This method opens the start_page, creates a referral link there,4923 and clicks on that link, which goes to the destination_page.4924 (This generates real traffic for testing analytics software.) """4925 self.__check_scope()4926 if not page_utils.is_valid_url(destination_page):4927 raise Exception(4928 "Exception: destination_page {%s} is not a valid URL!"4929 % destination_page)4930 if start_page:4931 if not page_utils.is_valid_url(start_page):4932 raise Exception(4933 "Exception: start_page {%s} is not a valid URL! "4934 "(Use an empty string or None to start from current page.)"4935 % start_page)4936 self.open(start_page)4937 time.sleep(0.08)4938 self.wait_for_ready_state_complete()4939 referral_link = ('''<body>'''4940 '''<a class='analytics referral test' href='%s' '''4941 '''style='font-family: Arial,sans-serif; '''4942 '''font-size: 30px; color: #18a2cd'>'''4943 '''Magic Link Button</a></body>''' % destination_page)4944 self.execute_script(4945 '''document.body.outerHTML = \"%s\"''' % referral_link)4946 self.click(4947 "a.analytics.referral.test", timeout=2) # Clicks generated button4948 time.sleep(0.15)4949 try:4950 self.click("html")4951 time.sleep(0.08)4952 except Exception:4953 pass4954 def generate_traffic(self, start_page, destination_page, loops=1):4955 """ Similar to generate_referral(), but can do multiple loops. """4956 self.__check_scope()4957 for loop in range(loops):4958 self.generate_referral(start_page, destination_page)4959 time.sleep(0.05)4960 def generate_referral_chain(self, pages):4961 """ Use this method to chain the action of creating button links on4962 one website page that will take you to the next page.4963 (When you want to create a referral to a website for traffic4964 generation without increasing the bounce rate, you'll want to visit4965 at least one additional page on that site with a button click.) """4966 self.__check_scope()4967 if not type(pages) is tuple and not type(pages) is list:4968 raise Exception(4969 "Exception: Expecting a list of website pages for chaining!")4970 if len(pages) < 2:4971 raise Exception(4972 "Exception: At least two website pages required for chaining!")4973 for page in pages:4974 # Find out if any of the web pages are invalid before continuing4975 if not page_utils.is_valid_url(page):4976 raise Exception(4977 "Exception: Website page {%s} is not a valid URL!" % page)4978 for page in pages:4979 self.generate_referral(None, page)4980 def generate_traffic_chain(self, pages, loops=1):4981 """ Similar to generate_referral_chain(), but for multiple loops. """4982 self.__check_scope()4983 for loop in range(loops):4984 self.generate_referral_chain(pages)4985 time.sleep(0.05)4986 ############4987 def wait_for_element_present(self, selector, by=By.CSS_SELECTOR,4988 timeout=None):4989 """ Waits for an element to appear in the HTML of a page.4990 The element does not need be visible (it may be hidden). """4991 self.__check_scope()4992 if not timeout:4993 timeout = settings.LARGE_TIMEOUT4994 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:4995 timeout = self.__get_new_timeout(timeout)4996 selector, by = self.__recalculate_selector(selector, by)4997 return page_actions.wait_for_element_present(4998 self.driver, selector, by, timeout)4999 def wait_for_element(self, selector, by=By.CSS_SELECTOR, timeout=None):5000 """ Waits for an element to appear in the HTML of a page.5001 The element must be visible (it cannot be hidden). """5002 self.__check_scope()5003 if not timeout:5004 timeout = settings.LARGE_TIMEOUT5005 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5006 timeout = self.__get_new_timeout(timeout)5007 selector, by = self.__recalculate_selector(selector, by)5008 return page_actions.wait_for_element_visible(5009 self.driver, selector, by, timeout)5010 def get_element(self, selector, by=By.CSS_SELECTOR, timeout=None):5011 """ Same as wait_for_element_present() - returns the element.5012 The element does not need be visible (it may be hidden). """5013 self.__check_scope()5014 if not timeout:5015 timeout = settings.SMALL_TIMEOUT5016 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5017 timeout = self.__get_new_timeout(timeout)5018 selector, by = self.__recalculate_selector(selector, by)5019 return self.wait_for_element_present(selector, by=by, timeout=timeout)5020 def assert_element_present(self, selector, by=By.CSS_SELECTOR,5021 timeout=None):5022 """ Similar to wait_for_element_present(), but returns nothing.5023 Waits for an element to appear in the HTML of a page.5024 The element does not need be visible (it may be hidden).5025 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5026 self.__check_scope()5027 if not timeout:5028 timeout = settings.SMALL_TIMEOUT5029 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5030 timeout = self.__get_new_timeout(timeout)5031 self.wait_for_element_present(selector, by=by, timeout=timeout)5032 return True5033 def find_element(self, selector, by=By.CSS_SELECTOR, timeout=None):5034 """ Same as wait_for_element_visible() - returns the element """5035 self.__check_scope()5036 if not timeout:5037 timeout = settings.LARGE_TIMEOUT5038 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5039 timeout = self.__get_new_timeout(timeout)5040 return self.wait_for_element_visible(selector, by=by, timeout=timeout)5041 def assert_element(self, selector, by=By.CSS_SELECTOR, timeout=None):5042 """ Similar to wait_for_element_visible(), but returns nothing.5043 As above, will raise an exception if nothing can be found.5044 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5045 self.__check_scope()5046 if not timeout:5047 timeout = settings.SMALL_TIMEOUT5048 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5049 timeout = self.__get_new_timeout(timeout)5050 self.wait_for_element_visible(selector, by=by, timeout=timeout)5051 if self.demo_mode:5052 selector, by = self.__recalculate_selector(selector, by)5053 a_t = "ASSERT"5054 if self._language != "English":5055 from seleniumbase.fixtures.words import SD5056 a_t = SD.translate_assert(self._language)5057 messenger_post = "%s %s: %s" % (a_t, by.upper(), selector)5058 self.__highlight_with_assert_success(messenger_post, selector, by)5059 return True5060 def assert_element_visible(self, selector, by=By.CSS_SELECTOR,5061 timeout=None):5062 """ Same as self.assert_element()5063 As above, will raise an exception if nothing can be found. """5064 self.__check_scope()5065 if not timeout:5066 timeout = settings.SMALL_TIMEOUT5067 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5068 timeout = self.__get_new_timeout(timeout)5069 self.assert_element(selector, by=by, timeout=timeout)5070 return True5071 ############5072 def wait_for_text_visible(self, text, selector="html", by=By.CSS_SELECTOR,5073 timeout=None):5074 self.__check_scope()5075 if not timeout:5076 timeout = settings.LARGE_TIMEOUT5077 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5078 timeout = self.__get_new_timeout(timeout)5079 selector, by = self.__recalculate_selector(selector, by)5080 return page_actions.wait_for_text_visible(5081 self.driver, text, selector, by, timeout)5082 def wait_for_exact_text_visible(self, text, selector="html",5083 by=By.CSS_SELECTOR,5084 timeout=None):5085 self.__check_scope()5086 if not timeout:5087 timeout = settings.LARGE_TIMEOUT5088 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5089 timeout = self.__get_new_timeout(timeout)5090 selector, by = self.__recalculate_selector(selector, by)5091 return page_actions.wait_for_exact_text_visible(5092 self.driver, text, selector, by, timeout)5093 def wait_for_text(self, text, selector="html", by=By.CSS_SELECTOR,5094 timeout=None):5095 """ The shorter version of wait_for_text_visible() """5096 self.__check_scope()5097 if not timeout:5098 timeout = settings.LARGE_TIMEOUT5099 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5100 timeout = self.__get_new_timeout(timeout)5101 return self.wait_for_text_visible(5102 text, selector, by=by, timeout=timeout)5103 def find_text(self, text, selector="html", by=By.CSS_SELECTOR,5104 timeout=None):5105 """ Same as wait_for_text_visible() - returns the element """5106 self.__check_scope()5107 if not timeout:5108 timeout = settings.LARGE_TIMEOUT5109 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5110 timeout = self.__get_new_timeout(timeout)5111 return self.wait_for_text_visible(5112 text, selector, by=by, timeout=timeout)5113 def assert_text_visible(self, text, selector="html", by=By.CSS_SELECTOR,5114 timeout=None):5115 """ Same as assert_text() """5116 self.__check_scope()5117 if not timeout:5118 timeout = settings.SMALL_TIMEOUT5119 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5120 timeout = self.__get_new_timeout(timeout)5121 return self.assert_text(text, selector, by=by, timeout=timeout)5122 def assert_text(self, text, selector="html", by=By.CSS_SELECTOR,5123 timeout=None):5124 """ Similar to wait_for_text_visible()5125 Raises an exception if the element or the text is not found.5126 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5127 self.__check_scope()5128 if not timeout:5129 timeout = settings.SMALL_TIMEOUT5130 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5131 timeout = self.__get_new_timeout(timeout)5132 self.wait_for_text_visible(text, selector, by=by, timeout=timeout)5133 if self.demo_mode:5134 selector, by = self.__recalculate_selector(selector, by)5135 a_t = "ASSERT TEXT"5136 i_n = "in"5137 if self._language != "English":5138 from seleniumbase.fixtures.words import SD5139 a_t = SD.translate_assert_text(self._language)5140 i_n = SD.translate_in(self._language)5141 messenger_post = ("%s: {%s} %s %s: %s"5142 % (a_t, text, i_n, by.upper(), selector))5143 self.__highlight_with_assert_success(messenger_post, selector, by)5144 return True5145 def assert_exact_text(self, text, selector="html", by=By.CSS_SELECTOR,5146 timeout=None):5147 """ Similar to assert_text(), but the text must be exact, rather than5148 exist as a subset of the full text.5149 (Extra whitespace at the beginning or the end doesn't count.)5150 Raises an exception if the element or the text is not found.5151 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5152 self.__check_scope()5153 if not timeout:5154 timeout = settings.SMALL_TIMEOUT5155 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5156 timeout = self.__get_new_timeout(timeout)5157 self.wait_for_exact_text_visible(5158 text, selector, by=by, timeout=timeout)5159 if self.demo_mode:5160 selector, by = self.__recalculate_selector(selector, by)5161 a_t = "ASSERT EXACT TEXT"5162 i_n = "in"5163 if self._language != "English":5164 from seleniumbase.fixtures.words import SD5165 a_t = SD.translate_assert_exact_text(self._language)5166 i_n = SD.translate_in(self._language)5167 messenger_post = ("%s: {%s} %s %s: %s"5168 % (a_t, text, i_n, by.upper(), selector))5169 self.__highlight_with_assert_success(messenger_post, selector, by)5170 return True5171 ############5172 def wait_for_link_text_present(self, link_text, timeout=None):5173 self.__check_scope()5174 if not timeout:5175 timeout = settings.SMALL_TIMEOUT5176 start_ms = time.time() * 1000.05177 stop_ms = start_ms + (timeout * 1000.0)5178 for x in range(int(timeout * 5)):5179 shared_utils.check_if_time_limit_exceeded()5180 try:5181 if not self.is_link_text_present(link_text):5182 raise Exception(5183 "Link text {%s} was not found!" % link_text)5184 return5185 except Exception:5186 now_ms = time.time() * 1000.05187 if now_ms >= stop_ms:5188 break5189 time.sleep(0.2)5190 message = (5191 "Link text {%s} was not present after %s seconds!"5192 "" % (link_text, timeout))5193 page_actions.timeout_exception("NoSuchElementException", message)5194 def wait_for_partial_link_text_present(self, link_text, timeout=None):5195 self.__check_scope()5196 if not timeout:5197 timeout = settings.SMALL_TIMEOUT5198 start_ms = time.time() * 1000.05199 stop_ms = start_ms + (timeout * 1000.0)5200 for x in range(int(timeout * 5)):5201 shared_utils.check_if_time_limit_exceeded()5202 try:5203 if not self.is_partial_link_text_present(link_text):5204 raise Exception(5205 "Partial Link text {%s} was not found!" % link_text)5206 return5207 except Exception:5208 now_ms = time.time() * 1000.05209 if now_ms >= stop_ms:5210 break5211 time.sleep(0.2)5212 message = (5213 "Partial Link text {%s} was not present after %s seconds!"5214 "" % (link_text, timeout))5215 page_actions.timeout_exception("NoSuchElementException", message)5216 def wait_for_link_text_visible(self, link_text, timeout=None):5217 self.__check_scope()5218 if not timeout:5219 timeout = settings.LARGE_TIMEOUT5220 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5221 timeout = self.__get_new_timeout(timeout)5222 return self.wait_for_element_visible(5223 link_text, by=By.LINK_TEXT, timeout=timeout)5224 def wait_for_link_text(self, link_text, timeout=None):5225 """ The shorter version of wait_for_link_text_visible() """5226 self.__check_scope()5227 if not timeout:5228 timeout = settings.LARGE_TIMEOUT5229 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5230 timeout = self.__get_new_timeout(timeout)5231 return self.wait_for_link_text_visible(link_text, timeout=timeout)5232 def find_link_text(self, link_text, timeout=None):5233 """ Same as wait_for_link_text_visible() - returns the element """5234 self.__check_scope()5235 if not timeout:5236 timeout = settings.LARGE_TIMEOUT5237 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5238 timeout = self.__get_new_timeout(timeout)5239 return self.wait_for_link_text_visible(link_text, timeout=timeout)5240 def assert_link_text(self, link_text, timeout=None):5241 """ Similar to wait_for_link_text_visible(), but returns nothing.5242 As above, will raise an exception if nothing can be found.5243 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5244 self.__check_scope()5245 if not timeout:5246 timeout = settings.SMALL_TIMEOUT5247 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5248 timeout = self.__get_new_timeout(timeout)5249 self.wait_for_link_text_visible(link_text, timeout=timeout)5250 if self.demo_mode:5251 a_t = "ASSERT LINK TEXT"5252 if self._language != "English":5253 from seleniumbase.fixtures.words import SD5254 a_t = SD.translate_assert_link_text(self._language)5255 messenger_post = ("%s: {%s}" % (a_t, link_text))5256 self.__highlight_with_assert_success(5257 messenger_post, link_text, by=By.LINK_TEXT)5258 return True5259 def wait_for_partial_link_text(self, partial_link_text, timeout=None):5260 self.__check_scope()5261 if not timeout:5262 timeout = settings.LARGE_TIMEOUT5263 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5264 timeout = self.__get_new_timeout(timeout)5265 return self.wait_for_element_visible(5266 partial_link_text, by=By.PARTIAL_LINK_TEXT, timeout=timeout)5267 def find_partial_link_text(self, partial_link_text, timeout=None):5268 """ Same as wait_for_partial_link_text() - returns the element """5269 self.__check_scope()5270 if not timeout:5271 timeout = settings.LARGE_TIMEOUT5272 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5273 timeout = self.__get_new_timeout(timeout)5274 return self.wait_for_partial_link_text(5275 partial_link_text, timeout=timeout)5276 def assert_partial_link_text(self, partial_link_text, timeout=None):5277 """ Similar to wait_for_partial_link_text(), but returns nothing.5278 As above, will raise an exception if nothing can be found.5279 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5280 self.__check_scope()5281 if not timeout:5282 timeout = settings.SMALL_TIMEOUT5283 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5284 timeout = self.__get_new_timeout(timeout)5285 self.wait_for_partial_link_text(partial_link_text, timeout=timeout)5286 if self.demo_mode:5287 a_t = "ASSERT PARTIAL LINK TEXT"5288 if self._language != "English":5289 from seleniumbase.fixtures.words import SD5290 a_t = SD.translate_assert_link_text(self._language)5291 messenger_post = ("%s: {%s}" % (a_t, partial_link_text))5292 self.__highlight_with_assert_success(5293 messenger_post, partial_link_text, by=By.PARTIAL_LINK_TEXT)5294 return True5295 ############5296 def wait_for_element_absent(self, selector, by=By.CSS_SELECTOR,5297 timeout=None):5298 """ Waits for an element to no longer appear in the HTML of a page.5299 A hidden element still counts as appearing in the page HTML.5300 If an element with "hidden" status is acceptable,5301 use wait_for_element_not_visible() instead. """5302 self.__check_scope()5303 if not timeout:5304 timeout = settings.LARGE_TIMEOUT5305 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5306 timeout = self.__get_new_timeout(timeout)5307 selector, by = self.__recalculate_selector(selector, by)5308 return page_actions.wait_for_element_absent(5309 self.driver, selector, by, timeout)5310 def assert_element_absent(self, selector, by=By.CSS_SELECTOR,5311 timeout=None):5312 """ Similar to wait_for_element_absent() - returns nothing.5313 As above, will raise an exception if the element stays present.5314 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5315 self.__check_scope()5316 if not timeout:5317 timeout = settings.SMALL_TIMEOUT5318 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5319 timeout = self.__get_new_timeout(timeout)5320 self.wait_for_element_absent(selector, by=by, timeout=timeout)5321 return True5322 ############5323 def wait_for_element_not_visible(self, selector, by=By.CSS_SELECTOR,5324 timeout=None):5325 """ Waits for an element to no longer be visible on a page.5326 The element can be non-existent in the HTML or hidden on the page5327 to qualify as not visible. """5328 self.__check_scope()5329 if not timeout:5330 timeout = settings.LARGE_TIMEOUT5331 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5332 timeout = self.__get_new_timeout(timeout)5333 selector, by = self.__recalculate_selector(selector, by)5334 return page_actions.wait_for_element_not_visible(5335 self.driver, selector, by, timeout)5336 def assert_element_not_visible(self, selector, by=By.CSS_SELECTOR,5337 timeout=None):5338 """ Similar to wait_for_element_not_visible() - returns nothing.5339 As above, will raise an exception if the element stays visible.5340 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5341 self.__check_scope()5342 if not timeout:5343 timeout = settings.SMALL_TIMEOUT5344 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5345 timeout = self.__get_new_timeout(timeout)5346 self.wait_for_element_not_visible(selector, by=by, timeout=timeout)5347 return True5348 ############5349 def wait_for_text_not_visible(self, text, selector="html",5350 by=By.CSS_SELECTOR,5351 timeout=None):5352 self.__check_scope()5353 if not timeout:5354 timeout = settings.LARGE_TIMEOUT5355 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5356 timeout = self.__get_new_timeout(timeout)5357 selector, by = self.__recalculate_selector(selector, by)5358 return page_actions.wait_for_text_not_visible(5359 self.driver, text, selector, by, timeout)5360 def assert_text_not_visible(self, text, selector="html",5361 by=By.CSS_SELECTOR,5362 timeout=None):5363 """ Similar to wait_for_text_not_visible()5364 Raises an exception if the element or the text is not found.5365 Returns True if successful. Default timeout = SMALL_TIMEOUT. """5366 self.__check_scope()5367 if not timeout:5368 timeout = settings.SMALL_TIMEOUT5369 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5370 timeout = self.__get_new_timeout(timeout)5371 self.wait_for_text_not_visible(text, selector, by=by, timeout=timeout)5372 ############5373 def wait_for_and_accept_alert(self, timeout=None):5374 self.__check_scope()5375 if not timeout:5376 timeout = settings.LARGE_TIMEOUT5377 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5378 timeout = self.__get_new_timeout(timeout)5379 return page_actions.wait_for_and_accept_alert(self.driver, timeout)5380 def wait_for_and_dismiss_alert(self, timeout=None):5381 self.__check_scope()5382 if not timeout:5383 timeout = settings.LARGE_TIMEOUT5384 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5385 timeout = self.__get_new_timeout(timeout)5386 return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)5387 def wait_for_and_switch_to_alert(self, timeout=None):5388 self.__check_scope()5389 if not timeout:5390 timeout = settings.LARGE_TIMEOUT5391 if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:5392 timeout = self.__get_new_timeout(timeout)5393 return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)5394 ############5395 def accept_alert(self, timeout=None):5396 """ Same as wait_for_and_accept_alert(), but smaller default T_O """5397 self.__check_scope()5398 if not timeout:5399 timeout = settings.SMALL_TIMEOUT5400 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5401 timeout = self.__get_new_timeout(timeout)5402 return page_actions.wait_for_and_accept_alert(self.driver, timeout)5403 def dismiss_alert(self, timeout=None):5404 """ Same as wait_for_and_dismiss_alert(), but smaller default T_O """5405 self.__check_scope()5406 if not timeout:5407 timeout = settings.SMALL_TIMEOUT5408 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5409 timeout = self.__get_new_timeout(timeout)5410 return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)5411 def switch_to_alert(self, timeout=None):5412 """ Same as wait_for_and_switch_to_alert(), but smaller default T_O """5413 self.__check_scope()5414 if not timeout:5415 timeout = settings.SMALL_TIMEOUT5416 if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:5417 timeout = self.__get_new_timeout(timeout)5418 return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)5419 ############5420 def __assert_eq(self, *args, **kwargs):5421 """ Minified assert_equal() using only the list diff. """5422 minified_exception = None5423 try:5424 self.assertEqual(*args, **kwargs)5425 except Exception as e:5426 str_e = str(e)5427 minified_exception = "\nAssertionError:\n"5428 lines = str_e.split('\n')5429 countdown = 35430 countdown_on = False5431 for line in lines:5432 if countdown_on:5433 minified_exception += line + '\n'5434 countdown = countdown - 15435 if countdown == 0:5436 countdown_on = False5437 elif line.startswith('F'):5438 countdown_on = True5439 countdown = 35440 minified_exception += line + '\n'5441 elif line.startswith('+') or line.startswith('-'):5442 minified_exception += line + '\n'5443 elif line.startswith('?'):5444 minified_exception += line + '\n'5445 elif line.strip().startswith('*'):5446 minified_exception += line + '\n'5447 if minified_exception:5448 raise Exception(minified_exception)5449 def check_window(self, name="default", level=0, baseline=False):5450 """ *** Automated Visual Testing with SeleniumBase ***5451 The first time a test calls self.check_window() for a unique "name"5452 parameter provided, it will set a visual baseline, meaning that it5453 creates a folder, saves the URL to a file, saves the current window5454 screenshot to a file, and creates the following three files5455 with the listed data saved:5456 tags_level1.txt -> HTML tags from the window5457 tags_level2.txt -> HTML tags + attributes from the window5458 tags_level3.txt -> HTML tags + attributes/values from the window5459 Baseline folders are named based on the test name and the name5460 parameter passed to self.check_window(). The same test can store5461 multiple baseline folders.5462 If the baseline is being set/reset, the "level" doesn't matter.5463 After the first run of self.check_window(), it will compare the5464 HTML tags of the latest window to the one from the initial run.5465 Here's how the level system works:5466 * level=0 ->5467 DRY RUN ONLY - Will perform a comparison to the baseline, and5468 print out any differences that are found, but5469 won't fail the test even if differences exist.5470 * level=1 ->5471 HTML tags are compared to tags_level1.txt5472 * level=2 ->5473 HTML tags are compared to tags_level1.txt and5474 HTML tags/attributes are compared to tags_level2.txt5475 * level=3 ->5476 HTML tags are compared to tags_level1.txt and5477 HTML tags + attributes are compared to tags_level2.txt and5478 HTML tags + attributes/values are compared to tags_level3.txt5479 As shown, Level-3 is the most strict, Level-1 is the least strict.5480 If the comparisons from the latest window to the existing baseline5481 don't match, the current test will fail, except for Level-0 tests.5482 You can reset the visual baseline on the command line by using:5483 --visual_baseline5484 As long as "--visual_baseline" is used on the command line while5485 running tests, the self.check_window() method cannot fail because5486 it will rebuild the visual baseline rather than comparing the html5487 tags of the latest run to the existing baseline. If there are any5488 expected layout changes to a website that you're testing, you'll5489 need to reset the baseline to prevent unnecessary failures.5490 self.check_window() will fail with "Page Domain Mismatch Failure"5491 if the page domain doesn't match the domain of the baseline.5492 If you want to use self.check_window() to compare a web page to5493 a later version of itself from within the same test run, you can5494 add the parameter "baseline=True" to the first time you call5495 self.check_window() in a test to use that as the baseline. This5496 only makes sense if you're calling self.check_window() more than5497 once with the same name parameter in the same test.5498 Automated Visual Testing with self.check_window() is not very5499 effective for websites that have dynamic content that changes5500 the layout and structure of web pages. For those, you're much5501 better off using regular SeleniumBase functional testing.5502 Example usage:5503 self.check_window(name="testing", level=0)5504 self.check_window(name="xkcd_home", level=1)5505 self.check_window(name="github_page", level=2)5506 self.check_window(name="wikipedia_page", level=3)5507 """5508 self.__check_scope()5509 if level == "0":5510 level = 05511 if level == "1":5512 level = 15513 if level == "2":5514 level = 25515 if level == "3":5516 level = 35517 if level != 0 and level != 1 and level != 2 and level != 3:5518 raise Exception('Parameter "level" must be set to 0, 1, 2, or 3!')5519 if self.demo_mode:5520 raise Exception(5521 "WARNING: Using Demo Mode will break layout tests "5522 "that use the check_window() method due to custom "5523 "HTML edits being made on the page!\n"5524 "Please rerun without using Demo Mode!")5525 module = self.__class__.__module__5526 if '.' in module and len(module.split('.')[-1]) > 1:5527 module = module.split('.')[-1]5528 test_id = "%s.%s" % (module, self._testMethodName)5529 if not name or len(name) < 1:5530 name = "default"5531 name = str(name)5532 from seleniumbase.core import visual_helper5533 visual_helper.visual_baseline_folder_setup()5534 baseline_dir = constants.VisualBaseline.STORAGE_FOLDER5535 visual_baseline_path = baseline_dir + "/" + test_id + "/" + name5536 page_url_file = visual_baseline_path + "/page_url.txt"5537 screenshot_file = visual_baseline_path + "/screenshot.png"5538 level_1_file = visual_baseline_path + "/tags_level_1.txt"5539 level_2_file = visual_baseline_path + "/tags_level_2.txt"5540 level_3_file = visual_baseline_path + "/tags_level_3.txt"5541 set_baseline = False5542 if baseline or self.visual_baseline:5543 set_baseline = True5544 if not os.path.exists(visual_baseline_path):5545 set_baseline = True5546 try:5547 os.makedirs(visual_baseline_path)5548 except Exception:5549 pass # Only reachable during multi-threaded test runs5550 if not os.path.exists(page_url_file):5551 set_baseline = True5552 if not os.path.exists(screenshot_file):5553 set_baseline = True5554 if not os.path.exists(level_1_file):5555 set_baseline = True5556 if not os.path.exists(level_2_file):5557 set_baseline = True5558 if not os.path.exists(level_3_file):5559 set_baseline = True5560 page_url = self.get_current_url()5561 soup = self.get_beautiful_soup()5562 html_tags = soup.body.find_all()5563 level_1 = [[tag.name] for tag in html_tags]5564 level_1 = json.loads(json.dumps(level_1)) # Tuples become lists5565 level_2 = [[tag.name, sorted(tag.attrs.keys())] for tag in html_tags]5566 level_2 = json.loads(json.dumps(level_2)) # Tuples become lists5567 level_3 = [[tag.name, sorted(tag.attrs.items())] for tag in html_tags]5568 level_3 = json.loads(json.dumps(level_3)) # Tuples become lists5569 if set_baseline:5570 self.save_screenshot("screenshot.png", visual_baseline_path)5571 out_file = codecs.open(page_url_file, "w+", encoding="utf-8")5572 out_file.writelines(page_url)5573 out_file.close()5574 out_file = codecs.open(level_1_file, "w+", encoding="utf-8")5575 out_file.writelines(json.dumps(level_1))5576 out_file.close()5577 out_file = codecs.open(level_2_file, "w+", encoding="utf-8")5578 out_file.writelines(json.dumps(level_2))5579 out_file.close()5580 out_file = codecs.open(level_3_file, "w+", encoding="utf-8")5581 out_file.writelines(json.dumps(level_3))5582 out_file.close()5583 if not set_baseline:5584 f = open(page_url_file, 'r')5585 page_url_data = f.read().strip()5586 f.close()5587 f = open(level_1_file, 'r')5588 level_1_data = json.loads(f.read())5589 f.close()5590 f = open(level_2_file, 'r')5591 level_2_data = json.loads(f.read())5592 f.close()5593 f = open(level_3_file, 'r')5594 level_3_data = json.loads(f.read())5595 f.close()5596 domain_fail = (5597 "\nPage Domain Mismatch Failure: "5598 "Current Page Domain doesn't match the Page Domain of the "5599 "Baseline! Can't compare two completely different sites! "5600 "Run with --visual_baseline to reset the baseline!")5601 level_1_failure = (5602 "\n*\n*** Exception: <Level 1> Visual Diff Failure:\n"5603 "* HTML tags don't match the baseline!")5604 level_2_failure = (5605 "\n*\n*** Exception: <Level 2> Visual Diff Failure:\n"5606 "* HTML tag attribute names don't match the baseline!")5607 level_3_failure = (5608 "\n*\n*** Exception: <Level 3> Visual Diff Failure:\n"5609 "* HTML tag attribute values don't match the baseline!")5610 page_domain = self.get_domain_url(page_url)5611 page_data_domain = self.get_domain_url(page_url_data)5612 unittest.TestCase.maxDiff = 10005613 if level != 0:5614 self.assertEqual(page_data_domain, page_domain, domain_fail)5615 unittest.TestCase.maxDiff = None5616 if level == 3:5617 self.__assert_eq(level_3_data, level_3, level_3_failure)5618 if level == 2:5619 self.__assert_eq(level_2_data, level_2, level_2_failure)5620 unittest.TestCase.maxDiff = 10005621 if level == 1:5622 self.__assert_eq(level_1_data, level_1, level_1_failure)5623 unittest.TestCase.maxDiff = None5624 if level == 0:5625 try:5626 unittest.TestCase.maxDiff = 10005627 self.assertEqual(5628 page_domain, page_data_domain, domain_fail)5629 unittest.TestCase.maxDiff = None5630 self.__assert_eq(level_3_data, level_3, level_3_failure)5631 except Exception as e:5632 print(e) # Level-0 Dry Run (Only print the differences)5633 ############5634 def __get_new_timeout(self, timeout):5635 """ When using --timeout_multiplier=#.# """5636 self.__check_scope()5637 try:5638 timeout_multiplier = float(self.timeout_multiplier)5639 if timeout_multiplier <= 0.5:5640 timeout_multiplier = 0.55641 timeout = int(math.ceil(timeout_multiplier * timeout))5642 return timeout5643 except Exception:5644 # Wrong data type for timeout_multiplier (expecting int or float)5645 return timeout5646 ############5647 def __check_scope(self):5648 if hasattr(self, 'browser'): # self.browser stores the type of browser5649 return # All good: setUp() already initialized variables in "self"5650 else:5651 from seleniumbase.common.exceptions import OutOfScopeException5652 message = (5653 "\n It looks like you are trying to call a SeleniumBase method"5654 "\n from outside the scope of your test class's `self` object."5655 "\n When using page objects, be sure to pass the `self` object"5656 "\n from your test class into your page object methods so that"5657 "\n they can call BaseCase class methods with all the required"5658 "\n variables, which are initialized during the setUp() method"5659 "\n that runs automatically before all tests called by pytest."5660 "\n The `self` object is where all test variables are defined."5661 )5662 raise OutOfScopeException(message)5663 ############5664 def __get_exception_message(self):5665 """ This method extracts the message from an exception if there5666 was an exception that occurred during the test, assuming5667 that the exception was in a try/except block and not thrown. """5668 exception_info = sys.exc_info()[1]5669 if hasattr(exception_info, 'msg'):5670 exc_message = exception_info.msg5671 elif hasattr(exception_info, 'message'):5672 exc_message = exception_info.message5673 else:5674 exc_message = sys.exc_info()5675 return exc_message5676 def __get_improved_exception_message(self):5677 """5678 If Chromedriver is out-of-date, make it clear!5679 Given the high popularity of the following StackOverflow article:5680 https://stackoverflow.com/questions/49162667/unknown-error-5681 call-function-result-missing-value-for-selenium-send-keys-even5682 ... the original error message was not helpful. Tell people directly.5683 (Only expected when using driver.send_keys() with an old Chromedriver.)5684 """5685 exc_message = self.__get_exception_message()5686 maybe_using_old_chromedriver = False5687 if "unknown error: call function result missing" in exc_message:5688 maybe_using_old_chromedriver = True5689 if self.browser == "chrome" and maybe_using_old_chromedriver:5690 update = ("Your version of ChromeDriver may be out-of-date! "5691 "Please go to "5692 "https://sites.google.com/a/chromium.org/chromedriver/ "5693 "and download the latest version to your system PATH! "5694 "Or use: ``seleniumbase install chromedriver`` . "5695 "Original Exception Message: %s" % exc_message)5696 exc_message = update5697 return exc_message5698 def __add_deferred_assert_failure(self):5699 """ Add a deferred_assert failure to a list for future processing. """5700 self.__check_scope()5701 current_url = self.driver.current_url5702 message = self.__get_exception_message()5703 self.__deferred_assert_failures.append(5704 "CHECK #%s: (%s)\n %s" % (5705 self.__deferred_assert_count, current_url, message))5706 ############5707 def deferred_assert_element(self, selector, by=By.CSS_SELECTOR,5708 timeout=None):5709 """ A non-terminating assertion for an element on a page.5710 Failures will be saved until the process_deferred_asserts()5711 method is called from inside a test, likely at the end of it. """5712 self.__check_scope()5713 if not timeout:5714 timeout = settings.MINI_TIMEOUT5715 if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:5716 timeout = self.__get_new_timeout(timeout)5717 self.__deferred_assert_count += 15718 try:5719 url = self.get_current_url()5720 if url == self.__last_url_of_deferred_assert:5721 timeout = 15722 else:5723 self.__last_url_of_deferred_assert = url5724 except Exception:5725 pass5726 try:5727 self.wait_for_element_visible(selector, by=by, timeout=timeout)5728 return True5729 except Exception:5730 self.__add_deferred_assert_failure()5731 return False5732 def deferred_assert_text(self, text, selector="html", by=By.CSS_SELECTOR,5733 timeout=None):5734 """ A non-terminating assertion for text from an element on a page.5735 Failures will be saved until the process_deferred_asserts()5736 method is called from inside a test, likely at the end of it. """5737 self.__check_scope()5738 if not timeout:5739 timeout = settings.MINI_TIMEOUT5740 if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:5741 timeout = self.__get_new_timeout(timeout)5742 self.__deferred_assert_count += 15743 try:5744 url = self.get_current_url()5745 if url == self.__last_url_of_deferred_assert:5746 timeout = 15747 else:5748 self.__last_url_of_deferred_assert = url5749 except Exception:5750 pass5751 try:5752 self.wait_for_text_visible(text, selector, by=by, timeout=timeout)5753 return True5754 except Exception:5755 self.__add_deferred_assert_failure()5756 return False5757 def process_deferred_asserts(self, print_only=False):5758 """ To be used with any test that uses deferred_asserts, which are5759 non-terminating verifications that only raise exceptions5760 after this method is called.5761 This is useful for pages with multiple elements to be checked when5762 you want to find as many bugs as possible in a single test run5763 before having all the exceptions get raised simultaneously.5764 Might be more useful if this method is called after processing all5765 the deferred asserts on a single html page so that the failure5766 screenshot matches the location of the deferred asserts.5767 If "print_only" is set to True, the exception won't get raised. """5768 if self.__deferred_assert_failures:5769 exception_output = ''5770 exception_output += "\n*** DEFERRED ASSERTION FAILURES FROM: "5771 exception_output += "%s\n" % self.id()5772 all_failing_checks = self.__deferred_assert_failures5773 self.__deferred_assert_failures = []5774 for tb in all_failing_checks:5775 exception_output += "%s\n" % tb5776 if print_only:5777 print(exception_output)5778 else:5779 raise Exception(exception_output)5780 ############5781 # Alternate naming scheme for the "deferred_assert" methods.5782 def delayed_assert_element(self, selector, by=By.CSS_SELECTOR,5783 timeout=None):5784 """ Same as self.deferred_assert_element() """5785 return self.deferred_assert_element(5786 selector=selector, by=by, timeout=timeout)5787 def delayed_assert_text(self, text, selector="html", by=By.CSS_SELECTOR,5788 timeout=None):5789 """ Same as self.deferred_assert_text() """5790 return self.deferred_assert_text(5791 text=text, selector=selector, by=by, timeout=timeout)5792 def process_delayed_asserts(self, print_only=False):5793 """ Same as self.process_deferred_asserts() """5794 self.process_deferred_asserts(print_only=print_only)5795 ############5796 def __js_click(self, selector, by=By.CSS_SELECTOR):5797 """ Clicks an element using pure JS. Does not use jQuery. """5798 selector, by = self.__recalculate_selector(selector, by)5799 css_selector = self.convert_to_css_selector(selector, by=by)5800 css_selector = re.escape(css_selector) # Add "\\" to special chars5801 css_selector = self.__escape_quotes_if_needed(css_selector)5802 script = ("""var simulateClick = function (elem) {5803 var evt = new MouseEvent('click', {5804 bubbles: true,5805 cancelable: true,5806 view: window5807 });5808 var canceled = !elem.dispatchEvent(evt);5809 };5810 var someLink = document.querySelector('%s');5811 simulateClick(someLink);"""5812 % css_selector)5813 self.execute_script(script)5814 def __js_click_all(self, selector, by=By.CSS_SELECTOR):5815 """ Clicks all matching elements using pure JS. (No jQuery) """5816 selector, by = self.__recalculate_selector(selector, by)5817 css_selector = self.convert_to_css_selector(selector, by=by)5818 css_selector = re.escape(css_selector) # Add "\\" to special chars5819 css_selector = self.__escape_quotes_if_needed(css_selector)5820 script = ("""var simulateClick = function (elem) {5821 var evt = new MouseEvent('click', {5822 bubbles: true,5823 cancelable: true,5824 view: window5825 });5826 var canceled = !elem.dispatchEvent(evt);5827 };5828 var $elements = document.querySelectorAll('%s');5829 var index = 0, length = $elements.length;5830 for(; index < length; index++){5831 simulateClick($elements[index]);}"""5832 % css_selector)5833 self.execute_script(script)5834 def __jquery_slow_scroll_to(self, selector, by=By.CSS_SELECTOR):5835 selector, by = self.__recalculate_selector(selector, by)5836 element = self.wait_for_element_present(5837 selector, by=by, timeout=settings.SMALL_TIMEOUT)5838 dist = js_utils.get_scroll_distance_to_element(self.driver, element)5839 time_offset = 05840 try:5841 if dist and abs(dist) > SSMD:5842 time_offset = int(float(abs(dist) - SSMD) / 12.5)5843 if time_offset > 950:5844 time_offset = 9505845 except Exception:5846 time_offset = 05847 scroll_time_ms = 550 + time_offset5848 sleep_time = 0.625 + (float(time_offset) / 1000.0)5849 selector = self.convert_to_css_selector(selector, by=by)5850 selector = self.__make_css_match_first_element_only(selector)5851 scroll_script = (5852 """jQuery([document.documentElement, document.body]).animate({5853 scrollTop: jQuery('%s').offset().top - 130}, %s);5854 """ % (selector, scroll_time_ms))5855 if js_utils.is_jquery_activated(self.driver):5856 self.execute_script(scroll_script)5857 else:5858 try:5859 self.safe_execute_script(scroll_script)5860 except Exception:5861 self.__slow_scroll_to_element(element)5862 self.sleep(sleep_time)5863 def __jquery_click(self, selector, by=By.CSS_SELECTOR):5864 """ Clicks an element using jQuery. Different from using pure JS. """5865 selector, by = self.__recalculate_selector(selector, by)5866 self.wait_for_element_present(5867 selector, by=by, timeout=settings.SMALL_TIMEOUT)5868 selector = self.convert_to_css_selector(selector, by=by)5869 selector = self.__make_css_match_first_element_only(selector)5870 click_script = """jQuery('%s')[0].click();""" % selector5871 self.safe_execute_script(click_script)5872 def __get_href_from_link_text(self, link_text, hard_fail=True):5873 href = self.get_link_attribute(link_text, "href", hard_fail)5874 if not href:5875 return None5876 if href.startswith('//'):5877 link = "http:" + href5878 elif href.startswith('/'):5879 url = self.driver.current_url5880 domain_url = self.get_domain_url(url)5881 link = domain_url + href5882 else:5883 link = href5884 return link5885 def __click_dropdown_link_text(self, link_text, link_css):5886 """ When a link may be hidden under a dropdown menu, use this. """5887 soup = self.get_beautiful_soup()5888 drop_down_list = []5889 for item in soup.select('li[class]'):5890 drop_down_list.append(item)5891 csstype = link_css.split('[')[1].split('=')[0]5892 for item in drop_down_list:5893 item_text_list = item.text.split('\n')5894 if link_text in item_text_list and csstype in item.decode():5895 dropdown_css = ""5896 try:5897 for css_class in item['class']:5898 dropdown_css += '.'5899 dropdown_css += css_class5900 except Exception:5901 continue5902 dropdown_css = item.name + dropdown_css5903 matching_dropdowns = self.find_visible_elements(dropdown_css)5904 for dropdown in matching_dropdowns:5905 # The same class names might be used for multiple dropdowns5906 if dropdown.is_displayed():5907 try:5908 try:5909 page_actions.hover_element(5910 self.driver, dropdown)5911 except Exception:5912 # If hovering fails, driver is likely outdated5913 # Time to go directly to the hidden link text5914 self.open(self.__get_href_from_link_text(5915 link_text))5916 return True5917 page_actions.hover_element_and_click(5918 self.driver, dropdown, link_text,5919 click_by=By.LINK_TEXT, timeout=0.12)5920 return True5921 except Exception:5922 pass5923 return False5924 def __get_href_from_partial_link_text(self, link_text, hard_fail=True):5925 href = self.get_partial_link_text_attribute(5926 link_text, "href", hard_fail)5927 if not href:5928 return None5929 if href.startswith('//'):5930 link = "http:" + href5931 elif href.startswith('/'):5932 url = self.driver.current_url5933 domain_url = self.get_domain_url(url)5934 link = domain_url + href5935 else:5936 link = href5937 return link5938 def __click_dropdown_partial_link_text(self, link_text, link_css):5939 """ When a partial link may be hidden under a dropdown, use this. """5940 soup = self.get_beautiful_soup()5941 drop_down_list = []5942 for item in soup.select('li[class]'):5943 drop_down_list.append(item)5944 csstype = link_css.split('[')[1].split('=')[0]5945 for item in drop_down_list:5946 item_text_list = item.text.split('\n')5947 if link_text in item_text_list and csstype in item.decode():5948 dropdown_css = ""5949 try:5950 for css_class in item['class']:5951 dropdown_css += '.'5952 dropdown_css += css_class5953 except Exception:5954 continue5955 dropdown_css = item.name + dropdown_css5956 matching_dropdowns = self.find_visible_elements(dropdown_css)5957 for dropdown in matching_dropdowns:5958 # The same class names might be used for multiple dropdowns5959 if dropdown.is_displayed():5960 try:5961 try:5962 page_actions.hover_element(5963 self.driver, dropdown)5964 except Exception:5965 # If hovering fails, driver is likely outdated5966 # Time to go directly to the hidden link text5967 self.open(5968 self.__get_href_from_partial_link_text(5969 link_text))5970 return True5971 page_actions.hover_element_and_click(5972 self.driver, dropdown, link_text,5973 click_by=By.LINK_TEXT, timeout=0.12)5974 return True5975 except Exception:5976 pass5977 return False5978 def __recalculate_selector(self, selector, by, xp_ok=True):5979 """ Use autodetection to return the correct selector with "by" updated.5980 If "xp_ok" is False, don't call convert_css_to_xpath(), which is5981 used to make the ":contains()" selector valid outside JS calls. """5982 _type = type(selector) # First make sure the selector is a string5983 not_string = False5984 if sys.version_info[0] < 3:5985 if _type is not str and _type is not unicode: # noqa5986 not_string = True5987 else:5988 if _type is not str:5989 not_string = True5990 if not_string:5991 msg = 'Expecting a selector of type: "<class \'str\'>" (string)!'5992 raise Exception('Invalid selector type: "%s"\n%s' % (_type, msg))5993 if page_utils.is_xpath_selector(selector):5994 by = By.XPATH5995 if page_utils.is_link_text_selector(selector):5996 selector = page_utils.get_link_text_from_selector(selector)5997 by = By.LINK_TEXT5998 if page_utils.is_partial_link_text_selector(selector):5999 selector = page_utils.get_partial_link_text_from_selector(selector)6000 by = By.PARTIAL_LINK_TEXT6001 if page_utils.is_name_selector(selector):6002 name = page_utils.get_name_from_selector(selector)6003 selector = '[name="%s"]' % name6004 by = By.CSS_SELECTOR6005 if xp_ok:6006 if ":contains(" in selector and by == By.CSS_SELECTOR:6007 selector = self.convert_css_to_xpath(selector)6008 by = By.XPATH6009 return (selector, by)6010 def __looks_like_a_page_url(self, url):6011 """ Returns True if the url parameter looks like a URL. This method6012 is slightly more lenient than page_utils.is_valid_url(url) due to6013 possible typos when calling self.get(url), which will try to6014 navigate to the page if a URL is detected, but will instead call6015 self.get_element(URL_AS_A_SELECTOR) if the input in not a URL. """6016 if (url.startswith("http:") or url.startswith("https:") or (6017 url.startswith("://") or url.startswith("chrome:") or (6018 url.startswith("about:") or url.startswith("data:") or (6019 url.startswith("file:") or url.startswith("edge:") or (6020 url.startswith("opera:")))))):6021 return True6022 else:6023 return False6024 def __make_css_match_first_element_only(self, selector):6025 # Only get the first match6026 return page_utils.make_css_match_first_element_only(selector)6027 def __demo_mode_pause_if_active(self, tiny=False):6028 if self.demo_mode:6029 wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT6030 if self.demo_sleep:6031 wait_time = float(self.demo_sleep)6032 if not tiny:6033 time.sleep(wait_time)6034 else:6035 time.sleep(wait_time / 3.4)6036 elif self.slow_mode:6037 self.__slow_mode_pause_if_active()6038 def __slow_mode_pause_if_active(self):6039 if self.slow_mode:6040 wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT6041 if self.demo_sleep:6042 wait_time = float(self.demo_sleep)6043 time.sleep(wait_time)6044 def __demo_mode_scroll_if_active(self, selector, by):6045 if self.demo_mode:6046 self.slow_scroll_to(selector, by=by)6047 def __demo_mode_highlight_if_active(self, selector, by):6048 if self.demo_mode:6049 # Includes self.slow_scroll_to(selector, by=by) by default6050 self.highlight(selector, by=by)6051 elif self.slow_mode:6052 # Just do the slow scroll part of the highlight() method6053 self.sleep(0.08)6054 selector, by = self.__recalculate_selector(selector, by)6055 element = self.wait_for_element_visible(6056 selector, by=by, timeout=settings.SMALL_TIMEOUT)6057 try:6058 scroll_distance = js_utils.get_scroll_distance_to_element(6059 self.driver, element)6060 if abs(scroll_distance) > SSMD:6061 self.__jquery_slow_scroll_to(selector, by)6062 else:6063 self.__slow_scroll_to_element(element)6064 except (StaleElementReferenceException, ENI_Exception):6065 self.wait_for_ready_state_complete()6066 time.sleep(0.12)6067 element = self.wait_for_element_visible(6068 selector, by=by, timeout=settings.SMALL_TIMEOUT)6069 self.__slow_scroll_to_element(element)6070 self.sleep(0.12)6071 def __scroll_to_element(self, element, selector=None, by=By.CSS_SELECTOR):6072 success = js_utils.scroll_to_element(self.driver, element)6073 if not success and selector:6074 self.wait_for_ready_state_complete()6075 element = page_actions.wait_for_element_visible(6076 self.driver, selector, by, timeout=settings.SMALL_TIMEOUT)6077 self.__demo_mode_pause_if_active(tiny=True)6078 def __slow_scroll_to_element(self, element):6079 try:6080 js_utils.slow_scroll_to_element(self.driver, element, self.browser)6081 except Exception:6082 # Scroll to the element instantly if the slow scroll fails6083 js_utils.scroll_to_element(self.driver, element)6084 def __highlight_with_assert_success(6085 self, message, selector, by=By.CSS_SELECTOR):6086 selector, by = self.__recalculate_selector(selector, by)6087 element = self.wait_for_element_visible(6088 selector, by=by, timeout=settings.SMALL_TIMEOUT)6089 try:6090 scroll_distance = js_utils.get_scroll_distance_to_element(6091 self.driver, element)6092 if abs(scroll_distance) > SSMD:6093 self.__jquery_slow_scroll_to(selector, by)6094 else:6095 self.__slow_scroll_to_element(element)6096 except Exception:6097 self.wait_for_ready_state_complete()6098 time.sleep(0.12)6099 element = self.wait_for_element_visible(6100 selector, by=by, timeout=settings.SMALL_TIMEOUT)6101 self.__slow_scroll_to_element(element)6102 try:6103 selector = self.convert_to_css_selector(selector, by=by)6104 except Exception:6105 # Don't highlight if can't convert to CSS_SELECTOR6106 return6107 o_bs = '' # original_box_shadow6108 try:6109 style = element.get_attribute('style')6110 except Exception:6111 self.wait_for_ready_state_complete()6112 time.sleep(0.12)6113 element = self.wait_for_element_visible(6114 selector, by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT)6115 style = element.get_attribute('style')6116 if style:6117 if 'box-shadow: ' in style:6118 box_start = style.find('box-shadow: ')6119 box_end = style.find(';', box_start) + 16120 original_box_shadow = style[box_start:box_end]6121 o_bs = original_box_shadow6122 if ":contains" not in selector and ":first" not in selector:6123 selector = re.escape(selector)6124 selector = self.__escape_quotes_if_needed(selector)6125 self.__highlight_with_js_2(message, selector, o_bs)6126 else:6127 selector = self.__make_css_match_first_element_only(selector)6128 selector = re.escape(selector)6129 selector = self.__escape_quotes_if_needed(selector)6130 try:6131 self.__highlight_with_jquery_2(message, selector, o_bs)6132 except Exception:6133 pass # JQuery probably couldn't load. Skip highlighting.6134 time.sleep(0.065)6135 def __highlight_with_js_2(self, message, selector, o_bs):6136 js_utils.highlight_with_js_2(6137 self.driver, message, selector, o_bs, self.message_duration)6138 def __highlight_with_jquery_2(self, message, selector, o_bs):6139 js_utils.highlight_with_jquery_2(6140 self.driver, message, selector, o_bs, self.message_duration)6141 ############6142 # Deprecated Methods (Replace these if they're still in your code!)6143 @decorators.deprecated(6144 "jq_format() is deprecated. Use re.escape() instead!")6145 def jq_format(self, code):6146 # DEPRECATED - re.escape() already performs the intended action!6147 return js_utils._jq_format(code)6148 ############6149 def setUp(self, masterqa_mode=False):6150 """6151 Be careful if a subclass of BaseCase overrides setUp()6152 You'll need to add the following line to the subclass setUp() method:6153 super(SubClassOfBaseCase, self).setUp()6154 """6155 self.masterqa_mode = masterqa_mode6156 self.is_pytest = None6157 try:6158 # This raises an exception if the test is not coming from pytest6159 self.is_pytest = sb_config.is_pytest6160 except Exception:6161 # Not using pytest (probably nosetests)6162 self.is_pytest = False6163 if self.is_pytest:6164 # pytest-specific code6165 test_id = self.__get_test_id()6166 self.browser = sb_config.browser6167 self.data = sb_config.data6168 self.var1 = sb_config.var16169 self.var2 = sb_config.var26170 self.var3 = sb_config.var36171 self.slow_mode = sb_config.slow_mode6172 self.demo_mode = sb_config.demo_mode6173 self.demo_sleep = sb_config.demo_sleep6174 self.highlights = sb_config.highlights6175 self.time_limit = sb_config._time_limit6176 sb_config.time_limit = sb_config._time_limit # Reset between tests6177 self.environment = sb_config.environment6178 self.env = self.environment # Add a shortened version6179 self.with_selenium = sb_config.with_selenium # Should be True6180 self.headless = sb_config.headless6181 self.headless_active = False6182 self.headed = sb_config.headed6183 self.locale_code = sb_config.locale_code6184 self.start_page = sb_config.start_page6185 self.log_path = sb_config.log_path6186 self.with_testing_base = sb_config.with_testing_base6187 self.with_basic_test_info = sb_config.with_basic_test_info6188 self.with_screen_shots = sb_config.with_screen_shots6189 self.with_page_source = sb_config.with_page_source6190 self.with_db_reporting = sb_config.with_db_reporting6191 self.with_s3_logging = sb_config.with_s3_logging6192 self.servername = sb_config.servername6193 self.port = sb_config.port6194 self.proxy_string = sb_config.proxy_string6195 self.user_agent = sb_config.user_agent6196 self.mobile_emulator = sb_config.mobile_emulator6197 self.device_metrics = sb_config.device_metrics6198 self.cap_file = sb_config.cap_file6199 self.cap_string = sb_config.cap_string6200 self.settings_file = sb_config.settings_file6201 self.database_env = sb_config.database_env6202 self.message_duration = sb_config.message_duration6203 self.js_checking_on = sb_config.js_checking_on6204 self.ad_block_on = sb_config.ad_block_on6205 self.block_images = sb_config.block_images6206 self.verify_delay = sb_config.verify_delay6207 self.disable_csp = sb_config.disable_csp6208 self.disable_ws = sb_config.disable_ws6209 self.enable_ws = sb_config.enable_ws6210 if not self.disable_ws:6211 self.enable_ws = True6212 self.enable_sync = sb_config.enable_sync6213 self.use_auto_ext = sb_config.use_auto_ext6214 self.no_sandbox = sb_config.no_sandbox6215 self.disable_gpu = sb_config.disable_gpu6216 self.incognito = sb_config.incognito6217 self.guest_mode = sb_config.guest_mode6218 self.devtools = sb_config.devtools6219 self.remote_debug = sb_config.remote_debug6220 self.dashboard = sb_config.dashboard6221 self._dash_initialized = sb_config._dashboard_initialized6222 self.swiftshader = sb_config.swiftshader6223 self.user_data_dir = sb_config.user_data_dir6224 self.extension_zip = sb_config.extension_zip6225 self.extension_dir = sb_config.extension_dir6226 self.maximize_option = sb_config.maximize_option6227 self._reuse_session = sb_config.reuse_session6228 self._crumbs = sb_config.crumbs6229 self.save_screenshot_after_test = sb_config.save_screenshot6230 self.visual_baseline = sb_config.visual_baseline6231 self.timeout_multiplier = sb_config.timeout_multiplier6232 self.pytest_html_report = sb_config.pytest_html_report6233 self.report_on = False6234 if self.pytest_html_report:6235 self.report_on = True6236 self.use_grid = False6237 if self.servername != "localhost":6238 # Use Selenium Grid (Use --server="127.0.0.1" for a local Grid)6239 self.use_grid = True6240 if self.with_db_reporting:6241 import getpass6242 import uuid6243 from seleniumbase.core.application_manager import (6244 ApplicationManager)6245 from seleniumbase.core.testcase_manager import (6246 ExecutionQueryPayload)6247 from seleniumbase.core.testcase_manager import (6248 TestcaseDataPayload)6249 from seleniumbase.core.testcase_manager import (6250 TestcaseManager)6251 self.execution_guid = str(uuid.uuid4())6252 self.testcase_guid = None6253 self.execution_start_time = 06254 self.case_start_time = 06255 self.application = None6256 self.testcase_manager = None6257 self.error_handled = False6258 self.testcase_manager = TestcaseManager(self.database_env)6259 #6260 exec_payload = ExecutionQueryPayload()6261 exec_payload.execution_start_time = int(time.time() * 1000)6262 self.execution_start_time = exec_payload.execution_start_time6263 exec_payload.guid = self.execution_guid6264 exec_payload.username = getpass.getuser()6265 self.testcase_manager.insert_execution_data(exec_payload)6266 #6267 data_payload = TestcaseDataPayload()6268 self.testcase_guid = str(uuid.uuid4())6269 data_payload.guid = self.testcase_guid6270 data_payload.execution_guid = self.execution_guid6271 if self.with_selenium:6272 data_payload.browser = self.browser6273 else:6274 data_payload.browser = "N/A"6275 data_payload.test_address = test_id6276 application = ApplicationManager.generate_application_string(6277 self._testMethodName)6278 data_payload.env = application.split('.')[0]6279 data_payload.start_time = application.split('.')[1]6280 data_payload.state = constants.State.UNTESTED6281 self.__skip_reason = None6282 self.testcase_manager.insert_testcase_data(data_payload)6283 self.case_start_time = int(time.time() * 1000)6284 if self.headless:6285 width = settings.HEADLESS_START_WIDTH6286 height = settings.HEADLESS_START_HEIGHT6287 try:6288 # from pyvirtualdisplay import Display # Skip for own lib6289 from seleniumbase.virtual_display.display import Display6290 self.display = Display(visible=0, size=(width, height))6291 self.display.start()6292 self.headless_active = True6293 except Exception:6294 # pyvirtualdisplay might not be necessary anymore because6295 # Chrome and Firefox now have built-in headless displays6296 pass6297 else:6298 # (Nosetests / Not Pytest)6299 pass # Setup performed in plugins6300 # Verify that SeleniumBase is installed successfully6301 if not hasattr(self, "browser"):6302 raise Exception("""SeleniumBase plugins DID NOT load!\n\n"""6303 """*** Please REINSTALL SeleniumBase using: >\n"""6304 """ >>> "pip install -r requirements.txt"\n"""6305 """ >>> "python setup.py install" """)6306 # Parse the settings file6307 if self.settings_file:6308 from seleniumbase.core import settings_parser6309 settings_parser.set_settings(self.settings_file)6310 # Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio6311 if self.device_metrics:6312 metrics_string = self.device_metrics6313 metrics_string = metrics_string.replace(' ', '')6314 metrics_list = metrics_string.split(',')6315 exception_string = (6316 'Invalid input for Mobile Emulator device metrics!\n'6317 'Expecting a comma-separated string with three\n'6318 'integer values for Width, Height, and Pixel-Ratio.\n'6319 'Example: --metrics="411,731,3" ')6320 if len(metrics_list) != 3:6321 raise Exception(exception_string)6322 try:6323 self.__device_width = int(metrics_list[0])6324 self.__device_height = int(metrics_list[1])6325 self.__device_pixel_ratio = int(metrics_list[2])6326 self.mobile_emulator = True6327 except Exception:6328 raise Exception(exception_string)6329 if self.mobile_emulator:6330 if not self.user_agent:6331 # Use the Pixel 3 user agent by default if not specified6332 self.user_agent = (6333 "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) "6334 "AppleWebKit/537.36 (KHTML, like Gecko) "6335 "Chrome/76.0.3809.132 Mobile Safari/537.36")6336 # Dashboard pre-processing:6337 if self.dashboard:6338 sb_config._sbase_detected = True6339 sb_config._only_unittest = False6340 if not self._dash_initialized:6341 sb_config._dashboard_initialized = True6342 sb_config._sbase_detected = True6343 self._dash_initialized = True6344 self.__process_dashboard(False, init=True)6345 has_url = False6346 if self._reuse_session:6347 if not hasattr(sb_config, 'shared_driver'):6348 sb_config.shared_driver = None6349 if sb_config.shared_driver:6350 try:6351 self._default_driver = sb_config.shared_driver6352 self.driver = sb_config.shared_driver6353 self._drivers_list = [sb_config.shared_driver]6354 url = self.get_current_url()6355 if url is not None:6356 has_url = True6357 if self._crumbs:6358 self.driver.delete_all_cookies()6359 except Exception:6360 pass6361 if self._reuse_session and sb_config.shared_driver and has_url:6362 if self.start_page and len(self.start_page) >= 4:6363 if page_utils.is_valid_url(self.start_page):6364 self.open(self.start_page)6365 else:6366 new_start_page = "http://" + self.start_page6367 if page_utils.is_valid_url(new_start_page):6368 self.open(new_start_page)6369 elif self._crumbs:6370 if self.get_current_url() != "data:,":6371 self.open("data:,")6372 else:6373 pass6374 else:6375 # Launch WebDriver for both Pytest and Nosetests6376 self.driver = self.get_new_driver(browser=self.browser,6377 headless=self.headless,6378 locale_code=self.locale_code,6379 servername=self.servername,6380 port=self.port,6381 proxy=self.proxy_string,6382 agent=self.user_agent,6383 switch_to=True,6384 cap_file=self.cap_file,6385 cap_string=self.cap_string,6386 disable_csp=self.disable_csp,6387 enable_ws=self.enable_ws,6388 enable_sync=self.enable_sync,6389 use_auto_ext=self.use_auto_ext,6390 no_sandbox=self.no_sandbox,6391 disable_gpu=self.disable_gpu,6392 incognito=self.incognito,6393 guest_mode=self.guest_mode,6394 devtools=self.devtools,6395 remote_debug=self.remote_debug,6396 swiftshader=self.swiftshader,6397 block_images=self.block_images,6398 user_data_dir=self.user_data_dir,6399 extension_zip=self.extension_zip,6400 extension_dir=self.extension_dir,6401 is_mobile=self.mobile_emulator,6402 d_width=self.__device_width,6403 d_height=self.__device_height,6404 d_p_r=self.__device_pixel_ratio)6405 self._default_driver = self.driver6406 if self._reuse_session:6407 sb_config.shared_driver = self.driver6408 if self.browser in ["firefox", "ie", "safari"]:6409 # Only Chromium-based browsers have the mobile emulator.6410 # Some actions such as hover-clicking are different on mobile.6411 self.mobile_emulator = False6412 # Configure the test time limit (if used).6413 self.set_time_limit(self.time_limit)6414 # Set the start time for the test (in ms).6415 # Although the pytest clock starts before setUp() begins,6416 # the time-limit clock starts at the end of the setUp() method.6417 sb_config.start_time_ms = int(time.time() * 1000.0)6418 def __set_last_page_screenshot(self):6419 """ self.__last_page_screenshot is only for pytest html report logs6420 self.__last_page_screenshot_png is for all screenshot log files """6421 if not self.__last_page_screenshot and (6422 not self.__last_page_screenshot_png):6423 try:6424 element = self.driver.find_element(6425 by=By.TAG_NAME, value="body")6426 if self.is_pytest and self.report_on:6427 self.__last_page_screenshot_png = (6428 self.driver.get_screenshot_as_png())6429 self.__last_page_screenshot = element.screenshot_as_base646430 else:6431 self.__last_page_screenshot_png = element.screenshot_as_png6432 except Exception:6433 if not self.__last_page_screenshot:6434 if self.is_pytest and self.report_on:6435 try:6436 self.__last_page_screenshot = (6437 self.driver.get_screenshot_as_base64())6438 except Exception:6439 pass6440 if not self.__last_page_screenshot_png:6441 try:6442 self.__last_page_screenshot_png = (6443 self.driver.get_screenshot_as_png())6444 except Exception:6445 pass6446 def __set_last_page_url(self):6447 if not self.__last_page_url:6448 try:6449 self.__last_page_url = log_helper.get_last_page(self.driver)6450 except Exception:6451 self.__last_page_url = None6452 def __set_last_page_source(self):6453 if not self.__last_page_source:6454 try:6455 self.__last_page_source = (6456 log_helper.get_html_source_with_base_href(6457 self.driver, self.driver.page_source))6458 except Exception:6459 self.__last_page_source = None6460 def __get_exception_info(self):6461 exc_message = None6462 if sys.version_info[0] >= 3 and hasattr(self, '_outcome') and (6463 hasattr(self._outcome, 'errors') and self._outcome.errors):6464 try:6465 exc_message = self._outcome.errors[0][1][1]6466 except Exception:6467 exc_message = "(Unknown Exception)"6468 else:6469 try:6470 exc_message = sys.last_value6471 except Exception:6472 exc_message = "(Unknown Exception)"6473 return str(exc_message)6474 def __insert_test_result(self, state, err):6475 from seleniumbase.core.testcase_manager import TestcaseDataPayload6476 data_payload = TestcaseDataPayload()6477 data_payload.runtime = int(time.time() * 1000) - self.case_start_time6478 data_payload.guid = self.testcase_guid6479 data_payload.execution_guid = self.execution_guid6480 data_payload.state = state6481 if err:6482 import traceback6483 tb_string = traceback.format_exc()6484 if "Message: " in tb_string:6485 data_payload.message = "Message: " + tb_string.split(6486 "Message: ")[-1]6487 elif "Exception: " in tb_string:6488 data_payload.message = tb_string.split("Exception: ")[-1]6489 elif "Error: " in tb_string:6490 data_payload.message = tb_string.split("Error: ")[-1]6491 else:6492 data_payload.message = self.__get_exception_info()6493 else:6494 test_id = self.__get_test_id_2()6495 if self.is_pytest and test_id in sb_config._results.keys() and (6496 sb_config._results[test_id] == "Skipped"):6497 if self.__skip_reason:6498 data_payload.message = "Skipped: " + self.__skip_reason6499 else:6500 data_payload.message = "Skipped: (no reason given)"6501 self.testcase_manager.update_testcase_data(data_payload)6502 def __add_pytest_html_extra(self):6503 if not self.__added_pytest_html_extra:6504 try:6505 if self.with_selenium:6506 if not self.__last_page_screenshot:6507 self.__set_last_page_screenshot()6508 self.__set_last_page_url()6509 self.__set_last_page_source()6510 if self.report_on:6511 extra_url = {}6512 extra_url['name'] = 'URL'6513 extra_url['format'] = 'url'6514 extra_url['content'] = self.get_current_url()6515 extra_url['mime_type'] = None6516 extra_url['extension'] = None6517 extra_image = {}6518 extra_image['name'] = 'Screenshot'6519 extra_image['format'] = 'image'6520 extra_image['content'] = self.__last_page_screenshot6521 extra_image['mime_type'] = 'image/png'6522 extra_image['extension'] = 'png'6523 self.__added_pytest_html_extra = True6524 self._html_report_extra.append(extra_url)6525 self._html_report_extra.append(extra_image)6526 except Exception:6527 pass6528 def __quit_all_drivers(self):6529 if self._reuse_session and sb_config.shared_driver:6530 if len(self._drivers_list) > 0:6531 sb_config.shared_driver = self._drivers_list[0]6532 self._default_driver = self._drivers_list[0]6533 self.switch_to_default_driver()6534 if len(self._drivers_list) > 1:6535 self._drivers_list = self._drivers_list[1:]6536 else:6537 self._drivers_list = []6538 # Close all open browser windows6539 self._drivers_list.reverse() # Last In, First Out6540 for driver in self._drivers_list:6541 try:6542 driver.quit()6543 except AttributeError:6544 pass6545 except Exception:6546 pass6547 self.driver = None6548 self._default_driver = None6549 self._drivers_list = []6550 def __has_exception(self):6551 has_exception = False6552 if hasattr(sys, 'last_traceback') and sys.last_traceback is not None:6553 has_exception = True6554 elif sys.version_info[0] >= 3 and hasattr(self, '_outcome'):6555 if hasattr(self._outcome, 'errors') and self._outcome.errors:6556 has_exception = True6557 else:6558 if sys.version_info[0] >= 3:6559 has_exception = sys.exc_info()[1] is not None6560 else:6561 if not hasattr(self, "_using_sb_fixture_class") and (6562 not hasattr(self, "_using_sb_fixture_no_class")):6563 has_exception = sys.exc_info()[1] is not None6564 else:6565 has_exception = (len(str(sys.exc_info()[1]).strip()) > 0)6566 return has_exception6567 def __get_test_id(self):6568 test_id = "%s.%s.%s" % (self.__class__.__module__,6569 self.__class__.__name__,6570 self._testMethodName)6571 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6572 test_id = self._sb_test_identifier6573 return test_id6574 def __get_test_id_2(self):6575 """ The id for SeleniumBase Dashboard entries. """6576 test_id = "%s.%s.%s" % (self.__class__.__module__.split('.')[-1],6577 self.__class__.__name__,6578 self._testMethodName)6579 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6580 test_id = self._sb_test_identifier6581 if test_id.count('.') > 1:6582 test_id = '.'.join(test_id.split('.')[1:])6583 return test_id6584 def __get_display_id(self):6585 test_id = "%s.py::%s::%s" % (6586 self.__class__.__module__.replace('.', '/'),6587 self.__class__.__name__,6588 self._testMethodName)6589 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6590 test_id = self._sb_test_identifier6591 if hasattr(self, "_using_sb_fixture_class"):6592 if test_id.count('.') >= 2:6593 parts = test_id.split('.')6594 full = parts[-3] + '.py::' + parts[-2] + '::' + parts[-1]6595 test_id = full6596 elif hasattr(self, "_using_sb_fixture_no_class"):6597 if test_id.count('.') >= 1:6598 parts = test_id.split('.')6599 full = parts[-2] + '.py::' + parts[-1]6600 test_id = full6601 return test_id6602 def __create_log_path_as_needed(self, test_logpath):6603 if not os.path.exists(test_logpath):6604 try:6605 os.makedirs(test_logpath)6606 except Exception:6607 pass # Only reachable during multi-threaded runs6608 def __process_dashboard(self, has_exception, init=False):6609 ''' SeleniumBase Dashboard Processing '''6610 if len(sb_config._extra_dash_entries) > 0:6611 # First take care of existing entries from non-SeleniumBase tests6612 for test_id in sb_config._extra_dash_entries:6613 if test_id in sb_config._results.keys():6614 if sb_config._results[test_id] == "Skipped":6615 sb_config.item_count_skipped += 16616 sb_config.item_count_untested -= 16617 elif sb_config._results[test_id] == "Failed":6618 sb_config.item_count_failed += 16619 sb_config.item_count_untested -= 16620 elif sb_config._results[test_id] == "Passed":6621 sb_config.item_count_passed += 16622 sb_config.item_count_untested -= 16623 else: # Mark "Skipped" if unknown6624 sb_config.item_count_skipped += 16625 sb_config.item_count_untested -= 16626 sb_config._extra_dash_entries = [] # Reset the list to empty6627 # Process new entries6628 test_id = self.__get_test_id_2()6629 dud = "seleniumbase/plugins/pytest_plugin.py::BaseClass::base_method"6630 if not init:6631 duration_ms = int(time.time() * 1000) - sb_config.start_time_ms6632 duration = float(duration_ms) / 1000.06633 sb_config._duration[test_id] = duration6634 if test_id not in sb_config._display_id.keys():6635 sb_config._display_id[test_id] = self.__get_display_id()6636 if sb_config._display_id[test_id] == dud:6637 return6638 if hasattr(self, "_using_sb_fixture") and (6639 test_id not in sb_config._results.keys()):6640 if test_id.count('.') > 1:6641 alt_test_id = '.'.join(test_id.split('.')[1:])6642 if alt_test_id in sb_config._results.keys():6643 sb_config._results.pop(alt_test_id)6644 elif test_id.count('.') == 1:6645 alt_test_id = sb_config._display_id[test_id]6646 alt_test_id = alt_test_id.replace(".py::", ".")6647 alt_test_id = alt_test_id.replace("::", ".")6648 if alt_test_id in sb_config._results.keys():6649 sb_config._results.pop(alt_test_id)6650 if test_id in sb_config._results.keys() and (6651 sb_config._results[test_id] == "Skipped"):6652 sb_config.item_count_skipped += 16653 sb_config.item_count_untested -= 16654 sb_config._results[test_id] = "Skipped"6655 elif has_exception:6656 # pytest-rerunfailures may cause duplicate results6657 if test_id not in sb_config._results.keys() or (6658 (not sb_config._results[test_id] == "Failed")):6659 sb_config._results[test_id] = "Failed"6660 sb_config.item_count_failed += 16661 sb_config.item_count_untested -= 16662 else:6663 if test_id in sb_config._results.keys() and (6664 sb_config._results[test_id] == "Failed"):6665 # Possibly pytest-rerunfailures reran the test6666 sb_config.item_count_failed -= 16667 sb_config.item_count_untested += 16668 sb_config._results[test_id] = "Passed"6669 sb_config.item_count_passed += 16670 sb_config.item_count_untested -= 16671 num_passed = sb_config.item_count_passed6672 num_failed = sb_config.item_count_failed6673 num_skipped = sb_config.item_count_skipped6674 num_untested = sb_config.item_count_untested6675 self.create_pie_chart(title=constants.Dashboard.TITLE)6676 self.add_data_point("Passed", num_passed, color="#84d474")6677 self.add_data_point("Untested", num_untested, color="#eaeaea")6678 self.add_data_point("Skipped", num_skipped, color="#efd8b4")6679 self.add_data_point("Failed", num_failed, color="#f17476")6680 style = (6681 '<link rel="stylesheet" '6682 'href="%s">' % constants.Dashboard.STYLE_CSS)6683 auto_refresh_html = ''6684 if num_untested > 0:6685 # Refresh every X seconds when waiting for more test results6686 auto_refresh_html = constants.Dashboard.META_REFRESH_HTML6687 else:6688 # The tests are complete6689 if sb_config._using_html_report:6690 # Add the pie chart to the pytest html report6691 sb_config._saved_dashboard_pie = self.extract_chart()6692 head = (6693 '<head><meta charset="utf-8" />'6694 '<meta property="og:image" '6695 'content="https://seleniumbase.io/img/dash_pie.png">'6696 '<link rel="shortcut icon" '6697 'href="https://seleniumbase.io/img/dash_pie_2.png">'6698 '%s'6699 '<title>Dashboard</title>'6700 '%s</head>' % (auto_refresh_html, style))6701 table_html = (6702 '<div></div>'6703 '<table border="1px solid #e6e6e6;" width="100%;" padding: 5px;'6704 ' font-size="12px;" text-align="left;" id="results-table">'6705 '<thead id="results-table-head"><tr>'6706 '<th col="result">Result</th><th col="name">Test</th>'6707 '<th col="duration">Duration</th><th col="links">Links</th>'6708 '</tr></thead>')6709 the_failed = []6710 the_skipped = []6711 the_passed = []6712 the_untested = []6713 for key in sb_config._results.keys():6714 t_res = sb_config._results[key]6715 t_dur = sb_config._duration[key]6716 t_d_id = sb_config._display_id[key]6717 res_low = t_res.lower()6718 if sb_config._results[key] == "Failed":6719 the_failed.append([res_low, t_res, t_d_id, t_dur])6720 if sb_config._results[key] == "Skipped":6721 the_skipped.append([res_low, t_res, t_d_id, t_dur])6722 if sb_config._results[key] == "Passed":6723 the_passed.append([res_low, t_res, t_d_id, t_dur])6724 if sb_config._results[key] == "Untested":6725 the_untested.append([res_low, t_res, t_d_id, t_dur])6726 for row in the_failed:6727 row = (6728 '<tbody class="%s results-table-row"><tr>'6729 '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6730 '<td><a href="latest_logs/">latest_logs/</a></td>'6731 '</tr></tbody>' % (row[0], row[1], row[2], row[3]))6732 table_html += row6733 for row in the_skipped:6734 row = (6735 '<tbody class="%s results-table-row"><tr>'6736 '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6737 '<td></td></tr></tbody>' % (row[0], row[1], row[2], row[3]))6738 table_html += row6739 for row in the_passed:6740 row = (6741 '<tbody class="%s results-table-row"><tr>'6742 '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6743 '<td></td></tr></tbody>' % (row[0], row[1], row[2], row[3]))6744 table_html += row6745 for row in the_untested:6746 row = (6747 '<tbody class="%s results-table-row"><tr>'6748 '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6749 '<td></td></tr></tbody>' % (row[0], row[1], row[2], row[3]))6750 table_html += row6751 table_html += "</table>"6752 add_more = "<br /><b>Last updated:</b> "6753 timestamp, the_date, the_time = log_helper.get_master_time()6754 last_updated = "%s at %s" % (the_date, the_time)6755 add_more = add_more + "%s" % last_updated6756 status = "<p></p><div><b>Status:</b> Awaiting results..."6757 status += " (Refresh the page for updates)"6758 if num_untested == 0:6759 status = "<p></p><div><b>Status:</b> Test Run Complete:"6760 if num_failed == 0:6761 if num_passed > 0:6762 if num_skipped == 0:6763 status += " <b>Success!</b> (All tests passed)"6764 else:6765 status += " <b>Success!</b> (No failing tests)"6766 else:6767 status += " All tests were skipped!"6768 else:6769 latest_logs_dir = "latest_logs/"6770 log_msg = "See latest logs for details"6771 if num_failed == 1:6772 status += (6773 ' <b>1 test failed!</b> --- '6774 '(<b><a href="%s">%s</a></b>)'6775 '' % (latest_logs_dir, log_msg))6776 else:6777 status += (6778 ' <b>%s tests failed!</b> --- '6779 '(<b><a href="%s">%s</a></b>)'6780 '' % (num_failed, latest_logs_dir, log_msg))6781 status += "</div><p></p>"6782 add_more = add_more + status6783 gen_by = (6784 '<p><div>Generated by: <b><a href="https://seleniumbase.io/">'6785 'SeleniumBase</a></b></div></p><p></p>')6786 add_more = add_more + gen_by6787 # Have dashboard auto-refresh on updates when using an http server6788 refresh_line = (6789 '<script type="text/javascript" src="%s">'6790 '</script>' % constants.Dashboard.LIVE_JS)6791 if num_untested == 0 and sb_config._using_html_report:6792 sb_config._dash_final_summary = status6793 add_more = add_more + refresh_line6794 the_html = head + self.extract_chart() + table_html + add_more6795 abs_path = os.path.abspath('.')6796 file_path = os.path.join(abs_path, "dashboard.html")6797 out_file = codecs.open(file_path, "w+", encoding="utf-8")6798 out_file.writelines(the_html)6799 out_file.close()6800 time.sleep(0.05) # Add time for dashboard server to process updates6801 def has_exception(self):6802 """ (This method should ONLY be used in custom tearDown() methods.)6803 This method returns True if the test failed or raised an exception.6804 This is useful for performing additional steps in your tearDown()6805 method (based on whether or not the test passed or failed).6806 Example use cases:6807 * Performing cleanup steps if a test didn't complete.6808 * Sending test data and/or results to a dashboard service.6809 """6810 return self.__has_exception()6811 def save_teardown_screenshot(self):6812 """ (Should ONLY be used at the start of custom tearDown() methods.)6813 This method takes a screenshot of the current web page for a6814 failing test (or when running your tests with --save-screenshot).6815 That way your tearDown() method can navigate away from the last6816 page where the test failed, and still get the correct screenshot6817 before performing tearDown() steps on other pages. If this method6818 is not included in your custom tearDown() method, a screenshot6819 will still be taken after the last step of your tearDown(), where6820 you should be calling "super(SubClassOfBaseCase, self).tearDown()"6821 """6822 if self.__has_exception() or self.save_screenshot_after_test:6823 test_id = self.__get_test_id()6824 test_logpath = self.log_path + "/" + test_id6825 self.__create_log_path_as_needed(test_logpath)6826 self.__set_last_page_screenshot()6827 self.__set_last_page_url()6828 self.__set_last_page_source()6829 if self.is_pytest:6830 self.__add_pytest_html_extra()6831 def tearDown(self):6832 """6833 Be careful if a subclass of BaseCase overrides setUp()6834 You'll need to add the following line to the subclass's tearDown():6835 super(SubClassOfBaseCase, self).tearDown()6836 """6837 try:6838 is_pytest = self.is_pytest # This fails if overriding setUp()6839 if is_pytest:6840 with_selenium = self.with_selenium6841 except Exception:6842 sub_class_name = str(6843 self.__class__.__bases__[0]).split('.')[-1].split("'")[0]6844 sub_file_name = str(self.__class__.__bases__[0]).split('.')[-2]6845 sub_file_name = sub_file_name + ".py"6846 class_name = str(self.__class__).split('.')[-1].split("'")[0]6847 file_name = str(self.__class__).split('.')[-2] + ".py"6848 class_name_used = sub_class_name6849 file_name_used = sub_file_name6850 if sub_class_name == "BaseCase":6851 class_name_used = class_name6852 file_name_used = file_name6853 fix_setup = "super(%s, self).setUp()" % class_name_used6854 fix_teardown = "super(%s, self).tearDown()" % class_name_used6855 message = ("You're overriding SeleniumBase's BaseCase setUp() "6856 "method with your own setUp() method, which breaks "6857 "SeleniumBase. You can fix this by going to your "6858 "%s class located in your %s file and adding the "6859 "following line of code AT THE BEGINNING of your "6860 "setUp() method:\n%s\n\nAlso make sure "6861 "you have added the following line of code AT THE "6862 "END of your tearDown() method:\n%s\n"6863 % (class_name_used, file_name_used,6864 fix_setup, fix_teardown))6865 raise Exception(message)6866 # *** Start tearDown() officially ***6867 self.__slow_mode_pause_if_active()6868 has_exception = self.__has_exception()6869 if self.__deferred_assert_failures:6870 print(6871 "\nWhen using self.deferred_assert_*() methods in your tests, "6872 "remember to call self.process_deferred_asserts() afterwards. "6873 "Now calling in tearDown()...\nFailures Detected:")6874 if not has_exception:6875 self.process_deferred_asserts()6876 else:6877 self.process_deferred_asserts(print_only=True)6878 if self.is_pytest:6879 # pytest-specific code6880 test_id = self.__get_test_id()6881 if with_selenium:6882 # Save a screenshot if logging is on when an exception occurs6883 if has_exception:6884 self.__add_pytest_html_extra()6885 sb_config._has_exception = True6886 if self.with_testing_base and not has_exception and (6887 self.save_screenshot_after_test):6888 test_logpath = self.log_path + "/" + test_id6889 self.__create_log_path_as_needed(test_logpath)6890 if not self.__last_page_screenshot_png:6891 self.__set_last_page_screenshot()6892 self.__set_last_page_url()6893 self.__set_last_page_source()6894 log_helper.log_screenshot(6895 test_logpath,6896 self.driver,6897 self.__last_page_screenshot_png)6898 self.__add_pytest_html_extra()6899 if self.with_testing_base and has_exception:6900 test_logpath = self.log_path + "/" + test_id6901 self.__create_log_path_as_needed(test_logpath)6902 if ((not self.with_screen_shots) and (6903 not self.with_basic_test_info) and (6904 not self.with_page_source)):6905 # Log everything if nothing specified (if testing_base)6906 if not self.__last_page_screenshot_png:6907 self.__set_last_page_screenshot()6908 self.__set_last_page_url()6909 self.__set_last_page_source()6910 log_helper.log_screenshot(6911 test_logpath,6912 self.driver,6913 self.__last_page_screenshot_png)6914 log_helper.log_test_failure_data(6915 self, test_logpath, self.driver, self.browser,6916 self.__last_page_url)6917 log_helper.log_page_source(6918 test_logpath, self.driver, self.__last_page_source)6919 else:6920 if self.with_screen_shots:6921 if not self.__last_page_screenshot_png:6922 self.__set_last_page_screenshot()6923 self.__set_last_page_url()6924 self.__set_last_page_source()6925 log_helper.log_screenshot(6926 test_logpath,6927 self.driver,6928 self.__last_page_screenshot_png)6929 if self.with_basic_test_info:6930 log_helper.log_test_failure_data(6931 self, test_logpath, self.driver, self.browser,6932 self.__last_page_url)6933 if self.with_page_source:6934 log_helper.log_page_source(6935 test_logpath, self.driver,6936 self.__last_page_source)6937 if self.dashboard:6938 self.__process_dashboard(has_exception)6939 # (Pytest) Finally close all open browser windows6940 self.__quit_all_drivers()6941 if self.headless:6942 if self.headless_active:6943 try:6944 self.display.stop()6945 except AttributeError:6946 pass6947 except Exception:6948 pass6949 self.display = None6950 if self.with_db_reporting:6951 if has_exception:6952 self.__insert_test_result(constants.State.FAILED, True)6953 else:6954 test_id = self.__get_test_id_2()6955 if test_id in sb_config._results.keys() and (6956 sb_config._results[test_id] == "Skipped"):6957 self.__insert_test_result(6958 constants.State.SKIPPED, False)6959 else:6960 self.__insert_test_result(6961 constants.State.PASSED, False)6962 runtime = int(time.time() * 1000) - self.execution_start_time6963 self.testcase_manager.update_execution_data(6964 self.execution_guid, runtime)6965 if self.with_s3_logging and has_exception:6966 """ If enabled, upload logs to S3 during test exceptions. """6967 import uuid6968 from seleniumbase.core.s3_manager import S3LoggingBucket...
webdriver_test.py
Source:webdriver_test.py
...3857 def skip(self, reason=""):3858 """ Mark the test as Skipped. """3859 self.__check_scope__()3860 if self.dashboard:3861 test_id = self.__get_test_id_2()3862 if hasattr(self, "_using_sb_fixture"):3863 test_id = sb_config._test_id3864 if (3865 test_id in sb_config._results.keys()3866 and sb_config._results[test_id] == "Passed"3867 ):3868 # Duplicate tearDown() called where test already passed3869 self.__passed_then_skipped = True3870 self.__will_be_skipped = True3871 sb_config._results[test_id] = "Skipped"3872 if hasattr(self, "with_db_reporting") and self.with_db_reporting:3873 self.__skip_reason = reason3874 # Add skip reason to the logs3875 if not hasattr(self, "_using_sb_fixture"):3876 test_id = self.__get_test_id() # Recalculate the test id3877 test_logpath = os.path.join(self.log_path, test_id)3878 self.__create_log_path_as_needed(test_logpath)3879 browser = self.browser3880 if not reason:3881 reason = "No skip reason given"3882 log_helper.log_skipped_test_data(3883 self, test_logpath, self.driver, browser, reason3884 )3885 # Finally skip the test for real3886 self.skipTest(reason)3887 # Application "Local Storage" controls3888 def set_local_storage_item(self, key, value):3889 self.__check_scope__()3890 self.execute_script(3891 "window.localStorage.setItem('{}', '{}');".format(key, value)3892 )3893 def get_local_storage_item(self, key):3894 self.__check_scope__()3895 return self.execute_script(3896 "return window.localStorage.getItem('{}');".format(key)3897 )3898 def remove_local_storage_item(self, key):3899 self.__check_scope__()3900 self.execute_script(3901 "window.localStorage.removeItem('{}');".format(key)3902 )3903 def clear_local_storage(self):3904 self.__check_scope__()3905 self.execute_script("window.localStorage.clear();")3906 def get_local_storage_keys(self):3907 self.__check_scope__()3908 return self.execute_script(3909 "var ls = window.localStorage, keys = []; "3910 "for (var i = 0; i < ls.length; ++i) "3911 " keys[i] = ls.key(i); "3912 "return keys;"3913 )3914 def get_local_storage_items(self):3915 self.__check_scope__()3916 return self.execute_script(3917 r"var ls = window.localStorage, items = {}; "3918 "for (var i = 0, k; i < ls.length; ++i) "3919 " items[k = ls.key(i)] = ls.getItem(k); "3920 "return items;"3921 )3922 # Application "Session Storage" controls3923 def set_session_storage_item(self, key, value):3924 self.__check_scope__()3925 self.execute_script(3926 "window.sessionStorage.setItem('{}', '{}');".format(key, value)3927 )3928 def get_session_storage_item(self, key):3929 self.__check_scope__()3930 return self.execute_script(3931 "return window.sessionStorage.getItem('{}');".format(key)3932 )3933 def remove_session_storage_item(self, key):3934 self.__check_scope__()3935 self.execute_script(3936 "window.sessionStorage.removeItem('{}');".format(key)3937 )3938 def clear_session_storage(self):3939 self.__check_scope__()3940 self.execute_script("window.sessionStorage.clear();")3941 def get_session_storage_keys(self):3942 self.__check_scope__()3943 return self.execute_script(3944 "var ls = window.sessionStorage, keys = []; "3945 "for (var i = 0; i < ls.length; ++i) "3946 " keys[i] = ls.key(i); "3947 "return keys;"3948 )3949 def get_session_storage_items(self):3950 self.__check_scope__()3951 return self.execute_script(3952 r"var ls = window.sessionStorage, items = {}; "3953 "for (var i = 0, k; i < ls.length; ++i) "3954 " items[k = ls.key(i)] = ls.getItem(k); "3955 "return items;"3956 )3957 ############3958 # Duplicates (Avoids name confusion when migrating from other frameworks.)3959 def open_url(self, url):3960 """ Same as self.open() """3961 self.open(url)3962 def visit(self, url):3963 """ Same as self.open() """3964 self.open(url)3965 def visit_url(self, url):3966 """ Same as self.open() """3967 self.open(url)3968 def goto(self, url):3969 """ Same as self.open() """3970 self.open(url)3971 def go_to(self, url):3972 """ Same as self.open() """3973 self.open(url)3974 def reload(self):3975 """ Same as self.refresh_page() """3976 self.refresh_page()3977 def reload_page(self):3978 """ Same as self.refresh_page() """3979 self.refresh_page()3980 def open_new_tab(self, switch_to=True):3981 """ Same as self.open_new_window() """3982 self.open_new_window(switch_to=switch_to)3983 def switch_to_tab(self, tab, timeout=None):3984 """ Same as self.switch_to_window()3985 Switches control of the browser to the specified window.3986 The window can be an integer: 0 -> 1st tab, 1 -> 2nd tab, etc...3987 Or it can be a list item from self.driver.window_handles """3988 self.switch_to_window(window=tab, timeout=timeout)3989 def switch_to_default_tab(self):3990 """ Same as self.switch_to_default_window() """3991 self.switch_to_default_window()3992 def switch_to_newest_tab(self):3993 """ Same as self.switch_to_newest_window() """3994 self.switch_to_newest_window()3995 def input(3996 self, selector, text, by=By.CSS_SELECTOR, timeout=None, retry=False3997 ):3998 """ Same as self.update_text() """3999 self.__check_scope__()4000 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4001 self.update_text(selector, text, by=by, timeout=timeout, retry=retry)4002 def fill(4003 self, selector, text, by=By.CSS_SELECTOR, timeout=None, retry=False4004 ):4005 """ Same as self.update_text() """4006 self.__check_scope__()4007 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4008 self.update_text(selector, text, by=by, timeout=timeout, retry=retry)4009 def write(4010 self, selector, text, by=By.CSS_SELECTOR, timeout=None, retry=False4011 ):4012 """ Same as self.update_text() """4013 self.__check_scope__()4014 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4015 self.update_text(selector, text, by=by, timeout=timeout, retry=retry)4016 def send_keys(self, selector, text, by=By.CSS_SELECTOR, timeout=None):4017 """ Same as self.add_text() """4018 self.__check_scope__()4019 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4020 self.add_text(selector, text, by=by, timeout=timeout)4021 def click_link(self, link_text, timeout=None):4022 """ Same as self.click_link_text() """4023 self.__check_scope__()4024 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4025 self.click_link_text(link_text, timeout=timeout)4026 def click_partial_link(self, partial_link_text, timeout=None):4027 """ Same as self.click_partial_link_text() """4028 self.__check_scope__()4029 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)4030 self.click_partial_link_text(partial_link_text, timeout=timeout)4031 def wait_for_element_visible(4032 self, selector, by=By.CSS_SELECTOR, timeout=None4033 ):4034 """ Same as self.wait_for_element() """4035 self.__check_scope__()4036 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)4037 if self._is_shadow_selector(selector):4038 return self._shadow.wait_for_shadow_element_visible(4039 selector, timeout4040 )4041 return element_actions.wait_for_element_visible(self.driver, selector, by, timeout)4042 def wait_for_element_interactable(4043 self, selector, by=By.CSS_SELECTOR, timeout=None4044 ):4045 """ Same as self.wait_for_element() """4046 self.__check_scope__()4047 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)4048 if self._is_shadow_selector(selector):4049 return self._shadow.wait_for_shadow_element_visible(4050 selector, timeout4051 )4052 return element_actions.wait_for_element_interactable(self.driver, selector, by, timeout)4053 def wait_for_element_not_present(4054 self, selector, by=By.CSS_SELECTOR, timeout=None4055 ):4056 """Same as self.wait_for_element_absent()4057 Waits for an element to no longer appear in the HTML of a page.4058 A hidden element still counts as appearing in the page HTML.4059 If waiting for elements to be hidden instead of nonexistent,4060 use wait_for_element_not_visible() instead.4061 """4062 self.__check_scope__()4063 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4064 selector, by = self.__recalculate_selector(selector, by)4065 return page_actions.wait_for_element_absent(4066 self.driver, selector, by, timeout4067 )4068 def get_google_auth_password(self, totp_key=None):4069 """ Same as self.get_mfa_code() """4070 return self.get_mfa_code(totp_key=totp_key)4071 def get_google_auth_code(self, totp_key=None):4072 """ Same as self.get_mfa_code() """4073 return self.get_mfa_code(totp_key=totp_key)4074 def get_totp_code(self, totp_key=None):4075 """ Same as self.get_mfa_code() """4076 return self.get_mfa_code(totp_key=totp_key)4077 def enter_totp_code(4078 self, selector, totp_key=None, by=By.CSS_SELECTOR, timeout=None4079 ):4080 """ Same as self.enter_mfa_code() """4081 return self.enter_mfa_code(4082 selector=selector, totp_key=totp_key, by=by, timeout=timeout4083 )4084 def _print(self, msg):4085 """Same as Python's print(), but won't print during multithreaded runs4086 because overlapping print() commands may lead to unexpected output.4087 In most cases, the print() command won't print for multithreaded tests,4088 but there are some exceptions, and this will take care of those.4089 Here's an example of running tests multithreaded: "pytest -n=4".4090 To force a print during multithreaded tests, use: "sys.stderr.write()".4091 To print without the new-line character end, use: "sys.stdout.write()".4092 """4093 if not sb_config._multithreaded:4094 print(msg)4095 def start_tour(self, name=None, interval=0):4096 self.play_tour(name=name, interval=interval)4097 ############4098 def add_css_link(self, css_link):4099 self.__check_scope__()4100 self.__check_browser()4101 js_utils.add_css_link(self.driver, css_link)4102 def add_js_link(self, js_link):4103 self.__check_scope__()4104 self.__check_browser__()4105 js_utils.add_js_link(self.driver, js_link)4106 def add_css_style(self, css_style):4107 self.__check_scope__()4108 self.__check_browser__()4109 js_utils.add_css_style(self.driver, css_style)4110 def add_js_code_from_link(self, js_link):4111 self.__check_scope__()4112 self.__check_browser__()4113 js_utils.add_js_code_from_link(self.driver, js_link)4114 def add_js_code(self, js_code):4115 self.__check_scope__()4116 self.__check_browser__()4117 js_utils.add_js_code(self.driver, js_code)4118 def add_meta_tag(self, http_equiv=None, content=None):4119 self.__check_scope__()4120 self.__check_browser__()4121 js_utils.add_meta_tag(4122 self.driver, http_equiv=http_equiv, content=content4123 )4124 ############4125 def create_presentation(4126 self, name=None, theme="default", transition="default"4127 ):4128 """Creates a Reveal-JS presentation that you can add slides to.4129 @Params4130 name - If creating multiple presentations at the same time,4131 use this to specify the name of the current presentation.4132 theme - Set a theme with a unique style for the presentation.4133 Valid themes: "serif" (default), "sky", "white", "black",4134 "simple", "league", "moon", "night",4135 "beige", "blood", and "solarized".4136 transition - Set a transition between slides.4137 Valid transitions: "none" (default), "slide", "fade",4138 "zoom", "convex", and "concave".4139 """4140 if not name:4141 name = "default"4142 if not theme or theme == "default":4143 theme = "serif"4144 valid_themes = [4145 "serif",4146 "white",4147 "black",4148 "beige",4149 "simple",4150 "sky",4151 "league",4152 "moon",4153 "night",4154 "blood",4155 "solarized",4156 ]4157 theme = theme.lower()4158 if theme not in valid_themes:4159 raise Exception(4160 "Theme {%s} not found! Valid themes: %s"4161 % (theme, valid_themes)4162 )4163 if not transition or transition == "default":4164 transition = "none"4165 valid_transitions = [4166 "none",4167 "slide",4168 "fade",4169 "zoom",4170 "convex",4171 "concave",4172 ]4173 transition = transition.lower()4174 if transition not in valid_transitions:4175 raise Exception(4176 "Transition {%s} not found! Valid transitions: %s"4177 % (transition, valid_transitions)4178 )4179 reveal_theme_css = None4180 if theme == "serif":4181 reveal_theme_css = constants.Reveal.SERIF_MIN_CSS4182 elif theme == "sky":4183 reveal_theme_css = constants.Reveal.SKY_MIN_CSS4184 elif theme == "white":4185 reveal_theme_css = constants.Reveal.WHITE_MIN_CSS4186 elif theme == "black":4187 reveal_theme_css = constants.Reveal.BLACK_MIN_CSS4188 elif theme == "simple":4189 reveal_theme_css = constants.Reveal.SIMPLE_MIN_CSS4190 elif theme == "league":4191 reveal_theme_css = constants.Reveal.LEAGUE_MIN_CSS4192 elif theme == "moon":4193 reveal_theme_css = constants.Reveal.MOON_MIN_CSS4194 elif theme == "night":4195 reveal_theme_css = constants.Reveal.NIGHT_MIN_CSS4196 elif theme == "beige":4197 reveal_theme_css = constants.Reveal.BEIGE_MIN_CSS4198 elif theme == "blood":4199 reveal_theme_css = constants.Reveal.BLOOD_MIN_CSS4200 elif theme == "solarized":4201 reveal_theme_css = constants.Reveal.SOLARIZED_MIN_CSS4202 else:4203 # Use the default if unable to determine the theme4204 reveal_theme_css = constants.Reveal.SERIF_MIN_CSS4205 new_presentation = (4206 "<html>\n"4207 "<head>\n"4208 '<meta charset="utf-8">\n'4209 '<meta http-equiv="Content-Type" content="text/html">\n'4210 '<meta name="viewport" content="shrink-to-fit=no">\n'4211 '<link rel="stylesheet" href="%s">\n'4212 '<link rel="stylesheet" href="%s">\n'4213 "<style>\n"4214 "pre{background-color:#fbe8d4;border-radius:8px;}\n"4215 "div[flex_div]{height:68vh;margin:0;align-items:center;"4216 "justify-content:center;}\n"4217 "img[rounded]{border-radius:16px;max-width:64%%;}\n"4218 "</style>\n"4219 "</head>\n\n"4220 "<body>\n"4221 "<!-- Generated by SeleniumBase - https://seleniumbase.io -->\n"4222 '<div class="reveal">\n'4223 '<div class="slides">\n'4224 % (constants.Reveal.MIN_CSS, reveal_theme_css)4225 )4226 self._presentation_slides[name] = []4227 self._presentation_slides[name].append(new_presentation)4228 self._presentation_transition[name] = transition4229 def add_slide(4230 self,4231 content=None,4232 image=None,4233 code=None,4234 iframe=None,4235 content2=None,4236 notes=None,4237 transition=None,4238 name=None,4239 ):4240 """Allows the user to add slides to a presentation.4241 @Params4242 content - The HTML content to display on the presentation slide.4243 image - Attach an image (from a URL link) to the slide.4244 code - Attach code of any programming language to the slide.4245 Language-detection will be used to add syntax formatting.4246 iframe - Attach an iFrame (from a URL link) to the slide.4247 content2 - HTML content to display after adding an image or code.4248 notes - Additional notes to include with the slide.4249 ONLY SEEN if show_notes is set for the presentation.4250 transition - Set a transition between slides. (overrides previous)4251 Valid transitions: "none" (default), "slide", "fade",4252 "zoom", "convex", and "concave".4253 name - If creating multiple presentations at the same time,4254 use this to select the presentation to add slides to.4255 """4256 if not name:4257 name = "default"4258 if name not in self._presentation_slides:4259 # Create a presentation if it doesn't already exist4260 self.create_presentation(name=name)4261 if not content:4262 content = ""4263 if not content2:4264 content2 = ""4265 if not notes:4266 notes = ""4267 if not transition:4268 transition = self._presentation_transition[name]4269 elif transition == "default":4270 transition = "none"4271 valid_transitions = [4272 "none",4273 "slide",4274 "fade",4275 "zoom",4276 "convex",4277 "concave",4278 ]4279 transition = transition.lower()4280 if transition not in valid_transitions:4281 raise Exception(4282 "Transition {%s} not found! Valid transitions: %s"4283 "" % (transition, valid_transitions)4284 )4285 add_line = ""4286 if content.startswith("<"):4287 add_line = "\n"4288 html = '\n<section data-transition="%s">%s%s' % (4289 transition,4290 add_line,4291 content,4292 )4293 if image:4294 html += '\n<div flex_div><img rounded src="%s" /></div>' % image4295 if code:4296 html += "\n<div></div>"4297 html += '\n<pre class="prettyprint">\n%s</pre>' % code4298 if iframe:4299 html += (4300 "\n<div></div>"4301 '\n<iframe src="%s" style="width:92%%;height:550px;" '4302 'title="iframe content"></iframe>' % iframe4303 )4304 add_line = ""4305 if content2.startswith("<"):4306 add_line = "\n"4307 if content2:4308 html += "%s%s" % (add_line, content2)4309 html += '\n<aside class="notes">%s</aside>' % notes4310 html += "\n</section>\n"4311 self._presentation_slides[name].append(html)4312 def save_presentation(4313 self, name=None, filename=None, show_notes=False, interval=04314 ):4315 """Saves a Reveal-JS Presentation to a file for later use.4316 @Params4317 name - If creating multiple presentations at the same time,4318 use this to select the one you wish to use.4319 filename - The name of the HTML file that you wish to4320 save the presentation to. (filename must end in ".html")4321 show_notes - When set to True, the Notes feature becomes enabled,4322 which allows presenters to see notes next to slides.4323 interval - The delay time between autoplaying slides. (in seconds)4324 If set to 0 (default), autoplay is disabled.4325 """4326 if not name:4327 name = "default"4328 if not filename:4329 filename = "my_presentation.html"4330 if name not in self._presentation_slides:4331 raise Exception("Presentation {%s} does not exist!" % name)4332 if not filename.endswith(".html"):4333 raise Exception('Presentation file must end in ".html"!')4334 if not interval:4335 interval = 04336 if interval == 0 and self.interval:4337 interval = float(self.interval)4338 if not type(interval) is int and not type(interval) is float:4339 raise Exception('Expecting a numeric value for "interval"!')4340 if interval < 0:4341 raise Exception('The "interval" cannot be a negative number!')4342 interval_ms = float(interval) * 1000.04343 show_notes_str = "false"4344 if show_notes:4345 show_notes_str = "true"4346 the_html = ""4347 for slide in self._presentation_slides[name]:4348 the_html += slide4349 the_html += (4350 "\n</div>\n"4351 "</div>\n"4352 '<script src="%s"></script>\n'4353 '<script src="%s"></script>\n'4354 "<script>Reveal.initialize("4355 "{showNotes: %s, slideNumber: true, progress: true, hash: false, "4356 "autoSlide: %s,});"4357 "</script>\n"4358 "</body>\n"4359 "</html>\n"4360 % (4361 constants.Reveal.MIN_JS,4362 constants.PrettifyJS.RUN_PRETTIFY_JS,4363 show_notes_str,4364 interval_ms,4365 )4366 )4367 # Remove duplicate ChartMaker library declarations4368 chart_libs = """4369 <script src="%s"></script>4370 <script src="%s"></script>4371 <script src="%s"></script>4372 <script src="%s"></script>4373 """ % (4374 constants.HighCharts.HC_JS,4375 constants.HighCharts.EXPORTING_JS,4376 constants.HighCharts.EXPORT_DATA_JS,4377 constants.HighCharts.ACCESSIBILITY_JS,4378 )4379 if the_html.count(chart_libs) > 1:4380 chart_libs_comment = "<!-- HighCharts Libraries Imported -->"4381 the_html = the_html.replace(chart_libs, chart_libs_comment)4382 # Only need to import the HighCharts libraries once4383 the_html = the_html.replace(chart_libs_comment, chart_libs, 1)4384 saved_presentations_folder = constants.Presentations.SAVED_FOLDER4385 if saved_presentations_folder.endswith("/"):4386 saved_presentations_folder = saved_presentations_folder[:-1]4387 if not os.path.exists(saved_presentations_folder):4388 try:4389 os.makedirs(saved_presentations_folder)4390 except Exception:4391 pass4392 file_path = saved_presentations_folder + "/" + filename4393 out_file = codecs.open(file_path, "w+", encoding="utf-8")4394 out_file.writelines(the_html)4395 out_file.close()4396 print("\n>>> [%s] was saved!\n" % file_path)4397 return file_path4398 def begin_presentation(4399 self, name=None, filename=None, show_notes=False, interval=04400 ):4401 """Begin a Reveal-JS Presentation in the web browser.4402 @Params4403 name - If creating multiple presentations at the same time,4404 use this to select the one you wish to use.4405 filename - The name of the HTML file that you wish to4406 save the presentation to. (filename must end in ".html")4407 show_notes - When set to True, the Notes feature becomes enabled,4408 which allows presenters to see notes next to slides.4409 interval - The delay time between autoplaying slides. (in seconds)4410 If set to 0 (default), autoplay is disabled.4411 """4412 if self.headless or self.xvfb:4413 return # Presentations should not run in headless mode.4414 if not name:4415 name = "default"4416 if not filename:4417 filename = "my_presentation.html"4418 if name not in self._presentation_slides:4419 raise Exception("Presentation {%s} does not exist!" % name)4420 if not filename.endswith(".html"):4421 raise Exception('Presentation file must end in ".html"!')4422 if not interval:4423 interval = 04424 if interval == 0 and self.interval:4425 interval = float(self.interval)4426 if not type(interval) is int and not type(interval) is float:4427 raise Exception('Expecting a numeric value for "interval"!')4428 if interval < 0:4429 raise Exception('The "interval" cannot be a negative number!')4430 end_slide = (4431 '\n<section data-transition="none">\n'4432 '<p class="End_Presentation_Now"> </p>\n</section>\n'4433 )4434 self._presentation_slides[name].append(end_slide)4435 file_path = self.save_presentation(4436 name=name,4437 filename=filename,4438 show_notes=show_notes,4439 interval=interval,4440 )4441 self._presentation_slides[name].pop()4442 self.open_html_file(file_path)4443 presentation_folder = constants.Presentations.SAVED_FOLDER4444 try:4445 while (4446 len(self.driver.window_handles) > 04447 and presentation_folder in self.get_current_url()4448 ):4449 time.sleep(0.05)4450 if self.is_element_visible(4451 "section.present p.End_Presentation_Now"4452 ):4453 break4454 time.sleep(0.05)4455 except Exception:4456 pass4457 ############4458 def activate_jquery_confirm(self):4459 """ See https://craftpip.github.io/jquery-confirm/ for usage. """4460 self.__check_scope__()4461 self.__check_browser__()4462 js_utils.activate_jquery_confirm(self.driver)4463 self.wait_for_ready_state_complete()4464 # def set_jqc_theme(self, theme, color=None, width=None):4465 # """ Sets the default jquery-confirm theme and width (optional).4466 # Available themes: "bootstrap", "modern", "material", "supervan",4467 # "light", "dark", and "seamless".4468 # Available colors: (This sets the BORDER color, NOT the button color.)4469 # "blue", "default", "green", "red", "purple", "orange", "dark".4470 # Width can be set using percent or pixels. Eg: "36.0%", "450px".4471 # """4472 # if not self.__changed_jqc_theme:4473 # self.__jqc_default_theme = constants.JqueryConfirm.DEFAULT_THEME4474 # self.__jqc_default_color = constants.JqueryConfirm.DEFAULT_COLOR4475 # self.__jqc_default_width = constants.JqueryConfirm.DEFAULT_WIDTH4476 # valid_themes = [4477 # "bootstrap",4478 # "modern",4479 # "material",4480 # "supervan",4481 # "light",4482 # "dark",4483 # "seamless",4484 # ]4485 # if theme.lower() not in valid_themes:4486 # raise Exception(4487 # "%s is not a valid jquery-confirm theme! "4488 # "Select from %s" % (theme.lower(), valid_themes)4489 # )4490 # constants.JqueryConfirm.DEFAULT_THEME = theme.lower()4491 # if color:4492 # valid_colors = [4493 # "blue",4494 # "default",4495 # "green",4496 # "red",4497 # "purple",4498 # "orange",4499 # "dark",4500 # ]4501 # if color.lower() not in valid_colors:4502 # raise Exception(4503 # "%s is not a valid jquery-confirm border color! "4504 # "Select from %s" % (color.lower(), valid_colors)4505 # )4506 # constants.JqueryConfirm.DEFAULT_COLOR = color.lower()4507 # if width:4508 # if type(width) is int or type(width) is float:4509 # # Convert to a string if a number is given4510 # width = str(width)4511 # if width.isnumeric():4512 # if int(width) <= 0:4513 # raise Exception("Width must be set to a positive number!")4514 # elif int(width) <= 100:4515 # width = str(width) + "%"4516 # else:4517 # width = str(width) + "px" # Use pixels if width is > 1004518 # if not width.endswith("%") and not width.endswith("px"):4519 # raise Exception(4520 # "jqc width must end with %% for percent or px for pixels!"4521 # )4522 # value = None4523 # if width.endswith("%"):4524 # value = width[:-1]4525 # if width.endswith("px"):4526 # value = width[:-2]4527 # try:4528 # value = float(value)4529 # except Exception:4530 # raise Exception("%s is not a numeric value!" % value)4531 # if value <= 0:4532 # raise Exception("%s is not a positive number!" % value)4533 # constants.JqueryConfirm.DEFAULT_WIDTH = width4534 #4535 # def reset_jqc_theme(self):4536 # """ Resets the jqc theme settings to factory defaults. """4537 # if self.__changed_jqc_theme:4538 # constants.JqueryConfirm.DEFAULT_THEME = self.__jqc_default_theme4539 # constants.JqueryConfirm.DEFAULT_COLOR = self.__jqc_default_color4540 # constants.JqueryConfirm.DEFAULT_WIDTH = self.__jqc_default_width4541 # self.__changed_jqc_theme = False4542 #4543 # def get_jqc_button_input(self, message, buttons, options=None):4544 # """4545 # Pop up a jquery-confirm box and return the text of the button clicked.4546 # If running in headless mode, the last button text is returned.4547 # @Params4548 # message: The message to display in the jquery-confirm dialog.4549 # buttons: A list of tuples for text and color.4550 # Example: [("Yes!", "green"), ("No!", "red")]4551 # Available colors: blue, green, red, orange, purple, default, dark.4552 # A simple text string also works: "My Button". (Uses default color.)4553 # options: A list of tuples for options to set.4554 # Example: [("theme", "bootstrap"), ("width", "450px")]4555 # Available theme options: bootstrap, modern, material, supervan,4556 # light, dark, and seamless.4557 # Available colors: (For the BORDER color, NOT the button color.)4558 # "blue", "default", "green", "red", "purple", "orange", "dark".4559 # Example option for changing the border color: ("color", "default")4560 # Width can be set using percent or pixels. Eg: "36.0%", "450px".4561 # """4562 # from seleniumbase.core import jqc_helper4563 #4564 # if message and type(message) is not str:4565 # raise Exception('Expecting a string for arg: "message"!')4566 # if not type(buttons) is list and not type(buttons) is tuple:4567 # raise Exception('Expecting a list or tuple for arg: "button"!')4568 # if len(buttons) < 1:4569 # raise Exception('List "buttons" requires at least one button!')4570 # new_buttons = []4571 # for button in buttons:4572 # if (4573 # (type(button) is list or type(button) is tuple)4574 # and (len(button) == 1)4575 # ):4576 # new_buttons.append(button[0])4577 # elif (4578 # (type(button) is list or type(button) is tuple)4579 # and (len(button) > 1)4580 # ):4581 # new_buttons.append((button[0], str(button[1]).lower()))4582 # else:4583 # new_buttons.append((str(button), ""))4584 # buttons = new_buttons4585 # if options:4586 # for option in options:4587 # if not type(option) is list and not type(option) is tuple:4588 # raise Exception('"options" should be a list of tuples!')4589 # if self.headless or self.xvfb:4590 # return buttons[-1][0]4591 # jqc_helper.jquery_confirm_button_dialog(4592 # self.driver, message, buttons, options4593 # )4594 # self.sleep(0.02)4595 # jf = "document.querySelector('.jconfirm-box').focus();"4596 # try:4597 # self.execute_script(jf)4598 # except Exception:4599 # pass4600 # waiting_for_response = True4601 # while waiting_for_response:4602 # self.sleep(0.05)4603 # jqc_open = self.execute_script(4604 # "return jconfirm.instances.length"4605 # )4606 # if str(jqc_open) == "0":4607 # break4608 # self.sleep(0.1)4609 # status = None4610 # try:4611 # status = self.execute_script("return $jqc_status")4612 # except Exception:4613 # status = self.execute_script(4614 # "return jconfirm.lastButtonText"4615 # )4616 # return status4617 #4618 # def get_jqc_text_input(self, message, button=None, options=None):4619 # """4620 # Pop up a jquery-confirm box and return the text submitted by the input.4621 # If running in headless mode, the text returned is "" by default.4622 # @Params4623 # message: The message to display in the jquery-confirm dialog.4624 # button: A 2-item list or tuple for text and color. Or just the text.4625 # Example: ["Submit", "blue"] -> (default button if not specified)4626 # Available colors: blue, green, red, orange, purple, default, dark.4627 # A simple text string also works: "My Button". (Uses default color.)4628 # options: A list of tuples for options to set.4629 # Example: [("theme", "bootstrap"), ("width", "450px")]4630 # Available theme options: bootstrap, modern, material, supervan,4631 # light, dark, and seamless.4632 # Available colors: (For the BORDER color, NOT the button color.)4633 # "blue", "default", "green", "red", "purple", "orange", "dark".4634 # Example option for changing the border color: ("color", "default")4635 # Width can be set using percent or pixels. Eg: "36.0%", "450px".4636 # """4637 # from seleniumbase.core import jqc_helper4638 #4639 # if message and type(message) is not str:4640 # raise Exception('Expecting a string for arg: "message"!')4641 # if button:4642 # if (4643 # (type(button) is list or type(button) is tuple)4644 # and (len(button) == 1)4645 # ):4646 # button = (str(button[0]), "")4647 # elif (4648 # (type(button) is list or type(button) is tuple)4649 # and (len(button) > 1)4650 # ):4651 # valid_colors = [4652 # "blue",4653 # "default",4654 # "green",4655 # "red",4656 # "purple",4657 # "orange",4658 # "dark",4659 # ]4660 # detected_color = str(button[1]).lower()4661 # if str(button[1]).lower() not in valid_colors:4662 # raise Exception(4663 # "%s is an invalid jquery-confirm button color!\n"4664 # "Select from %s" % (detected_color, valid_colors)4665 # )4666 # button = (str(button[0]), str(button[1]).lower())4667 # else:4668 # button = (str(button), "")4669 # else:4670 # button = ("Submit", "blue")4671 #4672 # if options:4673 # for option in options:4674 # if not type(option) is list and not type(option) is tuple:4675 # raise Exception('"options" should be a list of tuples!')4676 # if self.headless or self.xvfb:4677 # return ""4678 # jqc_helper.jquery_confirm_text_dialog(4679 # self.driver, message, button, options4680 # )4681 # self.sleep(0.02)4682 # jf = "document.querySelector('.jconfirm-box input.jqc_input').focus();"4683 # try:4684 # self.execute_script(jf)4685 # except Exception:4686 # pass4687 # waiting_for_response = True4688 # while waiting_for_response:4689 # self.sleep(0.05)4690 # jqc_open = self.execute_script(4691 # "return jconfirm.instances.length"4692 # )4693 # if str(jqc_open) == "0":4694 # break4695 # self.sleep(0.1)4696 # status = None4697 # try:4698 # status = self.execute_script("return $jqc_input")4699 # except Exception:4700 # status = self.execute_script(4701 # "return jconfirm.lastInputText"4702 # )4703 # return status4704 #4705 # def get_jqc_form_inputs(self, message, buttons, options=None):4706 # """4707 # Pop up a jquery-confirm box and return the input/button texts as tuple.4708 # If running in headless mode, returns the ("", buttons[-1][0]) tuple.4709 # @Params4710 # message: The message to display in the jquery-confirm dialog.4711 # buttons: A list of tuples for text and color.4712 # Example: [("Yes!", "green"), ("No!", "red")]4713 # Available colors: blue, green, red, orange, purple, default, dark.4714 # A simple text string also works: "My Button". (Uses default color.)4715 # options: A list of tuples for options to set.4716 # Example: [("theme", "bootstrap"), ("width", "450px")]4717 # Available theme options: bootstrap, modern, material, supervan,4718 # light, dark, and seamless.4719 # Available colors: (For the BORDER color, NOT the button color.)4720 # "blue", "default", "green", "red", "purple", "orange", "dark".4721 # Example option for changing the border color: ("color", "default")4722 # Width can be set using percent or pixels. Eg: "36.0%", "450px".4723 # """4724 # from seleniumbase.core import jqc_helper4725 #4726 # if message and type(message) is not str:4727 # raise Exception('Expecting a string for arg: "message"!')4728 # if not type(buttons) is list and not type(buttons) is tuple:4729 # raise Exception('Expecting a list or tuple for arg: "button"!')4730 # if len(buttons) < 1:4731 # raise Exception('List "buttons" requires at least one button!')4732 # new_buttons = []4733 # for button in buttons:4734 # if (4735 # (type(button) is list or type(button) is tuple)4736 # and (len(button) == 1)4737 # ):4738 # new_buttons.append(button[0])4739 # elif (4740 # (type(button) is list or type(button) is tuple)4741 # and (len(button) > 1)4742 # ):4743 # new_buttons.append((button[0], str(button[1]).lower()))4744 # else:4745 # new_buttons.append((str(button), ""))4746 # buttons = new_buttons4747 # if options:4748 # for option in options:4749 # if not type(option) is list and not type(option) is tuple:4750 # raise Exception('"options" should be a list of tuples!')4751 # if self.headless or self.xvfb:4752 # return ("", buttons[-1][0])4753 # jqc_helper.jquery_confirm_full_dialog(4754 # self.driver, message, buttons, options4755 # )4756 # self.sleep(0.02)4757 # jf = "document.querySelector('.jconfirm-box input.jqc_input').focus();"4758 # try:4759 # self.execute_script(jf)4760 # except Exception:4761 # pass4762 # waiting_for_response = True4763 # while waiting_for_response:4764 # self.sleep(0.05)4765 # jqc_open = self.execute_script(4766 # "return jconfirm.instances.length"4767 # )4768 # if str(jqc_open) == "0":4769 # break4770 # self.sleep(0.1)4771 # text_status = None4772 # button_status = None4773 # try:4774 # text_status = self.execute_script("return $jqc_input")4775 # button_status = self.execute_script("return $jqc_status")4776 # except Exception:4777 # text_status = self.execute_script(4778 # "return jconfirm.lastInputText"4779 # )4780 # button_status = self.execute_script(4781 # "return jconfirm.lastButtonText"4782 # )4783 # return (text_status, button_status)4784 ############4785 ############4786 def generate_referral(self, start_page, destination_page, selector=None):4787 """This method opens the start_page, creates a referral link there,4788 and clicks on that link, which goes to the destination_page.4789 If a selector is given, clicks that on the destination_page,4790 which can prevent an artificial rise in website bounce-rate.4791 (This generates real traffic for testing analytics software.)"""4792 self.__check_scope__()4793 if not page_utils.is_valid_url(destination_page):4794 raise Exception(4795 "Exception: destination_page {%s} is not a valid URL!"4796 % destination_page4797 )4798 if start_page:4799 if not page_utils.is_valid_url(start_page):4800 raise Exception(4801 "Exception: start_page {%s} is not a valid URL! "4802 "(Use an empty string or None to start from current page.)"4803 % start_page4804 )4805 self.open(start_page)4806 time.sleep(0.08)4807 self.wait_for_ready_state_complete()4808 referral_link = (4809 """<body>"""4810 """<a class='analytics referral test' href='%s' """4811 """style='font-family: Arial,sans-serif; """4812 """font-size: 30px; color: #18a2cd'>"""4813 """Magic Link Button</a></body>""" % destination_page4814 )4815 self.execute_script(4816 '''document.body.outerHTML = \"%s\"''' % referral_link4817 )4818 # Now click the generated button4819 self.click("a.analytics.referral.test", timeout=2)4820 time.sleep(0.15)4821 if selector:4822 self.click(selector)4823 time.sleep(0.15)4824 def generate_traffic(4825 self, start_page, destination_page, loops=1, selector=None4826 ):4827 """Similar to generate_referral(), but can do multiple loops.4828 If a selector is given, clicks that on the destination_page,4829 which can prevent an artificial rise in website bounce-rate."""4830 self.__check_scope__()4831 for loop in range(loops):4832 self.generate_referral(4833 start_page, destination_page, selector=selector4834 )4835 time.sleep(0.05)4836 def generate_referral_chain(self, pages):4837 """Use this method to chain the action of creating button links on4838 one website page that will take you to the next page.4839 (When you want to create a referral to a website for traffic4840 generation without increasing the bounce rate, you'll want to visit4841 at least one additional page on that site with a button click.)"""4842 self.__check_scope__()4843 if not type(pages) is tuple and not type(pages) is list:4844 raise Exception(4845 "Exception: Expecting a list of website pages for chaining!"4846 )4847 if len(pages) < 2:4848 raise Exception(4849 "Exception: At least two website pages required for chaining!"4850 )4851 for page in pages:4852 # Find out if any of the web pages are invalid before continuing4853 if not page_utils.is_valid_url(page):4854 raise Exception(4855 "Exception: Website page {%s} is not a valid URL!" % page4856 )4857 for page in pages:4858 self.generate_referral(None, page)4859 def generate_traffic_chain(self, pages, loops=1):4860 """ Similar to generate_referral_chain(), but for multiple loops. """4861 self.__check_scope__()4862 for loop in range(loops):4863 self.generate_referral_chain(pages)4864 time.sleep(0.05)4865 ############4866 def wait_for_element_present(4867 self, selector, by=By.CSS_SELECTOR, timeout=None4868 ):4869 """Waits for an element to appear in the HTML of a page.4870 The element does not need be visible (it may be hidden)."""4871 self.__check_scope__()4872 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4873 selector, by = self.__recalculate_selector(selector, by)4874 if self.__is_shadow_selector(selector):4875 return self.__wait_for_shadow_element_present(4876 selector, timeout4877 )4878 return page_actions.wait_for_element_present(4879 self.driver, selector, by, timeout4880 )4881 def wait_for_element(self, selector, by=By.CSS_SELECTOR, timeout=None):4882 """Waits for an element to appear in the HTML of a page.4883 The element must be visible (it cannot be hidden)."""4884 self.__check_scope__()4885 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4886 selector, by = self.__recalculate_selector(selector, by)4887 if self.__is_shadow_selector(selector):4888 return self.__wait_for_shadow_element_visible(4889 selector, timeout4890 )4891 return page_actions.wait_for_element_visible(4892 self.driver, selector, by, timeout4893 )4894 def get_element(4895 self,4896 how: SeleniumBy,4897 selector: str = Field(default="", strict=True, min_length=1),4898 timeout: OptionalInt = None,4899 ):4900 """Same as wait_for_element_present() - returns the element.4901 The element does not need be visible (it may be hidden)."""4902 self.__check_scope__()4903 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)4904 return self.wait_for_element_present(how, selector, timeout=timeout)4905 def find_element(self, selector, by=By.CSS_SELECTOR, timeout=None):4906 """ Same as wait_for_element_visible() - returns the element """4907 self.__check_scope__()4908 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4909 return self.wait_for_element_visible(selector, by=by, timeout=timeout)4910 ############4911 def wait_for_text_visible(4912 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None4913 ):4914 self.__check_scope__()4915 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4916 selector, by = self.__recalculate_selector(selector, by)4917 if self.__is_shadow_selector(selector):4918 return self.__wait_for_shadow_text_visible(4919 text, selector, timeout4920 )4921 return page_actions.wait_for_text_visible(4922 self.driver, text, selector, by, timeout, self.browser4923 )4924 def wait_for_exact_text_visible(4925 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None4926 ):4927 self.__check_scope__()4928 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4929 selector, by = self.__recalculate_selector(selector, by)4930 if self.__is_shadow_selector(selector):4931 return self.__wait_for_exact_shadow_text_visible(4932 text, selector, timeout4933 )4934 return page_actions.wait_for_exact_text_visible(4935 self.driver, text, selector, by, timeout, self.browser4936 )4937 def wait_for_text(4938 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None4939 ):4940 """ The shorter version of wait_for_text_visible() """4941 self.__check_scope__()4942 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4943 return self.wait_for_text_visible(4944 text, selector, by=by, timeout=timeout4945 )4946 def find_text(4947 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None4948 ):4949 """ Same as wait_for_text_visible() - returns the element """4950 self.__check_scope__()4951 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)4952 return self.wait_for_text_visible(4953 text, selector, by=by, timeout=timeout4954 )4955 ############4956 def wait_for_link_text_present(self, link_text, timeout=None):4957 self.__check_scope__()4958 if not timeout:4959 timeout = settings.SMALL_TIMEOUT4960 start_ms = time.time() * 1000.04961 stop_ms = start_ms + (timeout * 1000.0)4962 for x in range(int(timeout * 5)):4963 shared_utils.check_if_time_limit_exceeded()4964 try:4965 if not self.is_link_text_present(link_text):4966 raise Exception(4967 "Link text {%s} was not found!" % link_text4968 )4969 return4970 except Exception:4971 now_ms = time.time() * 1000.04972 if now_ms >= stop_ms:4973 break4974 time.sleep(0.2)4975 message = "Link text {%s} was not present after %s seconds!" % (4976 link_text,4977 timeout,4978 )4979 page_actions.timeout_exception("NoSuchElementException", message)4980 def wait_for_partial_link_text_present(self, link_text, timeout=None):4981 self.__check_scope__()4982 if not timeout:4983 timeout = settings.SMALL_TIMEOUT4984 start_ms = time.time() * 1000.04985 stop_ms = start_ms + (timeout * 1000.0)4986 for x in range(int(timeout * 5)):4987 shared_utils.check_if_time_limit_exceeded()4988 try:4989 if not self.is_partial_link_text_present(link_text):4990 raise Exception(4991 "Partial Link text {%s} was not found!" % link_text4992 )4993 return4994 except Exception:4995 now_ms = time.time() * 1000.04996 if now_ms >= stop_ms:4997 break4998 time.sleep(0.2)4999 message = (5000 "Partial Link text {%s} was not present after %s seconds!"5001 "" % (link_text, timeout)5002 )5003 page_actions.timeout_exception("NoSuchElementException", message)5004 def wait_for_link_text_visible(self, link_text, timeout=None):5005 self.__check_scope__()5006 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5007 return self.wait_for_element_visible(5008 link_text, by=By.LINK_TEXT, timeout=timeout5009 )5010 def wait_for_link_text(self, link_text, timeout=None):5011 """ The shorter version of wait_for_link_text_visible() """5012 self.__check_scope__()5013 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5014 return self.wait_for_link_text_visible(link_text, timeout=timeout)5015 def find_link_text(self, link_text, timeout=None):5016 """ Same as wait_for_link_text_visible() - returns the element """5017 self.__check_scope__()5018 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5019 return self.wait_for_link_text_visible(link_text, timeout=timeout)5020 @validate_arguments5021 def wait_for_partial_link_text(self, partial_link_text: str, timeout: OptionalInt = None):5022 self.__check_scope__()5023 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5024 return self.wait_for_element_visible(partial_link_text, by=By.PARTIAL_LINK_TEXT, timeout=timeout)5025 @validate_arguments5026 def find_partial_link_text(self, partial_link_text: str, timeout: OptionalInt = None):5027 """ Same as wait_for_partial_link_text() - returns the element """5028 self.__check_scope__()5029 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5030 return self.wait_for_partial_link_text(partial_link_text, timeout=timeout)5031 def wait_for_element_absent(5032 self, selector, by=By.CSS_SELECTOR, timeout=None5033 ):5034 """Waits for an element to no longer appear in the HTML of a page.5035 A hidden element counts as a present element, which fails this assert.5036 If waiting for elements to be hidden instead of nonexistent,5037 use wait_for_element_not_visible() instead.5038 """5039 self.__check_scope__()5040 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5041 return element_actions.wait_for_element_absent(self.driver, selector, by, timeout)5042 @validate_arguments5043 def wait_for_element_not_visible(5044 self,5045 selector: str = Field(default="", strict=True, min_length=1),5046 timeout: OptionalInt = constants.LARGE_TIMEOUT5047 ):5048 """Waits for an element to no longer be visible on a page.5049 The element can be non-existent in the HTML or hidden on the page5050 to qualify as not visible."""5051 self.__check_scope__()5052 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5053 return element_actions.wait_for_element_not_visible(self.driver, selector, by, timeout)5054 @validate_arguments5055 def wait_for_text_not_visible(5056 self,5057 text: str,5058 how: SeleniumBy,5059 selector: str = Field(default="html", strict=True, min_length=1),5060 timeout: OptionalInt = constants.LARGE_TIMEOUT5061 ):5062 self.__check_scope__()5063 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5064 return element_actions.wait_for_text_not_visible(self.driver, how, selector, timeout)5065 def wait_for_attribute_not_present(5066 self,5067 attribute_name: str,5068 attribute_value: str | bool | float | int | None,5069 how: SeleniumBy,5070 selector: str = Field(default="html", strict=True, min_length=1),5071 timeout: OptionalInt = None5072 ):5073 self.__check_scope__()5074 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5075 return element_actions.wait_for_attribute_not_present(5076 self.driver, attribute_name, attribute_value, how, selector, timeout5077 )5078 5079 def wait_for_and_accept_alert(self, timeout: OptionalInt = None):5080 self.__check_scope__()5081 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5082 return element_actions.wait_for_and_accept_alert(self.driver, timeout)5083 def wait_for_and_dismiss_alert(self, timeout=None):5084 self.__check_scope__()5085 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5086 return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)5087 def wait_for_and_switch_to_alert(self, timeout=None):5088 self.__check_scope__()5089 timeout = self.get_timeout(timeout, constants.LARGE_TIMEOUT)5090 return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)5091 def accept_alert(self, timeout=None):5092 """ Same as wait_for_and_accept_alert(), but smaller default T_O """5093 self.__check_scope__()5094 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)5095 return page_actions.wait_for_and_accept_alert(self.driver, timeout)5096 def dismiss_alert(self, timeout=None):5097 """ Same as wait_for_and_dismiss_alert(), but smaller default T_O """5098 self.__check_scope__()5099 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)5100 return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)5101 def switch_to_alert(self, timeout=None):5102 """ Same as wait_for_and_switch_to_alert(), but smaller default T_O """5103 self.__check_scope__()5104 timeout = self.get_timeout(timeout, constants.SMALL_TIMEOUT)5105 return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)5106 def __process_visual_baseline_logs(self):5107 """ Save copies of baseline PNGs in "./latest_logs" during failures.5108 Also create a side_by_side.html file for visual comparisons. """5109 test_logpath = os.path.join(self.log_path, self.__get_test_id())5110 for baseline_copy_tuple in self.__visual_baseline_copies:5111 baseline_path = baseline_copy_tuple[0]5112 baseline_copy_name = baseline_copy_tuple[1]5113 b_c_alt_name = baseline_copy_tuple[2]5114 latest_png_path = baseline_copy_tuple[3]5115 latest_copy_name = baseline_copy_tuple[4]5116 l_c_alt_name = baseline_copy_tuple[5]5117 baseline_copy_path = os.path.join(test_logpath, baseline_copy_name)5118 b_c_alt_path = os.path.join(test_logpath, b_c_alt_name)5119 latest_copy_path = os.path.join(test_logpath, latest_copy_name)5120 l_c_alt_path = os.path.join(test_logpath, l_c_alt_name)5121 if len(self.__visual_baseline_copies) == 1:5122 baseline_copy_path = b_c_alt_path5123 latest_copy_path = l_c_alt_path5124 if (5125 os.path.exists(baseline_path)5126 and not os.path.exists(baseline_copy_path)5127 ):5128 self.__create_log_path_as_needed(test_logpath)5129 shutil.copy(baseline_path, baseline_copy_path)5130 if (5131 os.path.exists(latest_png_path)5132 and not os.path.exists(latest_copy_path)5133 ):5134 self.__create_log_path_as_needed(test_logpath)5135 shutil.copy(latest_png_path, latest_copy_path)5136 if len(self.__visual_baseline_copies) != 1:5137 return # Only possible when deferred visual asserts are used5138 head = (5139 '<head><meta charset="utf-8">'5140 '<meta name="viewport" content="shrink-to-fit=no">'5141 '<link rel="shortcut icon" href="%s">'5142 "<title>Visual Comparison</title>"5143 "</head>"5144 % (constants.SideBySide.SIDE_BY_SIDE_PNG)5145 )5146 table_html = (5147 '<table border="3px solid #E6E6E6;" width="100%;" padding: 12px;'5148 ' font-size="16px;" text-align="left;" id="results-table"'5149 ' style="background-color: #FAFAFA;">'5150 '<thead id="results-table-head">'5151 '<tr>'5152 '<th style="background-color: rgba(0, 128, 0, 0.25);"'5153 ' col="baseline">Baseline Screenshot</th>'5154 '<th style="background-color: rgba(128, 0, 0, 0.25);"'5155 ' col="failure">Visual Diff Failure Screenshot</th>'5156 "</tr></thead>"5157 )5158 row = (5159 '<tbody class="compare results-table-row">'5160 '<tr style="background-color: #F4F4FE;">'5161 '<td><img src="%s" width="100%%" /></td>'5162 '<td><img src="%s" width="100%%" /></td>'5163 "</tr></tbody>"5164 "" % ("baseline.png", "baseline_diff.png")5165 )5166 header_text = "SeleniumBase Visual Comparison"5167 header = '<h3 align="center">%s</h3>' % header_text5168 table_html += row5169 table_html += "</table>"5170 footer = "<br /><b>Last updated:</b> "5171 timestamp, the_date, the_time = log_helper.get_master_time()5172 last_updated = "%s at %s" % (the_date, the_time)5173 footer = footer + "%s" % last_updated5174 gen_by = (5175 '<p><div>Generated by: <b><a href="https://seleniumbase.io/">'5176 "SeleniumBase</a></b></div></p><p></p>"5177 )5178 footer = footer + gen_by5179 the_html = (5180 '<html lang="en">'5181 + head5182 + '<body style="background-color: #FCFCF4;">'5183 + header5184 + table_html5185 + footer5186 + "</body>"5187 )5188 file_path = os.path.join(test_logpath, constants.SideBySide.HTML_FILE)5189 out_file = codecs.open(file_path, "w+", encoding="utf-8")5190 out_file.writelines(the_html)5191 out_file.close()5192 def check_window(5193 self,5194 name="default",5195 level=0,5196 baseline=False,5197 check_domain=True,5198 full_diff=False,5199 ):5200 """*** Automated Visual Testing with SeleniumBase ***5201 The first time a test calls self.check_window() for a unique "name"5202 parameter provided, it will set a visual baseline, meaning that it5203 creates a folder, saves the URL to a file, saves the current window5204 screenshot to a file, and creates the following three files5205 with the listed data saved:5206 tags_level1.txt -> HTML tags from the window5207 tags_level2.txt -> HTML tags + attributes from the window5208 tags_level3.txt -> HTML tags + attributes/values from the window5209 Baseline folders are named based on the test name and the name5210 parameter passed to self.check_window(). The same test can store5211 multiple baseline folders.5212 If the baseline is being set/reset, the "level" doesn't matter.5213 After the first run of self.check_window(), it will compare the5214 HTML tags of the latest window to the one from the initial run.5215 Here's how the level system works:5216 * level=0 ->5217 DRY RUN ONLY - Will perform comparisons to the baseline (and5218 print out any differences that are found) but5219 won't fail the test even if differences exist.5220 * level=1 ->5221 HTML tags are compared to tags_level1.txt5222 * level=2 ->5223 HTML tags are compared to tags_level1.txt and5224 HTML tags/attributes are compared to tags_level2.txt5225 * level=3 ->5226 HTML tags are compared to tags_level1.txt and5227 HTML tags + attributes are compared to tags_level2.txt and5228 HTML tags + attributes/values are compared to tags_level3.txt5229 As shown, Level-3 is the most strict, Level-1 is the least strict.5230 If the comparisons from the latest window to the existing baseline5231 don't match, the current test will fail, except for Level-0 tests.5232 You can reset the visual baseline on the command line by using:5233 --visual_baseline5234 As long as "--visual_baseline" is used on the command line while5235 running tests, the self.check_window() method cannot fail because5236 it will rebuild the visual baseline rather than comparing the html5237 tags of the latest run to the existing baseline. If there are any5238 expected layout changes to a website that you're testing, you'll5239 need to reset the baseline to prevent unnecessary failures.5240 self.check_window() will fail with "Page Domain Mismatch Failure"5241 if the page domain doesn't match the domain of the baseline,5242 unless "check_domain" is set to False when calling check_window().5243 If you want to use self.check_window() to compare a web page to5244 a later version of itself from within the same test run, you can5245 add the parameter "baseline=True" to the first time you call5246 self.check_window() in a test to use that as the baseline. This5247 only makes sense if you're calling self.check_window() more than5248 once with the same name parameter in the same test.5249 If "full_diff" is set to False, the error output will only5250 include the first differing element in the list comparison.5251 Set "full_diff" to True if you want to see the full output.5252 Automated Visual Testing with self.check_window() is not very5253 effective for websites that have dynamic content that changes5254 the layout and structure of web pages. For those, you're much5255 better off using regular SeleniumBase functional testing.5256 Example usage:5257 self.check_window(name="testing", level=0)5258 self.check_window(name="xkcd_home", level=1)5259 self.check_window(name="github_page", level=2)5260 self.check_window(name="wikipedia_page", level=3)5261 """5262 self.wait_for_ready_state_complete()5263 if level == "0":5264 level = 05265 if level == "1":5266 level = 15267 if level == "2":5268 level = 25269 if level == "3":5270 level = 35271 if level != 0 and level != 1 and level != 2 and level != 3:5272 raise Exception('Parameter "level" must be set to 0, 1, 2, or 3!')5273 if self.demo_mode:5274 message = (5275 "WARNING: Using check_window() from Demo Mode may lead "5276 "to unexpected results caused by Demo Mode HTML changes."5277 )5278 logging.info(message)5279 test_id = self.__get_display_id().split("::")[-1]5280 if not name or len(name) < 1:5281 name = "default"5282 name = str(name)5283 from seleniumbase.core import visual_helper5284 visual_helper.visual_baseline_folder_setup()5285 baseline_dir = constants.VisualBaseline.STORAGE_FOLDER5286 visual_baseline_path = baseline_dir + "/" + test_id + "/" + name5287 page_url_file = visual_baseline_path + "/page_url.txt"5288 baseline_png = "baseline.png"5289 baseline_png_path = visual_baseline_path + "/%s" % baseline_png5290 latest_png = "latest.png"5291 latest_png_path = visual_baseline_path + "/%s" % latest_png5292 level_1_file = visual_baseline_path + "/tags_level_1.txt"5293 level_2_file = visual_baseline_path + "/tags_level_2.txt"5294 level_3_file = visual_baseline_path + "/tags_level_3.txt"5295 set_baseline = False5296 if baseline or self.visual_baseline:5297 set_baseline = True5298 if not os.path.exists(visual_baseline_path):5299 set_baseline = True5300 try:5301 os.makedirs(visual_baseline_path)5302 except Exception:5303 pass # Only reachable during multi-threaded test runs5304 if not os.path.exists(page_url_file):5305 set_baseline = True5306 if not os.path.exists(baseline_png_path):5307 set_baseline = True5308 if not os.path.exists(level_1_file):5309 set_baseline = True5310 if not os.path.exists(level_2_file):5311 set_baseline = True5312 if not os.path.exists(level_3_file):5313 set_baseline = True5314 page_url = self.get_current_url()5315 soup = self.get_beautiful_soup()5316 html_tags = soup.body.find_all()5317 level_1 = [[tag.name] for tag in html_tags]5318 level_1 = json.loads(json.dumps(level_1)) # Tuples become lists5319 level_2 = [[tag.name, sorted(tag.attrs.keys())] for tag in html_tags]5320 level_2 = json.loads(json.dumps(level_2)) # Tuples become lists5321 level_3 = [[tag.name, sorted(tag.attrs.items())] for tag in html_tags]5322 level_3 = json.loads(json.dumps(level_3)) # Tuples become lists5323 if set_baseline:5324 self.save_screenshot(5325 baseline_png, visual_baseline_path, selector="body"5326 )5327 out_file = codecs.open(page_url_file, "w+", encoding="utf-8")5328 out_file.writelines(page_url)5329 out_file.close()5330 out_file = codecs.open(level_1_file, "w+", encoding="utf-8")5331 out_file.writelines(json.dumps(level_1))5332 out_file.close()5333 out_file = codecs.open(level_2_file, "w+", encoding="utf-8")5334 out_file.writelines(json.dumps(level_2))5335 out_file.close()5336 out_file = codecs.open(level_3_file, "w+", encoding="utf-8")5337 out_file.writelines(json.dumps(level_3))5338 out_file.close()5339 baseline_path = os.path.join(visual_baseline_path, baseline_png)5340 baseline_copy_name = "baseline_%s.png" % name5341 b_c_alt_name = "baseline.png"5342 latest_copy_name = "baseline_diff_%s.png" % name5343 l_c_alt_name = "baseline_diff.png"5344 baseline_copy_tuple = (5345 baseline_path, baseline_copy_name, b_c_alt_name,5346 latest_png_path, latest_copy_name, l_c_alt_name,5347 )5348 self.__visual_baseline_copies.append(baseline_copy_tuple)5349 if not set_baseline:5350 self.save_screenshot(5351 latest_png, visual_baseline_path, selector="body"5352 )5353 f = open(page_url_file, "r")5354 page_url_data = f.read().strip()5355 f.close()5356 f = open(level_1_file, "r")5357 level_1_data = json.loads(f.read())5358 f.close()5359 f = open(level_2_file, "r")5360 level_2_data = json.loads(f.read())5361 f.close()5362 f = open(level_3_file, "r")5363 level_3_data = json.loads(f.read())5364 f.close()5365 domain_fail = (5366 "\n*\nPage Domain Mismatch Failure: "5367 "Current Page Domain doesn't match the Page Domain of the "5368 "Baseline! Can't compare two completely different sites! "5369 "Run with --visual_baseline to reset the baseline!"5370 )5371 level_1_failure = (5372 "\n*\n*** Exception: <Level 1> Visual Diff Failure:\n"5373 "* HTML tags don't match the baseline!"5374 )5375 level_2_failure = (5376 "\n*\n*** Exception: <Level 2> Visual Diff Failure:\n"5377 "* HTML tag attribute names don't match the baseline!"5378 )5379 level_3_failure = (5380 "\n*\n*** Exception: <Level 3> Visual Diff Failure:\n"5381 "* HTML tag attribute values don't match the baseline!"5382 )5383 page_domain = self.get_domain_url(page_url)5384 page_data_domain = self.get_domain_url(page_url_data)5385 unittest.TestCase.maxDiff = 32005386 if level != 0 and check_domain:5387 self.assertEqual(page_data_domain, page_domain, domain_fail)5388 unittest.TestCase.maxDiff = 6400 # Use `None` for no limit5389 if level == 3:5390 if not full_diff:5391 self.__assert_eq(level_3_data, level_3, level_3_failure)5392 else:5393 self.assertEqual(level_3_data, level_3, level_3_failure)5394 unittest.TestCase.maxDiff = 32005395 if level == 2:5396 if not full_diff:5397 self.__assert_eq(level_2_data, level_2, level_2_failure)5398 else:5399 self.assertEqual(level_2_data, level_2, level_2_failure)5400 if level == 1:5401 if not full_diff:5402 self.__assert_eq(level_1_data, level_1, level_1_failure)5403 else:5404 self.assertEqual(level_1_data, level_1, level_1_failure)5405 unittest.TestCase.maxDiff = 6400 # Use `None` for no limit5406 if level == 0:5407 try:5408 unittest.TestCase.maxDiff = 32005409 if check_domain:5410 self.assertEqual(5411 page_domain, page_data_domain, domain_fail5412 )5413 try:5414 if not full_diff:5415 self.__assert_eq(5416 level_1_data, level_1, level_1_failure5417 )5418 else:5419 self.assertEqual(5420 level_1_data, level_1, level_1_failure5421 )5422 except Exception as e:5423 print(e)5424 try:5425 if not full_diff:5426 self.__assert_eq(5427 level_2_data, level_2, level_2_failure5428 )5429 else:5430 self.assertEqual(5431 level_2_data, level_2, level_2_failure5432 )5433 except Exception as e:5434 print(e)5435 unittest.TestCase.maxDiff = 6400 # Use `None` for no limit5436 if not full_diff:5437 self.__assert_eq(5438 level_3_data, level_3, level_3_failure5439 )5440 else:5441 self.assertEqual(5442 level_3_data, level_3, level_3_failure5443 )5444 except Exception as e:5445 print(e) # Level-0 Dry Run (Only print the differences)5446 unittest.TestCase.maxDiff = None # Reset unittest.TestCase.maxDiff5447 # Since the check passed, do not save an extra copy of the baseline5448 del self.__visual_baseline_copies[-1] # .pop() returns the element5449 def __get_exception_message(self):5450 """This method extracts the message from an exception if there5451 was an exception that occurred during the test, assuming5452 that the exception was in a try/except block and not thrown."""5453 exception_info = sys.exc_info()[1]5454 if hasattr(exception_info, "msg"):5455 exc_message = exception_info.msg5456 elif hasattr(exception_info, "message"):5457 exc_message = exception_info.message5458 else:5459 exc_message = sys.exc_info()5460 return exc_message5461 def __add_deferred_assert_failure(self):5462 """ Add a deferred_assert failure to a list for future processing. """5463 self.__check_scope__()5464 current_url = self.driver.current_url5465 message = self.__get_exception_message()5466 self.__deferred_assert_failures.append(5467 "CHECK #%s: (%s) %s\n"5468 % (self.__deferred_assert_count, current_url, message)5469 )5470 ############5471 def deferred_assert_element(5472 self, selector, by=By.CSS_SELECTOR, timeout=None5473 ):5474 """A non-terminating assertion for an element on a page.5475 Failures will be saved until the process_deferred_asserts()5476 method is called from inside a test, likely at the end of it."""5477 self.__check_scope__()5478 timeout = self.get_timeout(timeout, constants.MINI_TIMEOUT)5479 self.__deferred_assert_count += 15480 try:5481 url = self.get_current_url()5482 if url == self.__last_url_of_deferred_assert:5483 timeout = 1 # Was already on page (full wait not needed)5484 else:5485 self.__last_url_of_deferred_assert = url5486 except Exception:5487 pass5488 try:5489 self.wait_for_element_visible(selector, by=by, timeout=timeout)5490 return True5491 except Exception:5492 self.__add_deferred_assert_failure()5493 return False5494 def deferred_assert_text(5495 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None5496 ):5497 """A non-terminating assertion for text from an element on a page.5498 Failures will be saved until the process_deferred_asserts()5499 method is called from inside a test, likely at the end of it."""5500 self.__check_scope__()5501 timeout = self.get_timeout(timeout, constants.MINI_TIMEOUT)5502 self.__deferred_assert_count += 15503 try:5504 url = self.get_current_url()5505 if url == self.__last_url_of_deferred_assert:5506 timeout = 1 # Was already on page (full wait not needed)5507 else:5508 self.__last_url_of_deferred_assert = url5509 except Exception:5510 pass5511 try:5512 self.wait_for_text_visible(text, selector, by=by, timeout=timeout)5513 return True5514 except Exception:5515 self.__add_deferred_assert_failure()5516 return False5517 def deferred_assert_exact_text(5518 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None5519 ):5520 """A non-terminating assertion for exact text from an element.5521 Failures will be saved until the process_deferred_asserts()5522 method is called from inside a test, likely at the end of it."""5523 self.__check_scope__()5524 timeout = self.get_timeout(timeout, constants.MINI_TIMEOUT)5525 self.__deferred_assert_count += 15526 try:5527 url = self.get_current_url()5528 if url == self.__last_url_of_deferred_assert:5529 timeout = 1 # Was already on page (full wait not needed)5530 else:5531 self.__last_url_of_deferred_assert = url5532 except Exception:5533 pass5534 try:5535 self.wait_for_exact_text_visible(5536 text, selector, by=by, timeout=timeout5537 )5538 return True5539 except Exception:5540 self.__add_deferred_assert_failure()5541 return False5542 def deferred_check_window(5543 self,5544 name="default",5545 level=0,5546 baseline=False,5547 check_domain=True,5548 full_diff=False,5549 ):5550 """A non-terminating assertion for the check_window() method.5551 Failures will be saved until the process_deferred_asserts()5552 method is called from inside a test, likely at the end of it."""5553 self.__check_scope__()5554 self.__deferred_assert_count += 15555 try:5556 self.check_window(5557 name=name,5558 level=level,5559 baseline=baseline,5560 check_domain=check_domain,5561 full_diff=full_diff,5562 )5563 return True5564 except Exception:5565 self.__add_deferred_assert_failure()5566 return False5567 def process_deferred_asserts(self, print_only=False):5568 """To be used with any test that uses deferred_asserts, which are5569 non-terminating verifications that only raise exceptions5570 after this method is called.5571 This is useful for pages with multiple elements to be checked when5572 you want to find as many bugs as possible in a single test run5573 before having all the exceptions get raised simultaneously.5574 Might be more useful if this method is called after processing all5575 the deferred asserts on a single html page so that the failure5576 screenshot matches the location of the deferred asserts.5577 If "print_only" is set to True, the exception won't get raised."""5578 if self.__deferred_assert_failures:5579 exception_output = ""5580 exception_output += "\n***** DEFERRED ASSERTION FAILURES:\n"5581 exception_output += "TEST: %s\n\n" % self.id()5582 all_failing_checks = self.__deferred_assert_failures5583 self.__deferred_assert_failures = []5584 for tb in all_failing_checks:5585 exception_output += "%s\n" % tb5586 if print_only:5587 print(exception_output)5588 else:5589 raise Exception(exception_output.replace("\\n", "\n"))5590 ############5591 # Alternate naming scheme for the "deferred_assert" methods.5592 def delayed_assert_element(5593 self, selector, by=By.CSS_SELECTOR, timeout=None5594 ):5595 """ Same as self.deferred_assert_element() """5596 return self.deferred_assert_element(5597 selector=selector, by=by, timeout=timeout5598 )5599 def delayed_assert_text(5600 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None5601 ):5602 """ Same as self.deferred_assert_text() """5603 return self.deferred_assert_text(5604 text=text, selector=selector, by=by, timeout=timeout5605 )5606 def delayed_assert_exact_text(5607 self, text, selector="html", by=By.CSS_SELECTOR, timeout=None5608 ):5609 """ Same as self.deferred_assert_exact_text() """5610 return self.deferred_assert_exact_text(5611 text=text, selector=selector, by=by, timeout=timeout5612 )5613 def delayed_check_window(5614 self,5615 name="default",5616 level=0,5617 baseline=False,5618 check_domain=True,5619 full_diff=False5620 ):5621 """ Same as self.deferred_check_window() """5622 return self.deferred_check_window(5623 name=name,5624 level=level,5625 baseline=baseline,5626 check_domain=check_domain,5627 full_diff=full_diff,5628 )5629 def process_delayed_asserts(self, print_only=False):5630 """ Same as self.process_deferred_asserts() """5631 self.process_deferred_asserts(print_only=print_only)5632 ############5633 def __js_click(self, selector, by=By.CSS_SELECTOR):5634 """ Clicks an element using pure JS. Does not use jQuery. """5635 selector, by = self.__recalculate_selector(selector, by)5636 css_selector = self.convert_to_css_selector(selector, by=by)5637 css_selector = re.escape(css_selector) # Add "\\" to special chars5638 css_selector = self.__escape_quotes_if_needed(css_selector)5639 script = (5640 """var simulateClick = function (elem) {5641 var evt = new MouseEvent('click', {5642 bubbles: true,5643 cancelable: true,5644 view: window5645 });5646 var canceled = !elem.dispatchEvent(evt);5647 };5648 var someLink = document.querySelector('%s');5649 simulateClick(someLink);"""5650 % css_selector5651 )5652 self.execute_script(script)5653 def __js_click_all(self, selector, by=By.CSS_SELECTOR):5654 """ Clicks all matching elements using pure JS. (No jQuery) """5655 selector, by = self.__recalculate_selector(selector, by)5656 css_selector = self.convert_to_css_selector(selector, by=by)5657 css_selector = re.escape(css_selector) # Add "\\" to special chars5658 css_selector = self.__escape_quotes_if_needed(css_selector)5659 script = (5660 """var simulateClick = function (elem) {5661 var evt = new MouseEvent('click', {5662 bubbles: true,5663 cancelable: true,5664 view: window5665 });5666 var canceled = !elem.dispatchEvent(evt);5667 };5668 var $elements = document.querySelectorAll('%s');5669 var index = 0, length = $elements.length;5670 for(; index < length; index++){5671 simulateClick($elements[index]);}"""5672 % css_selector5673 )5674 self.execute_script(script)5675 def __jquery_slow_scroll_to(self, selector, by=By.CSS_SELECTOR):5676 selector, by = self.__recalculate_selector(selector, by)5677 element = self.wait_for_element_present(5678 selector, by=by, timeout=settings.SMALL_TIMEOUT5679 )5680 dist = js_utils.get_scroll_distance_to_element(self.driver, element)5681 time_offset = 05682 try:5683 if dist and abs(dist) > constants.Values.SSMD:5684 time_offset = int(5685 float(abs(dist) - constants.Values.SSMD) / 12.55686 )5687 if time_offset > 950:5688 time_offset = 9505689 except Exception:5690 time_offset = 05691 scroll_time_ms = 550 + time_offset5692 sleep_time = 0.625 + (float(time_offset) / 1000.0)5693 selector = self.convert_to_css_selector(selector, by=by)5694 selector = self.__make_css_match_first_element_only(selector)5695 scroll_script = (5696 """jQuery([document.documentElement, document.body]).animate({"""5697 """scrollTop: jQuery('%s').offset().top - 130}, %s);"""5698 % (selector, scroll_time_ms)5699 )5700 if js_utils.is_jquery_activated(self.driver):5701 self.execute_script(scroll_script)5702 else:5703 self.__slow_scroll_to_element(element)5704 self.sleep(sleep_time)5705 def __jquery_click(self, selector, by=By.CSS_SELECTOR):5706 """ Clicks an element using jQuery. Different from using pure JS. """5707 self.wait_for_element_present(5708 selector, by=by, timeout=settings.SMALL_TIMEOUT5709 )5710 selector = self.convert_to_css_selector(selector, by=by)5711 selector = self.__make_css_match_first_element_only(selector)5712 click_script = """jQuery('%s')[0].click();""" % selector5713 self.safe_execute_script(click_script)5714 def __get_href_from_link_text(self, link_text, hard_fail=True):5715 href = self.get_link_attribute(link_text, "href", hard_fail)5716 if not href:5717 return None5718 if href.startswith("//"):5719 link = "http:" + href5720 elif href.startswith("/"):5721 url = self.driver.current_url5722 domain_url = self.get_domain_url(url)5723 link = domain_url + href5724 else:5725 link = href5726 return link5727 def __click_dropdown_link_text(self, link_text, link_css):5728 """ When a link may be hidden under a dropdown menu, use this. """5729 soup = self.get_beautiful_soup()5730 drop_down_list = []5731 for item in soup.select("li[class]"):5732 drop_down_list.append(item)5733 csstype = link_css.split("[")[1].split("=")[0]5734 for item in drop_down_list:5735 item_text_list = item.text.split("\n")5736 if link_text in item_text_list and csstype in item.decode():5737 dropdown_css = ""5738 try:5739 for css_class in item["class"]:5740 dropdown_css += "."5741 dropdown_css += css_class5742 except Exception:5743 continue5744 dropdown_css = item.name + dropdown_css5745 matching_dropdowns = self.find_visible_elements(dropdown_css)5746 for dropdown in matching_dropdowns:5747 # The same class names might be used for multiple dropdowns5748 if dropdown.is_displayed():5749 try:5750 try:5751 page_actions.hover_element(5752 self.driver,5753 dropdown,5754 )5755 except Exception:5756 # If hovering fails, driver is likely outdated5757 # Time to go directly to the hidden link text5758 self.open(5759 self.__get_href_from_link_text(link_text)5760 )5761 return True5762 page_actions.hover_element_and_click(5763 self.driver,5764 dropdown,5765 link_text,5766 click_by=By.LINK_TEXT,5767 timeout=0.12,5768 )5769 return True5770 except Exception:5771 pass5772 return False5773 def __get_href_from_partial_link_text(self, link_text, hard_fail=True):5774 href = self.get_partial_link_text_attribute(5775 link_text, "href", hard_fail5776 )5777 if not href:5778 return None5779 if href.startswith("//"):5780 link = "http:" + href5781 elif href.startswith("/"):5782 url = self.driver.current_url5783 domain_url = self.get_domain_url(url)5784 link = domain_url + href5785 else:5786 link = href5787 return link5788 def __click_dropdown_partial_link_text(self, link_text, link_css):5789 """ When a partial link may be hidden under a dropdown, use this. """5790 soup = self.get_beautiful_soup()5791 drop_down_list = []5792 for item in soup.select("li[class]"):5793 drop_down_list.append(item)5794 csstype = link_css.split("[")[1].split("=")[0]5795 for item in drop_down_list:5796 item_text_list = item.text.split("\n")5797 if link_text in item_text_list and csstype in item.decode():5798 dropdown_css = ""5799 try:5800 for css_class in item["class"]:5801 dropdown_css += "."5802 dropdown_css += css_class5803 except Exception:5804 continue5805 dropdown_css = item.name + dropdown_css5806 matching_dropdowns = self.find_visible_elements(dropdown_css)5807 for dropdown in matching_dropdowns:5808 # The same class names might be used for multiple dropdowns5809 if dropdown.is_displayed():5810 try:5811 try:5812 page_actions.hover_element(5813 self.driver, dropdown5814 )5815 except Exception:5816 # If hovering fails, driver is likely outdated5817 # Time to go directly to the hidden link text5818 self.open(5819 self.__get_href_from_partial_link_text(5820 link_text5821 )5822 )5823 return True5824 page_actions.hover_element_and_click(5825 self.driver,5826 dropdown,5827 link_text,5828 click_by=By.LINK_TEXT,5829 timeout=0.12,5830 )5831 return True5832 except Exception:5833 pass5834 return False5835 def __looks_like_a_page_url(self, url):5836 """Returns True if the url parameter looks like a URL. This method5837 is slightly more lenient than page_utils.is_valid_url(url) due to5838 possible typos when calling self.get(url), which will try to5839 navigate to the page if a URL is detected, but will instead call5840 self.get_element(URL_AS_A_SELECTOR) if the input in not a URL."""5841 if (5842 url.startswith("http:")5843 or url.startswith("https:")5844 or url.startswith("://")5845 or url.startswith("chrome:")5846 or url.startswith("about:")5847 or url.startswith("data:")5848 or url.startswith("file:")5849 or url.startswith("edge:")5850 or url.startswith("opera:")5851 or url.startswith("view-source:")5852 ):5853 return True5854 else:5855 return False5856 def __make_css_match_first_element_only(self, selector):5857 # Only get the first match5858 return page_utils.make_css_match_first_element_only(selector)5859 def __demo_mode_scroll_if_active(self, selector, by):5860 if self.demo_mode:5861 self.slow_scroll_to(selector, by=by)5862 def __demo_mode_highlight_if_active(self, selector, by):5863 if self.demo_mode:5864 # Includes self.slow_scroll_to(selector, by=by) by default5865 self.highlight(selector, by=by)5866 elif self.slow_mode:5867 # Just do the slow scroll part of the highlight() method5868 time.sleep(0.08)5869 element = self.wait_for_element_visible(5870 selector, by=by, timeout=settings.SMALL_TIMEOUT5871 )5872 try:5873 scroll_distance = js_utils.get_scroll_distance_to_element(5874 self.driver, element5875 )5876 if abs(scroll_distance) > settings.SSMD:5877 self.__jquery_slow_scroll_to(selector, by)5878 else:5879 self.__slow_scroll_to_element(element)5880 except (StaleElementReferenceException, ElementNotInteractableException):5881 self.wait_for_ready_state_complete()5882 time.sleep(0.12)5883 element = self.wait_for_element_visible(5884 selector, by=by, timeout=settings.SMALL_TIMEOUT5885 )5886 self.__slow_scroll_to_element(element)5887 time.sleep(0.12)5888 def __scroll_to_element(self, element, selector=None, by=By.CSS_SELECTOR):5889 success = js_utils.scroll_to_element(self.driver, element)5890 if not success and selector:5891 self.wait_for_ready_state_complete()5892 element = element_actions.wait_for_element_visible(5893 self.driver, selector, by, timeout=settings.SMALL_TIMEOUT5894 )5895 self._demo_mode_pause_if_active(tiny=True)5896 def __slow_scroll_to_element(self, element):5897 try:5898 js_utils.slow_scroll_to_element(self.driver, element, self.browser)5899 except Exception:5900 # Scroll to the element instantly if the slow scroll fails5901 js_utils.scroll_to_element(self.driver, element)5902 def __highlight_with_assert_success(5903 self, message, selector, by=By.CSS_SELECTOR5904 ):5905 selector, by = self.__recalculate_selector(selector, by, xp_ok=False)5906 element = self.wait_for_element_visible(5907 selector, by=by, timeout=settings.SMALL_TIMEOUT5908 )5909 try:5910 scroll_distance = js_utils.get_scroll_distance_to_element(5911 self.driver, element5912 )5913 if abs(scroll_distance) > constants.Values.SSMD:5914 self.__jquery_slow_scroll_to(selector, by)5915 else:5916 self.__slow_scroll_to_element(element)5917 except Exception:5918 self.wait_for_ready_state_complete()5919 time.sleep(0.12)5920 element = self.wait_for_element_visible(5921 selector, by=by, timeout=settings.SMALL_TIMEOUT5922 )5923 self.__slow_scroll_to_element(element)5924 try:5925 selector = self.convert_to_css_selector(selector, by=by)5926 except Exception:5927 # Don't highlight if can't convert to CSS_SELECTOR5928 return5929 o_bs = "" # original_box_shadow5930 try:5931 style = element.get_attribute("style")5932 except Exception:5933 self.wait_for_ready_state_complete()5934 time.sleep(0.12)5935 element = self.wait_for_element_visible(5936 selector, by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT5937 )5938 style = element.get_attribute("style")5939 if style:5940 if "box-shadow: " in style:5941 box_start = style.find("box-shadow: ")5942 box_end = style.find(";", box_start) + 15943 original_box_shadow = style[box_start:box_end]5944 o_bs = original_box_shadow5945 if ":contains" not in selector and ":first" not in selector:5946 selector = re.escape(selector)5947 selector = self.__escape_quotes_if_needed(selector)5948 self.__highlight_with_js_2(message, selector, o_bs)5949 else:5950 selector = self.__make_css_match_first_element_only(selector)5951 selector = re.escape(selector)5952 selector = self.__escape_quotes_if_needed(selector)5953 try:5954 self.__highlight_with_jquery_2(message, selector, o_bs)5955 except Exception:5956 pass # JQuery probably couldn't load. Skip highlighting.5957 time.sleep(0.065)5958 def __highlight_with_js_2(self, message, selector, o_bs):5959 duration = self.message_duration5960 if not duration:5961 duration = settings.DEFAULT_MESSAGE_DURATION5962 if (self.headless or self.xvfb) and float(duration) > 0.75:5963 duration = 0.755964 js_utils.highlight_with_js_2(5965 self.driver, message, selector, o_bs, duration5966 )5967 def __highlight_with_jquery_2(self, message, selector, o_bs):5968 duration = self.message_duration5969 if not duration:5970 duration = settings.DEFAULT_MESSAGE_DURATION5971 if (self.headless or self.xvfb) and float(duration) > 0.75:5972 duration = 0.755973 js_utils.highlight_with_jquery_2(5974 self.driver, message, selector, o_bs, duration5975 )5976 ############5977 from seleniumbase.common import decorators5978 @decorators.deprecated("You should use re.escape() instead.")5979 def jq_format(self, code):5980 # DEPRECATED - re.escape() already performs the intended action.5981 return js_utils._jq_format(code)5982 ############5983 def setup(self):5984 """5985 Be careful if a subclass of BaseCase overrides setUp()5986 You'll need to add the following line to the subclass setUp() method:5987 super(SubClassOfBaseCase, self).setUp()5988 """5989 if self._called_setup:5990 # This test already called setUp()5991 return5992 self._called_setup = True5993 self._called_teardown = False5994 if self.is_pytest:5995 self.headless_active = False5996 self.mobile_emulator = sb_config.mobile_emulator5997 self.device_metrics = sb_config.device_metrics5998 self.cap_file = sb_config.cap_file5999 self.cap_string = sb_config.cap_string6000 self.settings_file = sb_config.settings_file6001 self.database_env = sb_config.database_env6002 self.message_duration = sb_config.message_duration6003 self.js_checking_on = sb_config.js_checking_on6004 self.ad_block_on = sb_config.ad_block_on6005 self.block_images = sb_config.block_images6006 self.chromium_arg = sb_config.chromium_arg6007 self.firefox_arg = sb_config.firefox_arg6008 self.firefox_pref = sb_config.firefox_pref6009 self.verify_delay = sb_config.verify_delay6010 self.recorder_mode = sb_config.recorder_mode6011 self.recorder_ext = sb_config.recorder_mode6012 self.disable_csp = sb_config.disable_csp6013 self.disable_ws = sb_config.disable_ws6014 self.enable_ws = sb_config.enable_ws6015 if not self.config.getoption("disable_ws", False):6016 self.config.option.enable_ws = True6017 self.enable_sync = sb_config.enable_sync6018 self.use_auto_ext = sb_config.use_auto_ext6019 self.no_sandbox = sb_config.no_sandbox6020 self.disable_gpu = sb_config.disable_gpu6021 self.incognito = sb_config.incognito6022 self.guest_mode = sb_config.guest_mode6023 self.devtools = sb_config.devtools6024 self.remote_debug = sb_config.remote_debug6025 self._multithreaded = sb_config._multithreaded6026 self._reuse_session = sb_config.reuse_session6027 self._crumbs = sb_config.crumbs6028 self.dashboard = sb_config.dashboard6029 self._dash_initialized = sb_config._dashboard_initialized6030 if self.dashboard and self._multithreaded:6031 import fasteners6032 self.dash_lock = fasteners.InterProcessLock(6033 constants.Dashboard.LOCKFILE6034 )6035 self.swiftshader = sb_config.swiftshader6036 self.user_data_dir = sb_config.user_data_dir6037 self.extension_zip = sb_config.extension_zip6038 self.extension_dir = sb_config.extension_dir6039 self.external_pdf = sb_config.external_pdf6040 self.maximize_option = sb_config.maximize_option6041 self.save_screenshot_after_test = sb_config.save_screenshot6042 self.visual_baseline = sb_config.visual_baseline6043 self.timeout_multiplier = sb_config.timeout_multiplier6044 self.pytest_html_report = sb_config.pytest_html_report6045 self.report_on = False6046 if self.pytest_html_report:6047 self.report_on = True6048 self.use_grid = False6049 if self.servername != "localhost":6050 # Use Selenium Grid (Use --server="127.0.0.1" for a local Grid)6051 self.use_grid = True6052 if self.with_db_reporting:6053 import getpass6054 import uuid6055 from seleniumbase.core.application_manager import (6056 ApplicationManager,6057 )6058 from seleniumbase.core.testcase_manager import (6059 ExecutionQueryPayload,6060 )6061 from seleniumbase.core.testcase_manager import (6062 TestcaseDataPayload,6063 )6064 from seleniumbase.core.testcase_manager import TestcaseManager6065 self.execution_guid = str(uuid.uuid4())6066 self.testcase_guid = None6067 self.execution_start_time = 06068 self.case_start_time = 06069 self.application = None6070 self.testcase_manager = None6071 self.error_handled = False6072 self.testcase_manager = TestcaseManager(self.database_env)6073 #6074 exec_payload = ExecutionQueryPayload()6075 exec_payload.execution_start_time = int(time.time() * 1000)6076 self.execution_start_time = exec_payload.execution_start_time6077 exec_payload.guid = self.execution_guid6078 exec_payload.username = getpass.getuser()6079 self.testcase_manager.insert_execution_data(exec_payload)6080 #6081 data_payload = TestcaseDataPayload()6082 self.testcase_guid = str(uuid.uuid4())6083 data_payload.guid = self.testcase_guid6084 data_payload.execution_guid = self.execution_guid6085 if self.with_selenium:6086 data_payload.browser = self.browser6087 else:6088 data_payload.browser = "N/A"6089 data_payload.test_address = test_id6090 application = ApplicationManager.generate_application_string(6091 self._testMethodName6092 )6093 data_payload.env = application.split(".")[0]6094 data_payload.start_time = application.split(".")[1]6095 data_payload.state = constants.State.UNTESTED6096 self.__skip_reason = None6097 self.testcase_manager.insert_testcase_data(data_payload)6098 self.case_start_time = int(time.time() * 1000)6099 if self.headless or self.xvfb:6100 width = settings.HEADLESS_START_WIDTH6101 height = settings.HEADLESS_START_HEIGHT6102 try:6103 # from pyvirtualdisplay import Display # Skip for own lib6104 from sbvirtualdisplay import Display6105 self.display = Display(visible=0, size=(width, height))6106 self.display.start()6107 self.headless_active = True6108 except Exception:6109 # pyvirtualdisplay might not be necessary anymore because6110 # Chrome and Firefox now have built-in headless displays6111 pass6112 # Verify that SeleniumBase is installed successfully6113 if not hasattr(self, "browser"):6114 raise Exception(6115 'SeleniumBase plugins DID NOT load! * Please REINSTALL!\n'6116 '*** Either install SeleniumBase in Dev Mode from a clone:\n'6117 ' >>> "pip install -e ." (Run in DIR with setup.py)\n'6118 '*** Or install the latest SeleniumBase version from PyPI:\n'6119 ' >>> "pip install -U seleniumbase" (Run in any DIR)'6120 )6121 if not hasattr(sb_config, "_is_timeout_changed"):6122 # Should only be reachable from pure Python runs6123 sb_config._is_timeout_changed = False6124 sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT6125 sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT6126 if sb_config._is_timeout_changed:6127 if sb_config._SMALL_TIMEOUT and sb_config._LARGE_TIMEOUT:6128 settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT6129 settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT6130 if not hasattr(sb_config, "_recorded_actions"):6131 # Only filled when Recorder Mode is enabled6132 sb_config._recorded_actions = {}6133 if not hasattr(settings, "SWITCH_TO_NEW_TABS_ON_CLICK"):6134 # If using an older settings file, set the new definitions manually6135 settings.SWITCH_TO_NEW_TABS_ON_CLICK = True6136 # Parse the settings file6137 if self.settings_file:6138 from seleniumbase.core import settings_parser6139 settings_parser.set_settings(self.settings_file)6140 # Set variables that may be useful to developers6141 self.log_abspath = os.path.abspath(self.log_path)6142 self.data_path = os.path.join(self.log_path, self.__get_test_id())6143 self.data_abspath = os.path.abspath(self.data_path)6144 # Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio6145 if self.device_metrics:6146 metrics_string = self.device_metrics6147 metrics_string = metrics_string.replace(" ", "")6148 metrics_list = metrics_string.split(",")6149 exception_string = (6150 "Invalid input for Mobile Emulator device metrics!\n"6151 "Expecting a comma-separated string with three\n"6152 "integer values for Width, Height, and Pixel-Ratio.\n"6153 'Example: --metrics="411,731,3" '6154 )6155 if len(metrics_list) != 3:6156 raise Exception(exception_string)6157 try:6158 self.__device_width = int(metrics_list[0])6159 self.__device_height = int(metrics_list[1])6160 self.__device_pixel_ratio = int(metrics_list[2])6161 self.mobile_emulator = True6162 except Exception:6163 raise Exception(exception_string)6164 if self.mobile_emulator:6165 if not self.user_agent:6166 # Use the Pixel 4 user agent by default if not specified6167 self.user_agent = (6168 "Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) "6169 "AppleWebKit/537.36 (KHTML, like Gecko) "6170 "Chrome/89.0.4389.105 Mobile Safari/537.36"6171 )6172 # Dashboard pre-processing:6173 if self.dashboard:6174 if self._multithreaded:6175 with self.dash_lock:6176 sb_config._sbase_detected = True6177 sb_config._only_unittest = False6178 if not self._dash_initialized:6179 sb_config._dashboard_initialized = True6180 self._dash_initialized = True6181 self.__process_dashboard(False, init=True)6182 else:6183 sb_config._sbase_detected = True6184 sb_config._only_unittest = False6185 if not self._dash_initialized:6186 sb_config._dashboard_initialized = True6187 self._dash_initialized = True6188 self.__process_dashboard(False, init=True)6189 has_url = False6190 if self._reuse_session:6191 if not hasattr(sb_config, "shared_driver"):6192 sb_config.shared_driver = None6193 if sb_config.shared_driver:6194 try:6195 self._default_driver = sb_config.shared_driver6196 self.driver = sb_config.shared_driver6197 self._drivers_list = [sb_config.shared_driver]6198 url = self.get_current_url()6199 if url is not None:6200 has_url = True6201 if len(self.driver.window_handles) > 1:6202 while len(self.driver.window_handles) > 1:6203 self.switch_to_window(6204 len(self.driver.window_handles) - 16205 )6206 self.driver.close()6207 self.switch_to_window(0)6208 if self._crumbs:6209 self.driver.delete_all_cookies()6210 except Exception:6211 pass6212 if self._reuse_session and sb_config.shared_driver and has_url:6213 good_start_page = False6214 if self.start_page and len(self.start_page) >= 4:6215 if page_utils.is_valid_url(self.start_page):6216 good_start_page = True6217 self.__new_window_on_rec_open = False6218 self.open(self.start_page)6219 self.__new_window_on_rec_open = True6220 else:6221 new_start_page = "https://" + self.start_page6222 if page_utils.is_valid_url(new_start_page):6223 good_start_page = True6224 self.__dont_record_open = True6225 self.open(new_start_page)6226 self.__dont_record_open = False6227 else:6228 # Launch WebDriver for both Pytest and Nosetests6229 self.driver = self.get_new_driver(6230 browser=self.browser,6231 headless=self.headless,6232 locale_code=self.locale_code,6233 protocol=self.protocol,6234 servername=self.servername,6235 port=self.port,6236 proxy=self.proxy_string,6237 proxy_bypass_list=self.proxy_bypass_list,6238 agent=self.user_agent,6239 switch_to=True,6240 cap_file=self.cap_file,6241 cap_string=self.cap_string,6242 recorder_ext=self.recorder_ext,6243 disable_csp=self.disable_csp,6244 enable_ws=self.enable_ws,6245 enable_sync=self.enable_sync,6246 use_auto_ext=self.use_auto_ext,6247 no_sandbox=self.no_sandbox,6248 disable_gpu=self.disable_gpu,6249 incognito=self.incognito,6250 guest_mode=self.guest_mode,6251 devtools=self.devtools,6252 remote_debug=self.remote_debug,6253 swiftshader=self.swiftshader,6254 ad_block_on=self.ad_block_on,6255 block_images=self.block_images,6256 chromium_arg=self.chromium_arg,6257 firefox_arg=self.firefox_arg,6258 firefox_pref=self.firefox_pref,6259 user_data_dir=self.user_data_dir,6260 extension_zip=self.extension_zip,6261 extension_dir=self.extension_dir,6262 external_pdf=self.external_pdf,6263 is_mobile=self.mobile_emulator,6264 d_width=self.__device_width,6265 d_height=self.__device_height,6266 d_p_r=self.__device_pixel_ratio,6267 )6268 self._default_driver = self.driver6269 if self._reuse_session:6270 sb_config.shared_driver = self.driver6271 if self.browser in ["firefox", "ie", "safari", "opera"]:6272 # Only Chrome and Edge browsers have the mobile emulator.6273 # Some actions such as hover-clicking are different on mobile.6274 self.mobile_emulator = False6275 # Configure the test time limit (if used).6276 self.set_time_limit(self.time_limit)6277 # Set the start time for the test (in ms).6278 # Although the pytest clock starts before setUp() begins,6279 # the time-limit clock starts at the end of the setUp() method.6280 sb_config.start_time_ms = int(time.time() * 1000.0)6281 if not self.__start_time_ms:6282 # Call this once in case of multiple setUp() calls in the same test6283 self.__start_time_ms = sb_config.start_time_ms6284 def __set_last_page_screenshot(self):6285 """self.__last_page_screenshot is only for pytest html report logs.6286 self.__last_page_screenshot_png is for all screenshot log files."""6287 if not self.__last_page_screenshot and (6288 not self.__last_page_screenshot_png6289 ):6290 try:6291 element = self.driver.find_element(6292 by=By.TAG_NAME, value="body"6293 )6294 if self.is_pytest and self.report_on:6295 self.__last_page_screenshot_png = (6296 self.driver.get_screenshot_as_png()6297 )6298 self.__last_page_screenshot = element.screenshot_as_base646299 else:6300 self.__last_page_screenshot_png = element.screenshot_as_png6301 except Exception:6302 if not self.__last_page_screenshot:6303 if self.is_pytest and self.report_on:6304 try:6305 self.__last_page_screenshot = (6306 self.driver.get_screenshot_as_base64()6307 )6308 except Exception:6309 self.__last_page_screenshot = (6310 constants.Warnings.SCREENSHOT_UNDEFINED6311 )6312 if not self.__last_page_screenshot_png:6313 try:6314 self.__last_page_screenshot_png = (6315 self.driver.get_screenshot_as_png()6316 )6317 except Exception:6318 self.__last_page_screenshot_png = (6319 constants.Warnings.SCREENSHOT_UNDEFINED6320 )6321 def __set_last_page_url(self):6322 if not self.__last_page_url:6323 try:6324 self.__last_page_url = log_helper.get_last_page(self.driver)6325 except Exception:6326 self.__last_page_url = None6327 def __set_last_page_source(self):6328 if not self.__last_page_source:6329 try:6330 self.__last_page_source = (6331 log_helper.get_html_source_with_base_href(6332 self.driver, self.driver.page_source6333 )6334 )6335 except Exception:6336 self.__last_page_source = (6337 constants.Warnings.PAGE_SOURCE_UNDEFINED6338 )6339 def __get_exception_info(self):6340 exc_message = None6341 if (6342 python36343 and hasattr(self, "_outcome")6344 and (hasattr(self._outcome, "errors") and self._outcome.errors)6345 ):6346 try:6347 exc_message = self._outcome.errors[0][1][1]6348 except Exception:6349 exc_message = "(Unknown Exception)"6350 else:6351 try:6352 exc_message = sys.last_value6353 except Exception:6354 exc_message = "(Unknown Exception)"6355 return str(exc_message)6356 def __insert_test_result(self, state, err):6357 from seleniumbase.core.testcase_manager import TestcaseDataPayload6358 data_payload = TestcaseDataPayload()6359 data_payload.runtime = int(time.time() * 1000) - self.case_start_time6360 data_payload.guid = self.testcase_guid6361 data_payload.execution_guid = self.execution_guid6362 data_payload.state = state6363 if err:6364 import traceback6365 tb_string = traceback.format_exc()6366 if "Message: " in tb_string:6367 data_payload.message = (6368 "Message: " + tb_string.split("Message: ")[-1]6369 )6370 elif "Exception: " in tb_string:6371 data_payload.message = tb_string.split("Exception: ")[-1]6372 elif "Error: " in tb_string:6373 data_payload.message = tb_string.split("Error: ")[-1]6374 else:6375 data_payload.message = self.__get_exception_info()6376 else:6377 test_id = self.__get_test_id_2()6378 if (6379 self.is_pytest6380 and test_id in sb_config._results.keys()6381 and (sb_config._results[test_id] == "Skipped")6382 ):6383 if self.__skip_reason:6384 data_payload.message = "Skipped: " + self.__skip_reason6385 else:6386 data_payload.message = "Skipped: (no reason given)"6387 self.testcase_manager.update_testcase_data(data_payload)6388 def __add_pytest_html_extra(self):6389 if not self.__added_pytest_html_extra:6390 try:6391 if self.with_selenium:6392 if not self.__last_page_screenshot:6393 self.__set_last_page_screenshot()6394 self.__set_last_page_url()6395 self.__set_last_page_source()6396 if self.report_on:6397 extra_url = {}6398 extra_url["name"] = "URL"6399 extra_url["format"] = "url"6400 extra_url["content"] = self.get_current_url()6401 extra_url["mime_type"] = None6402 extra_url["extension"] = None6403 extra_image = {}6404 extra_image["name"] = "Screenshot"6405 extra_image["format"] = "image"6406 extra_image["content"] = self.__last_page_screenshot6407 extra_image["mime_type"] = "image/png"6408 extra_image["extension"] = "png"6409 self.__added_pytest_html_extra = True6410 if self.__last_page_screenshot != (6411 constants.Warnings.SCREENSHOT_UNDEFINED6412 ):6413 self._html_report_extra.append(extra_url)6414 self._html_report_extra.append(extra_image)6415 except Exception:6416 pass6417 def __has_exception(self):6418 has_exception = False6419 if hasattr(sys, "last_traceback") and sys.last_traceback is not None:6420 has_exception = True6421 elif python3 and hasattr(self, "_outcome"):6422 if hasattr(self._outcome, "errors") and self._outcome.errors:6423 has_exception = True6424 else:6425 if python3:6426 has_exception = sys.exc_info()[1] is not None6427 else:6428 if not hasattr(self, "_using_sb_fixture_class") and (6429 not hasattr(self, "_using_sb_fixture_no_class")6430 ):6431 has_exception = sys.exc_info()[1] is not None6432 else:6433 has_exception = len(str(sys.exc_info()[1]).strip()) > 06434 if (6435 self.__will_be_skipped6436 and (hasattr(self, "_using_sb_fixture") or not python3)6437 ):6438 has_exception = False6439 return has_exception6440 def __get_test_id(self):6441 """ The id used in various places such as the test log path. """6442 test_id = "%s.%s.%s" % (6443 self.__class__.__module__,6444 self.__class__.__name__,6445 self._testMethodName,6446 )6447 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6448 test_id = self._sb_test_identifier6449 test_id = test_id.replace(".py::", ".").replace("::", ".")6450 return test_id6451 def __get_test_id_2(self):6452 """ The id for SeleniumBase Dashboard entries. """6453 if "PYTEST_CURRENT_TEST" in os.environ:6454 return os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]6455 test_id = "%s.%s.%s" % (6456 self.__class__.__module__.split(".")[-1],6457 self.__class__.__name__,6458 self._testMethodName,6459 )6460 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6461 test_id = self._sb_test_identifier6462 if test_id.count(".") > 1:6463 test_id = ".".join(test_id.split(".")[1:])6464 return test_id6465 def __get_display_id(self):6466 """ The id for running a test from pytest. (Displayed on Dashboard) """6467 if "PYTEST_CURRENT_TEST" in os.environ:6468 return os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]6469 test_id = "%s.py::%s::%s" % (6470 self.__class__.__module__.replace(".", "/"),6471 self.__class__.__name__,6472 self._testMethodName,6473 )6474 if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:6475 test_id = self._sb_test_identifier6476 if hasattr(self, "_using_sb_fixture_class"):6477 if test_id.count(".") >= 2:6478 parts = test_id.split(".")6479 full = parts[-3] + ".py::" + parts[-2] + "::" + parts[-1]6480 test_id = full6481 elif hasattr(self, "_using_sb_fixture_no_class"):6482 if test_id.count(".") >= 1:6483 parts = test_id.split(".")6484 full = parts[-2] + ".py::" + parts[-1]6485 test_id = full6486 return test_id6487 def __get_filename(self):6488 """ The filename of the current SeleniumBase test. (NOT Path) """6489 filename = None6490 if "PYTEST_CURRENT_TEST" in os.environ:6491 test_id = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]6492 filename = test_id.split("::")[0].split("/")[-1]6493 else:6494 filename = self.__class__.__module__.split(".")[-1] + ".py"6495 return filename6496 def __create_log_path_as_needed(self, test_logpath):6497 if not os.path.exists(test_logpath):6498 try:6499 os.makedirs(test_logpath)6500 except Exception:6501 pass # Only reachable during multi-threaded runs6502 # def __process_dashboard(self, has_exception, init=False):6503 # """ SeleniumBase Dashboard Processing """6504 # if self._multithreaded:6505 # existing_res = sb_config._results # For recording "Skipped" tests6506 # abs_path = os.path.abspath(".")6507 # dash_json_loc = constants.Dashboard.DASH_JSON6508 # dash_jsonpath = os.path.join(abs_path, dash_json_loc)6509 # if not init and os.path.exists(dash_jsonpath):6510 # with open(dash_jsonpath, "r") as f:6511 # dash_json = f.read().strip()6512 # dash_data, d_id, dash_rt, tlp, d_stats = json.loads(dash_json)6513 # num_passed, num_failed, num_skipped, num_untested = d_stats6514 # sb_config._results = dash_data6515 # sb_config._display_id = d_id6516 # sb_config._duration = dash_rt # Dashboard Run Time6517 # sb_config._d_t_log_path = tlp # Test Log Path6518 # sb_config.item_count_passed = num_passed6519 # sb_config.item_count_failed = num_failed6520 # sb_config.item_count_skipped = num_skipped6521 # sb_config.item_count_untested = num_untested6522 # if len(sb_config._extra_dash_entries) > 0:6523 # # First take care of existing entries from non-SeleniumBase tests6524 # for test_id in sb_config._extra_dash_entries:6525 # if test_id in sb_config._results.keys():6526 # if sb_config._results[test_id] == "Skipped":6527 # sb_config.item_count_skipped += 16528 # sb_config.item_count_untested -= 16529 # elif sb_config._results[test_id] == "Failed":6530 # sb_config.item_count_failed += 16531 # sb_config.item_count_untested -= 16532 # elif sb_config._results[test_id] == "Passed":6533 # sb_config.item_count_passed += 16534 # sb_config.item_count_untested -= 16535 # else: # Mark "Skipped" if unknown6536 # sb_config.item_count_skipped += 16537 # sb_config.item_count_untested -= 16538 # sb_config._extra_dash_entries = [] # Reset the list to empty6539 # # Process new entries6540 # log_dir = self.log_path6541 # ft_id = self.__get_test_id() # Full test id with path to log files6542 # test_id = self.__get_test_id_2() # The test id used by the DashBoard6543 # dud = "seleniumbase/plugins/pytest_plugin.py::BaseClass::base_method"6544 # dud2 = "pytest_plugin.BaseClass.base_method"6545 # if hasattr(self, "_using_sb_fixture") and self.__will_be_skipped:6546 # test_id = sb_config._test_id6547 # if not init:6548 # duration_ms = int(time.time() * 1000) - self.__start_time_ms6549 # duration = float(duration_ms) / 1000.06550 # duration = "{:.2f}".format(duration)6551 # sb_config._duration[test_id] = duration6552 # if (6553 # has_exception6554 # or self.save_screenshot_after_test6555 # or self.__screenshot_count > 06556 # or self.__will_be_skipped6557 # ):6558 # sb_config._d_t_log_path[test_id] = os.path.join(log_dir, ft_id)6559 # else:6560 # sb_config._d_t_log_path[test_id] = None6561 # if test_id not in sb_config._display_id.keys():6562 # sb_config._display_id[test_id] = self.__get_display_id()6563 # if sb_config._display_id[test_id] == dud:6564 # return6565 # if (6566 # hasattr(self, "_using_sb_fixture")6567 # and test_id not in sb_config._results.keys()6568 # ):6569 # if test_id.count(".") > 1:6570 # alt_test_id = ".".join(test_id.split(".")[1:])6571 # if alt_test_id in sb_config._results.keys():6572 # sb_config._results.pop(alt_test_id)6573 # elif test_id.count(".") == 1:6574 # alt_test_id = sb_config._display_id[test_id]6575 # alt_test_id = alt_test_id.replace(".py::", ".")6576 # alt_test_id = alt_test_id.replace("::", ".")6577 # if alt_test_id in sb_config._results.keys():6578 # sb_config._results.pop(alt_test_id)6579 # if test_id in sb_config._results.keys() and (6580 # sb_config._results[test_id] == "Skipped"6581 # ):6582 # if self.__passed_then_skipped:6583 # # Multiple calls of setUp() and tearDown() in the same test6584 # sb_config.item_count_passed -= 16585 # sb_config.item_count_untested += 16586 # self.__passed_then_skipped = False6587 # sb_config._results[test_id] = "Skipped"6588 # sb_config.item_count_skipped += 16589 # sb_config.item_count_untested -= 16590 # elif (6591 # self._multithreaded6592 # and test_id in existing_res.keys()6593 # and existing_res[test_id] == "Skipped"6594 # ):6595 # sb_config._results[test_id] = "Skipped"6596 # sb_config.item_count_skipped += 16597 # sb_config.item_count_untested -= 16598 # elif has_exception:6599 # if test_id not in sb_config._results.keys():6600 # sb_config._results[test_id] = "Failed"6601 # sb_config.item_count_failed += 16602 # sb_config.item_count_untested -= 16603 # elif not sb_config._results[test_id] == "Failed":6604 # # tearDown() was called more than once in the test6605 # if sb_config._results[test_id] == "Passed":6606 # # Passed earlier, but last run failed6607 # sb_config._results[test_id] = "Failed"6608 # sb_config.item_count_failed += 16609 # sb_config.item_count_passed -= 16610 # else:6611 # sb_config._results[test_id] = "Failed"6612 # sb_config.item_count_failed += 16613 # sb_config.item_count_untested -= 16614 # else:6615 # # pytest-rerunfailures caused a duplicate failure6616 # sb_config._results[test_id] = "Failed"6617 # else:6618 # if (6619 # test_id in sb_config._results.keys()6620 # and sb_config._results[test_id] == "Failed"6621 # ):6622 # # pytest-rerunfailures reran a test that failed6623 # sb_config._d_t_log_path[test_id] = os.path.join(6624 # log_dir, ft_id6625 # )6626 # sb_config.item_count_failed -= 16627 # sb_config.item_count_untested += 16628 # elif (6629 # test_id in sb_config._results.keys()6630 # and sb_config._results[test_id] == "Passed"6631 # ):6632 # # tearDown() was called more than once in the test6633 # sb_config.item_count_passed -= 16634 # sb_config.item_count_untested += 16635 # sb_config._results[test_id] = "Passed"6636 # sb_config.item_count_passed += 16637 # sb_config.item_count_untested -= 16638 # else:6639 # pass # Only initialize the Dashboard on the first processing6640 # num_passed = sb_config.item_count_passed6641 # num_failed = sb_config.item_count_failed6642 # num_skipped = sb_config.item_count_skipped6643 # num_untested = sb_config.item_count_untested6644 # self.create_pie_chart(title=constants.Dashboard.TITLE)6645 # self.add_data_point("Passed", num_passed, color="#84d474")6646 # self.add_data_point("Untested", num_untested, color="#eaeaea")6647 # self.add_data_point("Skipped", num_skipped, color="#efd8b4")6648 # self.add_data_point("Failed", num_failed, color="#f17476")6649 # style = (6650 # '<link rel="stylesheet" charset="utf-8" '6651 # 'href="%s">' % constants.Dashboard.STYLE_CSS6652 # )6653 # auto_refresh_html = ""6654 # if num_untested > 0:6655 # # Refresh every X seconds when waiting for more test results6656 # auto_refresh_html = constants.Dashboard.META_REFRESH_HTML6657 # else:6658 # # The tests are complete6659 # if sb_config._using_html_report:6660 # # Add the pie chart to the pytest html report6661 # sb_config._saved_dashboard_pie = self.extract_chart()6662 # if self._multithreaded:6663 # abs_path = os.path.abspath(".")6664 # dash_pie = json.dumps(sb_config._saved_dashboard_pie)6665 # dash_pie_loc = constants.Dashboard.DASH_PIE6666 # pie_path = os.path.join(abs_path, dash_pie_loc)6667 # pie_file = codecs.open(pie_path, "w+", encoding="utf-8")6668 # pie_file.writelines(dash_pie)6669 # pie_file.close()6670 # head = (6671 # '<head><meta charset="utf-8">'6672 # '<meta name="viewport" content="shrink-to-fit=no">'6673 # '<link rel="shortcut icon" href="%s">'6674 # "%s"6675 # "<title>Dashboard</title>"6676 # "%s</head>"6677 # % (constants.Dashboard.DASH_PIE_PNG_1, auto_refresh_html, style)6678 # )6679 # table_html = (6680 # "<div></div>"6681 # '<table border="1px solid #e6e6e6;" width="100%;" padding: 5px;'6682 # ' font-size="12px;" text-align="left;" id="results-table">'6683 # '<thead id="results-table-head">'6684 # '<tr style="background-color: #F7F7FD;">'6685 # '<th col="result">Result</th><th col="name">Test</th>'6686 # '<th col="duration">Duration</th><th col="links">Links</th>'6687 # "</tr></thead>"6688 # )6689 # the_failed = []6690 # the_skipped = []6691 # the_passed_hl = [] # Passed and has logs6692 # the_passed_nl = [] # Passed and no logs6693 # the_untested = []6694 # if dud2 in sb_config._results.keys():6695 # sb_config._results.pop(dud2)6696 # for key in sb_config._results.keys():6697 # t_res = sb_config._results[key]6698 # t_dur = sb_config._duration[key]6699 # t_d_id = sb_config._display_id[key]6700 # t_l_path = sb_config._d_t_log_path[key]6701 # res_low = t_res.lower()6702 # if sb_config._results[key] == "Failed":6703 # if not sb_config._d_t_log_path[key]:6704 # sb_config._d_t_log_path[key] = os.path.join(log_dir, ft_id)6705 # the_failed.append([res_low, t_res, t_d_id, t_dur, t_l_path])6706 # elif sb_config._results[key] == "Skipped":6707 # the_skipped.append([res_low, t_res, t_d_id, t_dur, t_l_path])6708 # elif sb_config._results[key] == "Passed" and t_l_path:6709 # the_passed_hl.append([res_low, t_res, t_d_id, t_dur, t_l_path])6710 # elif sb_config._results[key] == "Passed" and not t_l_path:6711 # the_passed_nl.append([res_low, t_res, t_d_id, t_dur, t_l_path])6712 # elif sb_config._results[key] == "Untested":6713 # the_untested.append([res_low, t_res, t_d_id, t_dur, t_l_path])6714 # for row in the_failed:6715 # row = (6716 # '<tbody class="%s results-table-row">'6717 # '<tr style="background-color: #FFF8F8;">'6718 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6719 # '<td><a href="%s">Logs</a> / <a href="%s/">Data</a>'6720 # "</td></tr></tbody>"6721 # "" % (row[0], row[1], row[2], row[3], log_dir, row[4])6722 # )6723 # table_html += row6724 # for row in the_skipped:6725 # if not row[4]:6726 # row = (6727 # '<tbody class="%s results-table-row">'6728 # '<tr style="background-color: #FEFEF9;">'6729 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6730 # "<td>-</td></tr></tbody>"6731 # % (row[0], row[1], row[2], row[3])6732 # )6733 # else:6734 # row = (6735 # '<tbody class="%s results-table-row">'6736 # '<tr style="background-color: #FEFEF9;">'6737 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6738 # '<td><a href="%s">Logs</a> / <a href="%s/">Data</a>'6739 # "</td></tr></tbody>"6740 # "" % (row[0], row[1], row[2], row[3], log_dir, row[4])6741 # )6742 # table_html += row6743 # for row in the_passed_hl:6744 # # Passed and has logs6745 # row = (6746 # '<tbody class="%s results-table-row">'6747 # '<tr style="background-color: #F8FFF8;">'6748 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6749 # '<td><a href="%s">Logs</a> / <a href="%s/">Data</a>'6750 # "</td></tr></tbody>"6751 # "" % (row[0], row[1], row[2], row[3], log_dir, row[4])6752 # )6753 # table_html += row6754 # for row in the_passed_nl:6755 # # Passed and no logs6756 # row = (6757 # '<tbody class="%s results-table-row">'6758 # '<tr style="background-color: #F8FFF8;">'6759 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6760 # "<td>-</td></tr></tbody>" % (row[0], row[1], row[2], row[3])6761 # )6762 # table_html += row6763 # for row in the_untested:6764 # row = (6765 # '<tbody class="%s results-table-row"><tr>'6766 # '<td class="col-result">%s</td><td>%s</td><td>%s</td>'6767 # "<td>-</td></tr></tbody>" % (row[0], row[1], row[2], row[3])6768 # )6769 # table_html += row6770 # table_html += "</table>"6771 # add_more = "<br /><b>Last updated:</b> "6772 # timestamp, the_date, the_time = log_helper.get_master_time()6773 # last_updated = "%s at %s" % (the_date, the_time)6774 # add_more = add_more + "%s" % last_updated6775 # status = "<p></p><div><b>Status:</b> Awaiting results..."6776 # status += " (Refresh the page for updates)"6777 # if num_untested == 0:6778 # status = "<p></p><div><b>Status:</b> Test Run Complete:"6779 # if num_failed == 0:6780 # if num_passed > 0:6781 # if num_skipped == 0:6782 # status += " <b>Success!</b> (All tests passed)"6783 # else:6784 # status += " <b>Success!</b> (No failing tests)"6785 # else:6786 # status += " All tests were skipped!"6787 # else:6788 # latest_logs_dir = "latest_logs/"6789 # log_msg = "See latest logs for details"6790 # if num_failed == 1:6791 # status += (6792 # " <b>1 test failed!</b> --- "6793 # '(<b><a href="%s">%s</a></b>)'6794 # "" % (latest_logs_dir, log_msg)6795 # )6796 # else:6797 # status += (6798 # " <b>%s tests failed!</b> --- "6799 # '(<b><a href="%s">%s</a></b>)'6800 # "" % (num_failed, latest_logs_dir, log_msg)6801 # )6802 # status += "</div><p></p>"6803 # add_more = add_more + status6804 # gen_by = (6805 # '<p><div>Generated by: <b><a href="https://seleniumbase.io/">'6806 # "SeleniumBase</a></b></div></p><p></p>"6807 # )6808 # add_more = add_more + gen_by6809 # # Have dashboard auto-refresh on updates when using an http server6810 # refresh_line = (6811 # '<script type="text/javascript" src="%s">'6812 # "</script>" % constants.Dashboard.LIVE_JS6813 # )6814 # if num_untested == 0 and sb_config._using_html_report:6815 # sb_config._dash_final_summary = status6816 # add_more = add_more + refresh_line6817 # the_html = (6818 # '<html lang="en">'6819 # + head6820 # + self.extract_chart()6821 # + table_html6822 # + add_more6823 # )6824 # abs_path = os.path.abspath(".")6825 # file_path = os.path.join(abs_path, "dashboard.html")6826 # out_file = codecs.open(file_path, "w+", encoding="utf-8")6827 # out_file.writelines(the_html)6828 # out_file.close()6829 # sb_config._dash_html = the_html6830 # if self._multithreaded:6831 # d_stats = (num_passed, num_failed, num_skipped, num_untested)6832 # _results = sb_config._results6833 # _display_id = sb_config._display_id6834 # _rt = sb_config._duration # Run Time (RT)6835 # _tlp = sb_config._d_t_log_path # Test Log Path (TLP)6836 # dash_json = json.dumps((_results, _display_id, _rt, _tlp, d_stats))6837 # dash_json_loc = constants.Dashboard.DASH_JSON6838 # dash_jsonpath = os.path.join(abs_path, dash_json_loc)6839 # dash_json_file = codecs.open(dash_jsonpath, "w+", encoding="utf-8")6840 # dash_json_file.writelines(dash_json)6841 # dash_json_file.close()6842 def has_exception(self):6843 """(This method should ONLY be used in custom tearDown() methods.)6844 This method returns True if the test failed or raised an exception.6845 This is useful for performing additional steps in your tearDown()6846 method (based on whether or not the test passed or failed).6847 Example use cases:6848 * Performing cleanup steps if a test didn't complete.6849 * Sending test data and/or results to a dashboard service.6850 """6851 return self.__has_exception()6852 def save_teardown_screenshot(self):6853 """(Should ONLY be used at the start of custom tearDown() methods.)6854 This method takes a screenshot of the current web page for a6855 failing test (or when running your tests with --save-screenshot).6856 That way your tearDown() method can navigate away from the last6857 page where the test failed, and still get the correct screenshot6858 before performing tearDown() steps on other pages. If this method6859 is not included in your custom tearDown() method, a screenshot6860 will still be taken after the last step of your tearDown(), where6861 you should be calling "super(SubClassOfBaseCase, self).tearDown()"6862 """6863 try:6864 self.__check_scope__()6865 except Exception:6866 return6867 if self.__has_exception() or self.save_screenshot_after_test:6868 test_logpath = os.path.join(self.log_path, self.__get_test_id())6869 self.__create_log_path_as_needed(test_logpath)6870 self.__set_last_page_screenshot()6871 self.__set_last_page_url()6872 self.__set_last_page_source()6873 self.__add_pytest_html_extra()6874 def teardown(self):6875 """6876 Be careful if a subclass of BaseCase overrides setUp()6877 You'll need to add the following line to the subclass's tearDown():6878 super(SubClassOfBaseCase, self).tearDown()6879 """6880 if self._called_teardown:6881 logger.info("This test already called teardown()")6882 return6883 self._called_teardown = True6884 self._called_setup = False6885 try:6886 is_pytest = self.is_pytest # This fails if overriding setUp()6887 if is_pytest:6888 with_selenium = self.with_selenium6889 except Exception:6890 sub_class_name = (6891 str(self.__class__.__bases__[0]).split(".")[-1].split("'")[0]6892 )6893 sub_file_name = str(self.__class__.__bases__[0]).split(".")[-2]6894 sub_file_name = sub_file_name + ".py"6895 class_name = str(self.__class__).split(".")[-1].split("'")[0]6896 file_name = str(self.__class__).split(".")[-2] + ".py"6897 class_name_used = sub_class_name6898 file_name_used = sub_file_name6899 if sub_class_name == "BaseCase":6900 class_name_used = class_name6901 file_name_used = file_name6902 fix_setup = "super(%s, self).setUp()" % class_name_used6903 fix_teardown = "super(%s, self).tearDown()" % class_name_used6904 message = (6905 "You're overriding SeleniumBase's BaseCase setUp() "6906 "method with your own setUp() method, which breaks "6907 "SeleniumBase. You can fix this by going to your "6908 "%s class located in your %s file and adding the "6909 "following line of code AT THE BEGINNING of your "6910 "setUp() method:\n%s\n\nAlso make sure "6911 "you have added the following line of code AT THE "6912 "END of your tearDown() method:\n%s\n"6913 % (class_name_used, file_name_used, fix_setup, fix_teardown)6914 )6915 raise Exception(message)6916 # *** Start tearDown() officially ***6917 self._slow_mode_pause_if_active()6918 has_exception = self.__has_exception()6919 if self.__overrided_default_timeouts:6920 # Reset default timeouts in case there are more tests6921 # These were changed in set_default_timeout()6922 if sb_config._SMALL_TIMEOUT and sb_config._LARGE_TIMEOUT:6923 settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT6924 settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT6925 sb_config._is_timeout_changed = False6926 self.__overrided_default_timeouts = False6927 deferred_exception = None6928 if self.__deferred_assert_failures:6929 print(6930 "\nWhen using self.deferred_assert_*() methods in your tests, "6931 "remember to call self.process_deferred_asserts() afterwards. "6932 "Now calling in tearDown()...\nFailures Detected:"6933 )6934 if not has_exception:6935 try:6936 self.process_deferred_asserts()6937 except Exception as e:6938 deferred_exception = e6939 else:6940 self.process_deferred_asserts(print_only=True)6941 test_id = self.__get_test_id()6942 if with_selenium:6943 # Save a screenshot if logging is on when an exception occurs6944 if has_exception:6945 self.__add_pytest_html_extra()6946 sb_config._has_exception = True6947 if (6948 self.with_testing_base6949 and not has_exception6950 and self.save_screenshot_after_test6951 ):6952 test_logpath = os.path.join(self.log_path, test_id)6953 self.__create_log_path_as_needed(test_logpath)6954 if not self.__last_page_screenshot_png:6955 self.__set_last_page_screenshot()6956 self.__set_last_page_url()6957 self.__set_last_page_source()6958 log_helper.log_screenshot(6959 test_logpath,6960 self.driver,6961 self.__last_page_screenshot_png,6962 )6963 self.__add_pytest_html_extra()6964 if self.with_testing_base and has_exception:6965 test_logpath = os.path.join(self.log_path, test_id)6966 self.__create_log_path_as_needed(test_logpath)6967 if (6968 not self.with_screen_shots6969 and not self.with_basic_test_info6970 and not self.with_page_source6971 ):6972 # Log everything if nothing specified (if testing_base)6973 if not self.__last_page_screenshot_png:6974 self.__set_last_page_screenshot()6975 self.__set_last_page_url()6976 self.__set_last_page_source()6977 log_helper.log_screenshot(6978 test_logpath,6979 self.driver,6980 self.__last_page_screenshot_png,6981 )6982 log_helper.log_test_failure_data(6983 self,6984 test_logpath,6985 self.driver,6986 self.browser,6987 self.__last_page_url,6988 )6989 log_helper.log_page_source(6990 test_logpath, self.driver, self.__last_page_source6991 )6992 else:6993 if self.with_screen_shots:6994 if not self.__last_page_screenshot_png:6995 self.__set_last_page_screenshot()6996 self.__set_last_page_url()6997 self.__set_last_page_source()6998 log_helper.log_screenshot(6999 test_logpath,7000 self.driver,7001 self.__last_page_screenshot_png,7002 )7003 if self.with_basic_test_info:7004 log_helper.log_test_failure_data(7005 self,7006 test_logpath,7007 self.driver,7008 self.browser,7009 self.__last_page_url,7010 )7011 if self.with_page_source:7012 log_helper.log_page_source(7013 test_logpath,7014 self.driver,7015 self.__last_page_source,7016 )7017 if self.dashboard:7018 if self._multithreaded:7019 with self.dash_lock:7020 self.__process_dashboard(has_exception)7021 else:7022 self.__process_dashboard(has_exception)7023 # (Pytest) Finally close all open browser windows7024 self.__quit_all_drivers()7025 if self.config.getoption("headless") or self.config.getoption("xvfb"):7026 if self.headless_active:7027 try:7028 self.display.stop()7029 except AttributeError:7030 pass7031 except Exception:7032 pass7033 self.display = None7034 if self.config.getoption("with_db_reporting", False):7035 if has_exception:7036 self.__insert_test_result(constants.State.FAILED, True)7037 else:7038 test_id = self.__get_test_id_2()7039 if test_id in sb_config._results.keys() and (7040 sb_config._results[test_id] == "Skipped"7041 ):7042 self.__insert_test_result(7043 constants.State.SKIPPED, False7044 )7045 else:7046 self.__insert_test_result(7047 constants.State.PASSED, False7048 )7049 runtime = int(time.time() * 1000) - self.execution_start_time7050 self.testcase_manager.update_execution_data(7051 self.execution_guid, runtime7052 )...
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!