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"',