From 2d9b0deaadce53fd7ccc84a1bfb1a0b0121b4745 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:33:49 -0400 Subject: [PATCH 1/8] Update the selenium ActionChains import --- seleniumbase/fixtures/base_case.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 95a96a0b..7dbde4fe 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -177,7 +177,7 @@ class BaseCase(unittest.TestCase): def double_click(self, selector, by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT): - from selenium.webdriver import ActionChains + from selenium.webdriver.common.action_chains import ActionChains if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) if page_utils.is_xpath_selector(selector): From 853bafe83409ce0a55ec81ea2ceed5c029e3a57b Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:34:11 -0400 Subject: [PATCH 2/8] Update a comment --- seleniumbase/fixtures/base_case.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 7dbde4fe..7367eaeb 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -1962,6 +1962,8 @@ class BaseCase(unittest.TestCase): def hover_and_click(self, hover_selector, click_selector, hover_by=By.CSS_SELECTOR, click_by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT): + """ When you want to hover over an element or dropdown menu, + and then click an element that appears after that. """ if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: timeout = self.__get_new_timeout(timeout) hover_selector, hover_by = self.__recalculate_selector( From 87a7f921546479e48d8b24a63edd822594c77285 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:35:49 -0400 Subject: [PATCH 3/8] Update requirements of pip, setuptools, and wheel --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8fdd84d5..5f062a55 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -pip>=19.2.2 -setuptools>=41.1.0 -wheel>=0.33.4 +pip>=19.2.3 +setuptools>=41.2.0 +wheel>=0.33.6 six>=1.12.0 nose>=1.3.7 ipdb>=0.12.2 From c4781d7cdbf5bf5235bbb6b12ec0364c350897e7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:42:55 -0400 Subject: [PATCH 4/8] Fix issue with auto-closing alert pop-ups --- seleniumbase/fixtures/base_case.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 7367eaeb..c12b8cbb 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -2411,6 +2411,13 @@ class BaseCase(unittest.TestCase): ############ def wait_for_ready_state_complete(self, timeout=settings.EXTREME_TIMEOUT): + try: + # If there's an alert, skip + self.driver.switch_to.alert + return + except Exception: + # If there's no alert, continue + pass if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT: timeout = self.__get_new_timeout(timeout) is_ready = js_utils.wait_for_ready_state_complete(self.driver, timeout) From 6c11ace20dd1e3bce1effb325abcd9b728a3d2be Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:44:40 -0400 Subject: [PATCH 5/8] Add hover_and_double_click() method --- help_docs/method_summary.md | 3 +++ seleniumbase/fixtures/base_case.py | 29 +++++++++++++++++++++++++++ seleniumbase/fixtures/page_actions.py | 25 +++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 58d18fb0..b0ce8e62 100755 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -230,6 +230,9 @@ self.hover_on_element(selector, by=By.CSS_SELECTOR) self.hover_and_click(hover_selector, click_selector, hover_by=By.CSS_SELECTOR, click_by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT) +self.hover_and_double_click(hover_selector, click_selector, + hover_by=By.CSS_SELECTOR, click_by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT) + self.select_option_by_text(dropdown_selector, option, dropdown_by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index c12b8cbb..eead3f5d 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -1987,6 +1987,35 @@ class BaseCase(unittest.TestCase): self.__demo_mode_pause_if_active(tiny=True) return element + def hover_and_double_click(self, hover_selector, click_selector, + hover_by=By.CSS_SELECTOR, + click_by=By.CSS_SELECTOR, + timeout=settings.SMALL_TIMEOUT): + """ When you want to hover over an element or dropdown menu, + and then double-click an element that appears after that. """ + if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: + timeout = self.__get_new_timeout(timeout) + hover_selector, hover_by = self.__recalculate_selector( + hover_selector, hover_by) + hover_selector = self.convert_to_css_selector( + hover_selector, hover_by) + click_selector, click_by = self.__recalculate_selector( + click_selector, click_by) + hover_element = self.wait_for_element_visible( + hover_selector, by=hover_by, timeout=timeout) + self.__demo_mode_highlight_if_active(hover_selector, hover_by) + self.scroll_to(hover_selector, by=hover_by) + pre_action_url = self.driver.current_url + click_element = page_actions.hover_element_and_double_click( + self.driver, hover_element, click_selector, + click_by=By.CSS_SELECTOR, timeout=timeout) + if self.demo_mode: + if self.driver.current_url != pre_action_url: + self.__demo_mode_pause_if_active() + else: + self.__demo_mode_pause_if_active(tiny=True) + return click_element + def __select_option(self, dropdown_selector, option, dropdown_by=By.CSS_SELECTOR, option_by="text", timeout=settings.SMALL_TIMEOUT): diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index ef88230d..f09c006c 100755 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -158,6 +158,31 @@ def hover_element_and_click(driver, element, click_selector, (click_selector, timeout)) +def hover_element_and_double_click(driver, element, click_selector, + click_by=By.CSS_SELECTOR, + timeout=settings.SMALL_TIMEOUT): + start_ms = time.time() * 1000.0 + stop_ms = start_ms + (timeout * 1000.0) + hover = ActionChains(driver).move_to_element(element) + hover.perform() + for x in range(int(timeout * 10)): + try: + element_2 = driver.find_element(by=click_by, value=click_selector) + actions = ActionChains(driver) + actions.move_to_element(element_2) + actions.double_click(element_2) + actions.perform() + return element_2 + except Exception: + now_ms = time.time() * 1000.0 + if now_ms >= stop_ms: + break + time.sleep(0.1) + raise NoSuchElementException( + "Element {%s} was not present after %s seconds!" % + (click_selector, timeout)) + + def wait_for_element_present(driver, selector, by=By.CSS_SELECTOR, timeout=settings.LARGE_TIMEOUT): """ From d3735d0790a830174a971efab62ea787e731d10a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:50:17 -0400 Subject: [PATCH 6/8] Allow version input with "seleniumbase install chromedriver" --- seleniumbase/console_scripts/run.py | 11 +++- seleniumbase/console_scripts/sb_install.py | 77 ++++++++++++++-------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py index 9772da51..3ab83423 100644 --- a/seleniumbase/console_scripts/run.py +++ b/seleniumbase/console_scripts/run.py @@ -37,7 +37,7 @@ def show_basic_usage(): print("") print('Usage: "seleniumbase [COMMAND] [PARAMETERS]"') print("Commands:") - print(" install [DRIVER_NAME]") + print(" install [DRIVER_NAME] [OPTIONS]") print(" mkdir [NEW_TEST_DIRECTORY_NAME]") print(" convert [PYTHON_WEBDRIVER_UNITTEST_FILE]") print(" extract-objects [SELENIUMBASE_PYTHON_FILE]") @@ -55,11 +55,18 @@ def show_install_usage(): print(" ** install **") print("") print(" Usage:") - print(" seleniumbase install [DRIVER_NAME]") + print(" seleniumbase install [DRIVER_NAME] [OPTIONS]") print(" (Drivers: chromedriver, geckodriver, edgedriver") print(" iedriver, operadriver)") + print(" Options:") + print(" VERSION - Specify the version (For Chromedriver ONLY).") + print(" (Default Chromedriver version = 2.44)") + print(' Use "latest" to get the latest Chromedriver.') print(" Example:") print(" seleniumbase install chromedriver") + print(" seleniumbase install chromedriver 76.0.3809.126") + print(" seleniumbase install chromedriver latest") + print(" seleniumbase install geckodriver") print(" Output:") print(" Installs the specified webdriver.") print(" (chromedriver is required for Chrome automation)") diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index d8b797f3..6505a6d4 100755 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -3,7 +3,16 @@ Installs the specified web driver. Usage: seleniumbase install {chromedriver|geckodriver|edgedriver| - iedriver|operadriver} + iedriver|operadriver} [OPTIONS] +Options: + VERSION - Specify the version (For Chromedriver ONLY) + (Default Chromedriver version = 2.44) + Use "latest" to get the latest Chromedriver. +Example: + seleniumbase install chromedriver + seleniumbase install chromedriver 76.0.3809.126 + seleniumbase install chromedriver latest + seleniumbase install geckodriver Output: Installs the specified webdriver. (chromedriver is required for Chrome automation) @@ -24,16 +33,24 @@ import zipfile from seleniumbase import drivers # webdriver storage folder for SeleniumBase urllib3.disable_warnings() DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__)) +DEFAULT_CHROMEDRIVER_VERSION = "2.44" def invalid_run_command(): exp = (" ** install **\n\n") exp += " Usage:\n" - exp += " seleniumbase install [DRIVER_NAME]\n" + exp += " seleniumbase install [DRIVER_NAME] [OPTIONS]\n" exp += " (Drivers: chromedriver, geckodriver, edgedriver,\n" exp += " iedriver, operadriver)\n" + exp += " Options:\n" + exp += " VERSION - Specify the version (For Chromedriver ONLY)." + exp += " (Default Chromedriver version = 2.44)" + exp += ' Use "latest" to get the latest Chromedriver.' exp += " Example:\n" exp += " seleniumbase install chromedriver\n" + exp += " seleniumbase install chromedriver 76.0.3809.126\n" + exp += " seleniumbase install chromedriver latest\n" + exp += " seleniumbase install geckodriver\n" exp += " Output:\n" exp += " Installs the specified webdriver.\n" exp += " (chromedriver is required for Chrome automation)\n" @@ -56,11 +73,11 @@ def main(): num_args = len(sys.argv) if sys.argv[0].split('/')[-1].lower() == "seleniumbase" or ( sys.argv[0].split('\\')[-1].lower() == "seleniumbase"): - if num_args < 3 or num_args > 3: + if num_args < 3 or num_args > 4: invalid_run_command() else: invalid_run_command() - name = sys.argv[num_args - 1].lower() + name = sys.argv[2].lower() file_name = None download_url = None @@ -71,52 +88,56 @@ def main(): inner_folder = None if name == "chromedriver": - latest_version = "2.44" # It's not the latest, but most compatible + use_version = DEFAULT_CHROMEDRIVER_VERSION + get_latest = False + if num_args == 4: + use_version = sys.argv[3] + if use_version.lower() == "latest": + get_latest = True if "darwin" in sys_plat: file_name = "chromedriver_mac64.zip" elif "linux" in sys_plat: - latest_version = "2.44" # Linux machines may need the old driver file_name = "chromedriver_linux64.zip" elif "win32" in sys_plat or "win64" in sys_plat or "x64" in sys_plat: file_name = "chromedriver_win32.zip" # Works for win32 / win_x64 else: raise Exception("Cannot determine which version of Chromedriver " "to download!") - download_url = ("http://chromedriver.storage.googleapis.com/" - "%s/%s" % (latest_version, file_name)) - # Forcing Chromedriver v2.40 for now, even though it's not the latest. - get_latest = False + found_chromedriver = False if get_latest: last = "http://chromedriver.storage.googleapis.com/LATEST_RELEASE" - print('\nLocating the latest version of Chromedriver...') - latest_version = requests.get(last).text - if not requests.get(download_url).ok: - fallback_version = "2.44" - download_url = ("http://chromedriver.storage.googleapis.com/" - "%s/%s" % (fallback_version, file_name)) - else: - download_url = ("http://chromedriver.storage.googleapis.com/" - "%s/%s" % (latest_version, file_name)) - print("Found %s" % download_url) + url_request = requests.get(last) + if url_request.ok: + found_chromedriver = True + use_version = url_request.text + download_url = ("http://chromedriver.storage.googleapis.com/" + "%s/%s" % (use_version, file_name)) + url_request = None + if not found_chromedriver: + url_request = requests.get(download_url) + if found_chromedriver or url_request.ok: + print("\nChromedriver version for download = %s" % use_version) + else: + raise Exception("Could not find Chromedriver to download!\n") elif name == "geckodriver" or name == "firefoxdriver": - latest_version = "v0.24.0" + use_version = "v0.24.0" if "darwin" in sys_plat: - file_name = "geckodriver-%s-macos.tar.gz" % latest_version + file_name = "geckodriver-%s-macos.tar.gz" % use_version elif "linux" in sys_plat: arch = platform.architecture()[0] if "64" in arch: - file_name = "geckodriver-%s-linux64.tar.gz" % latest_version + file_name = "geckodriver-%s-linux64.tar.gz" % use_version else: - file_name = "geckodriver-%s-linux32.tar.gz" % latest_version + file_name = "geckodriver-%s-linux32.tar.gz" % use_version elif "win32" in sys_plat or "win64" in sys_plat or "x64" in sys_plat: - file_name = "geckodriver-%s-win64.zip" % latest_version + file_name = "geckodriver-%s-win64.zip" % use_version else: raise Exception("Cannot determine which version of Geckodriver " "(Firefox Driver) to download!") download_url = ("https://github.com/mozilla/geckodriver/" "releases/download/" - "%s/%s" % (latest_version, file_name)) + "%s/%s" % (use_version, file_name)) elif name == "edgedriver" or name == "microsoftwebdriver": name = "edgedriver" version_code = "F/8/A/F8AF50AB-3C3A-4BC4-8773-DC27B32988DD" @@ -141,7 +162,7 @@ def main(): "%s/%s" % (major_version, file_name)) elif name == "operadriver" or name == "operachromiumdriver": name = "operadriver" - latest_version = "v.2.40" + use_version = "v.2.40" if "darwin" in sys_plat: file_name = "operadriver_mac64.zip" platform_code = "mac64" @@ -176,7 +197,7 @@ def main(): download_url = ("https://github.com/operasoftware/operachromiumdriver/" "releases/download/" - "%s/%s" % (latest_version, file_name)) + "%s/%s" % (use_version, file_name)) else: invalid_run_command() From e9f02a338f48e42ad5c82d0d6f10f801f781f3e0 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:51:45 -0400 Subject: [PATCH 7/8] Update the minimum pytest version to use --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5f062a55..6671b4f1 100755 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ requests>=2.22.0 selenium==3.141.0 pluggy>=0.12.0 pytest>=4.6.5;python_version<"3" -pytest>=5.1.0;python_version>="3" +pytest>=5.1.2;python_version>="3" pytest-cov>=2.7.1 pytest-forked>=1.0.2 pytest-html==1.22.0 diff --git a/setup.py b/setup.py index 8ef4d09c..48203a45 100755 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ setup( 'selenium==3.141.0', 'pluggy>=0.12.0', 'pytest>=4.6.5;python_version<"3"', # For Python 2 compatibility - 'pytest>=5.1.0;python_version>="3"', + 'pytest>=5.1.2;python_version>="3"', 'pytest-cov>=2.7.1', 'pytest-forked>=1.0.2', 'pytest-html==1.22.0', # Keep at 1.22.0 unless tested on Windows From a3ac4d4839dd4df30cf2ebe46f41214e3cd71c46 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Fri, 30 Aug 2019 20:53:09 -0400 Subject: [PATCH 8/8] Version 1.31.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 48203a45..4385e6ed 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ except IOError: setup( name='seleniumbase', - version='1.30.0', + version='1.31.0', description='Fast, Easy, and Reliable Browser Automation & Testing.', long_description=long_description, long_description_content_type='text/markdown',