diff --git a/docs/requirements.txt b/docs/requirements.txt index 2060f065..b52b119f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ regex>=2020.11.13 -tqdm>=4.55.2 +tqdm>=4.56.0 livereload==2.6.3;python_version>="3.6" Markdown==3.3.3 readme-renderer==28.0 diff --git a/examples/presenter/core_presentation.py b/examples/presenter/core_presentation.py index 596b6c22..c435110e 100644 --- a/examples/presenter/core_presentation.py +++ b/examples/presenter/core_presentation.py @@ -5,14 +5,14 @@ class MyChartMakerClass(BaseCase): def test_seleniumbase_chart(self): self.create_presentation(theme="league", transition="slide") - self.create_pie_chart(title="There are 4 core areas of SeleniumBase:") - self.add_data_point("Basic API (Test methods/functions)", 1) - self.add_data_point("Command-line Options (pytest Options)", 1) + self.create_pie_chart(title="The 4 core areas of SeleniumBase:") + self.add_data_point("Basic API (test methods)", 1) + self.add_data_point("Command-line options (pytest options)", 1) self.add_data_point("The Console Scripts interface", 1) self.add_data_point("Advanced API (Tours, Charts, & Presentations)", 1) self.add_slide("

SeleniumBase core areas

" + self.extract_chart()) self.add_slide( - "

Basic API (Test methods/functions) Example

", + "

Basic API (test methods). Example test:

", code=( 'from seleniumbase import BaseCase\n\n' 'class MyTestClass(BaseCase):\n\n' @@ -27,20 +27,23 @@ class MyChartMakerClass(BaseCase): ' self.click_link_text("comic #249")\n' ' self.assert_element(\'img[alt*="Chess"]\')\n')) self.add_slide( - "

Command-line Options Example

", + "

Command-line options. Examples:

", code=( '$ pytest my_first_test.py\n' '$ pytest test_swag_labs.py --mobile\n' '$ pytest edge_test.py --browser=edge\n' '$ pytest basic_test.py --headless\n' - '$ pytest my_first_test.py --demo\n' + '$ pytest my_first_test.py --demo --guest\n' '$ pytest basic_test.py --slow\n' '$ pytest -v -m marker2 --headless --save-screenshot\n' - '$ pytest test_suite.py --reuse-session --html=report.html\n' - '$ pytest basic_test.py --incognito\n' - '$ pytest parameterized_test.py --guest --reuse-session\n')) + '$ pytest parameterized_test.py --reuse-session\n' + '$ pytest test_suite.py --html=report.html --rs\n' + '$ pytest test_suite.py --dashboard --html=report.html\n' + '$ pytest github_test.py --demo --disable-csp\n' + '$ pytest test_suite.py -n=2 --rs --crumbs\n' + '$ pytest basic_test.py --incognito\n')) self.add_slide( - "

Console scripts interface Example

", + "

The Console Scripts interface. Examples:

", code=( '$ sbase install chromedriver\n' '$ sbase install chromedriver latest\n' @@ -57,7 +60,7 @@ class MyChartMakerClass(BaseCase): '$ sbase grid-hub stop\n' '$ sbase options\n')) self.add_slide( - '

Advanced API (creating a presentation) Example

', + '

Advanced API. "Presenter" example:

', code=( 'from seleniumbase import BaseCase\n\n' 'class MyPresenterClass(BaseCase):\n\n' diff --git a/integrations/node_js/ReadMe.md b/integrations/node_js/ReadMe.md index dc4f43c5..f0337666 100755 --- a/integrations/node_js/ReadMe.md +++ b/integrations/node_js/ReadMe.md @@ -1,6 +1,6 @@ -

Creating a SeleniumBase Test Runner with NodeJS

+

Creating a Test Runner with NodeJS + Express

-You can create a customized web app for running SeleniumBase tests by using NodeJS. (This tutorial assumes that you've already installed SeleniumBase by following the instructions from the [top-level ReadMe](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md) file.) +You can create a customized web app for running SeleniumBase tests by using NodeJS and Express. (This tutorial assumes that you've already installed SeleniumBase by following the instructions from the [top-level ReadMe](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md) file.) diff --git a/mkdocs.yml b/mkdocs.yml index d0b5e17d..8e01b81d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ # Project information -site_name: SeleniumBase +site_name: SeleniumBase / Docs site_url: https://seleniumbase.io site_author: Michael Mintz site_description: A fast, reliable web automation framework for end-to-end testing (and RPA) with Python, pytest, and WebDriver. @@ -30,7 +30,7 @@ theme: sticky_navigation: true language: en include_search_page: false - search_index_only: true + search_index_only: false features: - tabs - instant @@ -53,7 +53,7 @@ plugins: on_pre_build: docs.prepare:main # Page tree nav: - - SeleniumBase | README: README.md + - SeleniumBase / README: README.md - Features List: help_docs/features_list.md - Running Example Tests: examples/ReadMe.md - Command Line Options: help_docs/customizing_test_runs.md @@ -104,6 +104,7 @@ nav: - TinyMCE Demo Page: https://seleniumbase.io/other/tinymce - Virtual Device Farm: https://seleniumbase.io/devices/ - Error Demo Page: https://seleniumbase.io/error_page/ + - Presentations: - Presenter Demo: https://seleniumbase.io/other/presenter.html - Core Presentation: https://seleniumbase.io/other/core_presentation.html - Chart Maker Demo: https://seleniumbase.io/other/chart_presentation.html diff --git a/requirements.txt b/requirements.txt index 14a82b10..963ba8f2 100755 --- a/requirements.txt +++ b/requirements.txt @@ -64,7 +64,7 @@ ipython==7.19.0;python_version>="3.7" colorama==0.4.4 pathlib2==2.3.5;python_version<"3.5" importlib-metadata==2.0.0;python_version<"3.6" -virtualenv>=20.2.2 +virtualenv>=20.3.0 pymysql==0.10.1;python_version<"3.6" pymysql==1.0.2;python_version>="3.6" coverage==5.3.1 @@ -76,7 +76,7 @@ toml==0.10.2 Pillow==6.2.2;python_version<"3.5" Pillow==7.2.0;python_version>="3.5" and python_version<"3.6" Pillow==8.1.0;python_version>="3.6" -rich==9.7.0;python_version>="3.6" and python_version<"4.0" +rich==9.8.0;python_version>="3.6" and python_version<"4.0" zipp==1.2.0;python_version<"3.6" zipp==3.4.0;python_version>="3.6" flake8==3.7.9;python_version<"3.5" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 89901ceb..0b697613 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "1.51.14" +__version__ = "1.52.0" diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index a15c2b2a..1ac9bcd6 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -134,6 +134,8 @@ class BaseCase(unittest.TestCase): timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) + original_selector = selector + original_by = by selector, by = self.__recalculate_selector(selector, by) if page_utils.is_link_text_selector(selector) or by == By.LINK_TEXT: if not self.is_link_text_visible(selector): @@ -148,7 +150,7 @@ class BaseCase(unittest.TestCase): return element = page_actions.wait_for_element_visible( self.driver, selector, by, timeout=timeout) - self.__demo_mode_highlight_if_active(selector, by) + self.__demo_mode_highlight_if_active(original_selector, original_by) if not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) pre_action_url = self.driver.current_url @@ -226,10 +228,12 @@ class BaseCase(unittest.TestCase): timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) + original_selector = selector + original_by = by selector, by = self.__recalculate_selector(selector, by) element = page_actions.wait_for_element_visible( self.driver, selector, by, timeout=timeout) - self.__demo_mode_highlight_if_active(selector, by) + self.__demo_mode_highlight_if_active(original_selector, original_by) if not self.demo_mode and not self.slow_mode: self.__scroll_to_element(element, selector, by) pre_action_url = self.driver.current_url @@ -1289,13 +1293,15 @@ class BaseCase(unittest.TestCase): "element {%s}!" % selector) def hover_on_element(self, selector, by=By.CSS_SELECTOR): + original_selector = selector + original_by = by selector, by = self.__recalculate_selector(selector, by) if page_utils.is_xpath_selector(selector): selector = self.convert_to_css_selector(selector, By.XPATH) by = By.CSS_SELECTOR self.wait_for_element_visible( selector, by=by, timeout=settings.SMALL_TIMEOUT) - self.__demo_mode_highlight_if_active(selector, by) + self.__demo_mode_highlight_if_active(original_selector, original_by) self.scroll_to(selector, by=by) time.sleep(0.05) # Settle down from scrolling before hovering if self.browser != "chrome": @@ -1339,6 +1345,8 @@ class BaseCase(unittest.TestCase): timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) + original_selector = hover_selector + original_by = hover_by hover_selector, hover_by = self.__recalculate_selector( hover_selector, hover_by) hover_selector = self.convert_to_css_selector( @@ -1348,7 +1356,7 @@ class BaseCase(unittest.TestCase): click_selector, click_by) dropdown_element = self.wait_for_element_visible( hover_selector, by=hover_by, timeout=timeout) - self.__demo_mode_highlight_if_active(hover_selector, hover_by) + self.__demo_mode_highlight_if_active(original_selector, original_by) self.scroll_to(hover_selector, by=hover_by) pre_action_url = self.driver.current_url outdated_driver = False @@ -1400,6 +1408,8 @@ class BaseCase(unittest.TestCase): timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) + original_selector = hover_selector + original_by = hover_by hover_selector, hover_by = self.__recalculate_selector( hover_selector, hover_by) hover_selector = self.convert_to_css_selector( @@ -1409,7 +1419,7 @@ class BaseCase(unittest.TestCase): click_selector, click_by) dropdown_element = self.wait_for_element_visible( hover_selector, by=hover_by, timeout=timeout) - self.__demo_mode_highlight_if_active(hover_selector, hover_by) + self.__demo_mode_highlight_if_active(original_selector, original_by) self.scroll_to(hover_selector, by=hover_by) pre_action_url = self.driver.current_url outdated_driver = False @@ -2239,7 +2249,7 @@ class BaseCase(unittest.TestCase): (Default: 4. Each loop lasts for about 0.18s) scroll - the option to scroll to the element first (Default: True) """ - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) element = self.wait_for_element_visible( selector, by=by, timeout=settings.SMALL_TIMEOUT) if not loops: @@ -2464,7 +2474,7 @@ class BaseCase(unittest.TestCase): def js_click(self, selector, by=By.CSS_SELECTOR, all_matches=False): """ Clicks an element using JavaScript. If "all_matches" is False, only the first match is clicked. """ - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) if by == By.LINK_TEXT: message = ( "Pure JavaScript doesn't support clicking by Link Text. " @@ -2504,7 +2514,7 @@ class BaseCase(unittest.TestCase): def jquery_click(self, selector, by=By.CSS_SELECTOR): """ Clicks an element using jQuery. Different from using pure JS. """ - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) self.wait_for_element_present( selector, by=by, timeout=settings.SMALL_TIMEOUT) if self.is_element_visible(selector, by=by): @@ -2517,7 +2527,7 @@ class BaseCase(unittest.TestCase): def jquery_click_all(self, selector, by=By.CSS_SELECTOR): """ Clicks all matching elements using jQuery. """ - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) self.wait_for_element_present( selector, by=by, timeout=settings.SMALL_TIMEOUT) if self.is_element_visible(selector, by=by): @@ -3137,7 +3147,7 @@ class BaseCase(unittest.TestCase): timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: timeout = self.__get_new_timeout(timeout) - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) orginal_selector = selector css_selector = self.convert_to_css_selector(selector, by=by) self.__demo_mode_highlight_if_active(orginal_selector, by) @@ -3249,7 +3259,7 @@ class BaseCase(unittest.TestCase): timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: timeout = self.__get_new_timeout(timeout) - selector, by = self.__recalculate_selector(selector, by) + selector, by = self.__recalculate_selector(selector, by, xp_ok=False) element = self.wait_for_element_visible( selector, by=by, timeout=timeout) self.__demo_mode_highlight_if_active(selector, by) @@ -6063,8 +6073,10 @@ class BaseCase(unittest.TestCase): pass return False - def __recalculate_selector(self, selector, by): - # Use auto-detection to return the correct selector with "by" updated + def __recalculate_selector(self, selector, by, xp_ok=True): + """ Use autodetection to return the correct selector with "by" updated. + If "xp_ok" is False, don't call convert_css_to_xpath(), which is + used to make the ":contains()" selector valid outside JS calls. """ _type = type(selector) # First make sure the selector is a string if _type is not str: msg = 'Expecting a selector of type: "" (string)!' @@ -6081,9 +6093,10 @@ class BaseCase(unittest.TestCase): name = page_utils.get_name_from_selector(selector) selector = '[name="%s"]' % name by = By.CSS_SELECTOR - if ":contains(" in selector and by == By.CSS_SELECTOR: - selector = self.convert_css_to_xpath(selector) - by = By.XPATH + if xp_ok: + if ":contains(" in selector and by == By.CSS_SELECTOR: + selector = self.convert_css_to_xpath(selector) + by = By.XPATH return (selector, by) def __looks_like_a_page_url(self, url): diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index 1e288b9a..72ca26c4 100755 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -90,7 +90,7 @@ class Messenger: class Underscore: - VER = "1.10.2" + VER = "1.12.0" MIN_JS = ("https://cdnjs.cloudflare.com/ajax/libs/" "underscore.js/%s/underscore-min.js" % VER) diff --git a/seleniumbase/fixtures/css_to_xpath.py b/seleniumbase/fixtures/css_to_xpath.py index db424f0c..ca202b50 100755 --- a/seleniumbase/fixtures/css_to_xpath.py +++ b/seleniumbase/fixtures/css_to_xpath.py @@ -39,7 +39,7 @@ class ConvertibleToCssTranslator(GenericTranslator): def xpath_class(self, class_selector): xpath = self.xpath(class_selector.selector) - return self.xpath_attrib_equals( + return self.xpath_attrib_includes( xpath, '@class', class_selector.class_name) def xpath_descendant_combinator(self, left, right): diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index cb2cdd85..d408158d 100755 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -475,20 +475,30 @@ def activate_messenger(driver): add_js_link(driver, underscore_js) add_css_link(driver, spinner_css) add_js_link(driver, messenger_js) - add_js_link(driver, msgr_theme_flat_js) - add_js_link(driver, msgr_theme_future_js) from seleniumbase.core import style_sheet add_css_style(driver, style_sheet.messenger_style) for x in range(int(settings.MINI_TIMEOUT * 10.0)): # Messenger needs a small amount of time to load & activate. try: - driver.execute_script(msg_style) - wait_for_ready_state_complete(driver) - wait_for_angularjs(driver) - return + result = (driver.execute_script( + """ if (typeof Messenger === 'undefined') { return "U"; } """)) + if result == "U": + time.sleep(0.01) + continue + else: + break except Exception: - time.sleep(0.1) + time.sleep(0.01) + try: + driver.execute_script(msg_style) + add_js_link(driver, msgr_theme_flat_js) + add_js_link(driver, msgr_theme_future_js) + wait_for_ready_state_complete(driver) + wait_for_angularjs(driver) + return + except Exception: + time.sleep(0.1) def set_messenger_theme(driver, theme="default", location="default", diff --git a/setup.py b/setup.py index 57955dfe..893219cb 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ if sys.argv[-1] == 'publish': print("\n*** Installing twine: *** (Required for PyPI uploads)\n") os.system("python -m pip install --upgrade 'twine>=1.15.0'") print("\n*** Installing tqdm: *** (Required for PyPI uploads)\n") - os.system("python -m pip install --upgrade 'tqdm>=4.55.2'") + os.system("python -m pip install --upgrade 'tqdm>=4.56.0'") print("\n*** Publishing The Release to PyPI: ***\n") os.system('python -m twine upload dist/*') # Requires ~/.pypirc Keys print("\n*** The Release was PUBLISHED SUCCESSFULLY to PyPI! :) ***\n") @@ -168,7 +168,7 @@ setup( 'colorama==0.4.4', 'pathlib2==2.3.5;python_version<"3.5"', # Sync with "virtualenv" 'importlib-metadata==2.0.0;python_version<"3.6"', # Sync "virtualenv" - 'virtualenv>=20.2.2', # Sync with importlib-metadata and pathlib2 + 'virtualenv>=20.3.0', # Sync with importlib-metadata and pathlib2 'pymysql==0.10.1;python_version<"3.6"', 'pymysql==1.0.2;python_version>="3.6"', 'coverage==5.3.1', @@ -180,7 +180,7 @@ setup( 'Pillow==6.2.2;python_version<"3.5"', 'Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"', 'Pillow==8.1.0;python_version>="3.6"', - 'rich==9.7.0;python_version>="3.6" and python_version<"4.0"', + 'rich==9.8.0;python_version>="3.6" and python_version<"4.0"', 'zipp==1.2.0;python_version<"3.6"', 'zipp==3.4.0;python_version>="3.6"', 'flake8==3.7.9;python_version<"3.5"',